X Tutup
#region License // The PostgreSQL License // // Copyright (C) 2016 The Npgsql Development Team // // 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. #endregion using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Data.Common; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Diagnostics.Contracts; using System.Globalization; using System.Net.Sockets; using AsyncRewriter; using JetBrains.Annotations; using Npgsql.BackendMessages; using Npgsql.FrontendMessages; using Npgsql.Logging; 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 #if NETSTANDARD1_3 public sealed partial class NpgsqlCommand : DbCommand #else // ReSharper disable once RedundantNameQualifier [System.ComponentModel.DesignerCategory("")] public sealed partial class NpgsqlCommand : DbCommand, ICloneable #endif { #region Fields NpgsqlConnection _connection; /// /// Cached version of _connection.Connector, for performance /// NpgsqlConnector _connector; NpgsqlTransaction _transaction; String _commandText; int? _timeout; readonly NpgsqlParameterCollection _parameters = new NpgsqlParameterCollection(); List _statements; /// /// Returns details about each statement that this command has executed. /// Is only populated when an Execute* method is called. /// public IReadOnlyList Statements => _statements.AsReadOnly(); int _readStatementIndex; int _writeStatementIndex; /// /// If part of the send happens asynchronously (see , /// the Task for that remaining send is stored here. /// internal Task RemainingSendTask; UpdateRowSource _updateRowSource = UpdateRowSource.Both; /// /// Indicates whether this command has been prepared. /// Never access this field directly, use instead. /// bool _isPrepared; /// /// For prepared commands, captures the connection's /// at the time the command was prepared. This allows us to know whether the connection was /// closed since the command was prepared. /// int _prepareConnectionOpenId; static readonly NpgsqlLogger Log = NpgsqlLogManager.GetCurrentClassLogger(); #endregion Fields #region Constants internal const int DefaultTimeout = 30; #endregion #region 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. // ReSharper disable once IntroduceOptionalParameters.Global 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. // ReSharper disable once IntroduceOptionalParameters.Global 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, [CanBeNull] NpgsqlConnection connection, [CanBeNull] NpgsqlTransaction transaction) { GC.SuppressFinalize(this); Init(cmdText); Connection = connection; Transaction = transaction; } void Init(string cmdText) { _commandText = cmdText; CommandType = CommandType.Text; _statements = new List(); } #endregion Constructors #region 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. [DefaultValue("")] [Category("Data")] public override string CommandText { get { return _commandText; } set { if (value == null) throw new ArgumentNullException(nameof(value)); Contract.EndContractBlock(); _commandText = value; DeallocatePrepared(); } } /// /// 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 value is 30 seconds. [DefaultValue(DefaultTimeout)] public override int CommandTimeout { get { return _timeout ?? (_connection?.CommandTimeout ?? DefaultTimeout); } set { if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value), value, "CommandTimeout can't be less than zero."); } _timeout = 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. [DefaultValue(CommandType.Text)] [Category("Data")] public override CommandType CommandType { get; set; } /// /// DB connection. /// protected override DbConnection DbConnection { get { return Connection; } set { Connection = (NpgsqlConnection)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. [DefaultValue(null)] [Category("Behavior")] public new NpgsqlConnection Connection { get { return _connection; } set { if (_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 (_transaction != null && _connection != null && _connection.Connector != null && _connection.Connector.InTransaction) { throw new InvalidOperationException("The Connection property can't be changed with an uncommited transaction."); } IsPrepared = false; _connection = value; Transaction = null; } } /// /// Design time visible. /// public override bool DesignTimeVisible { get; set; } /// /// Gets or sets how command results are applied to the DataRow when used by the /// DbDataAdapter.Update(DataSet) method. /// /// One of the UpdateRowSource values. [Category("Behavior"), DefaultValue(UpdateRowSource.Both)] public override UpdateRowSource UpdatedRowSource { get { 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 whether this query will execute as a prepared (compiled) query. /// public bool IsPrepared { get { if (_isPrepared) { Contract.Assert(Connection != null); if (Connection.State != ConnectionState.Open || _prepareConnectionOpenId != Connection.OpenCounter) { _isPrepared = false; } } return _isPrepared; } private set { Contract.Requires(!value || Connection != null); _isPrepared = value; if (value) { _prepareConnectionOpenId = Connection.OpenCounter; } } } #endregion Public properties #region Known/unknown Result Types Management /// /// Marks all of the query's result columns as either known or unknown. /// Unknown results column are requested them from PostgreSQL in text format, and Npgsql makes no /// attempt to parse them. They will be accessible as strings only. /// public bool AllResultTypesAreUnknown { private get { return _allResultTypesAreUnknown; } set { // TODO: Check that this isn't modified after calling prepare _unknownResultTypeList = null; _allResultTypesAreUnknown = value; } } bool _allResultTypesAreUnknown; /// /// Marks the query's result columns as known or unknown, on a column-by-column basis. /// Unknown results column are requested them from PostgreSQL in text format, and Npgsql makes no /// attempt to parse them. They will be accessible as strings only. /// /// /// If the query includes several queries (e.g. SELECT 1; SELECT 2), this will only apply to the first /// one. The rest of the queries will be fetched and parsed as usual. /// /// The array size must correspond exactly to the number of result columns the query returns, or an /// error will be raised. /// public bool[] UnknownResultTypeList { private get { return _unknownResultTypeList; } set { // TODO: Check that this isn't modified after calling prepare _allResultTypesAreUnknown = false; _unknownResultTypeList = value; } } bool[] _unknownResultTypeList; #endregion #region Result Types Management /// /// Marks result types to be used when using GetValue on a data reader, on a column-by-column basis. /// Used for Entity Framework 5-6 compability. /// Only primitive numerical types and DateTimeOffset are supported. /// Set the whole array or just a value to null to use default type. /// internal Type[] ObjectResultTypes { get; set; } #endregion #region State management int _state; /// /// Gets the current state of the connector /// internal CommandState State { private get { return (CommandState)_state; } set { var newState = (int)value; if (newState == _state) return; Interlocked.Exchange(ref _state, newState); } } SendState _sendState; #endregion State management #region Parameters /// /// Creates a new instance of an DbParameter object. /// /// An DbParameter object. protected override DbParameter CreateDbParameter() { return CreateParameter(); } /// /// Creates a new instance of a NpgsqlParameter object. /// /// A NpgsqlParameter object. public new NpgsqlParameter CreateParameter() { return new NpgsqlParameter(); } /// /// DB parameter collection. /// protected override DbParameterCollection DbParameterCollection => 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 => _parameters; #endregion #region Prepare /// /// Creates a prepared version of the command on a PostgreSQL server. /// public override void Prepare() { _connector = CheckReadyAndGetConnector(); if (Parameters.Any(p => !p.IsTypeExplicitlySet)) throw new InvalidOperationException("The Prepare method requires all parameters to have an explicitly set type."); Log.Debug("Preparing: " + CommandText, _connector.Id); using (_connector.StartUserAction()) { DeallocatePrepared(); ProcessRawQuery(); _sendState = SendState.Start; _writeStatementIndex = 0; Send(PopulatePrepare); _readStatementIndex = 0; while (true) { var msg = _connector.ReadMessage(DataRowLoadingMode.NonSequential); switch (msg.Code) { case BackendMessageCode.CompletedResponse: // prepended messages, e.g. begin transaction case BackendMessageCode.ParseComplete: case BackendMessageCode.ParameterDescription: continue; case BackendMessageCode.RowDescription: var description = (RowDescriptionMessage) msg; FixupRowDescription(description, _readStatementIndex == 0); _statements[_readStatementIndex++].Description = description; continue; case BackendMessageCode.NoData: _statements[_readStatementIndex++].Description = null; continue; case BackendMessageCode.ReadyForQuery: Contract.Assume(_readStatementIndex == _statements.Count); IsPrepared = true; return; default: throw _connector.UnexpectedMessageReceived(msg.Code); } } } } void DeallocatePrepared() { if (!IsPrepared) return; _connector = CheckReadyAndGetConnector(); using (_connector.StartUserAction()) { _writeStatementIndex = 0; Send(PopulateDeallocate); for (var i = 0; i < _statements.Count; i++) _connector.ReadExpecting(); _connector.ReadExpecting(); IsPrepared = false; } } #endregion Prepare #region Query analysis void ProcessRawQuery() { _statements.Clear(); switch (CommandType) { case CommandType.Text: SqlQueryParser.ParseRawQuery(CommandText, _connection == null || _connection.UseConformantStrings, _parameters, _statements); if (_statements.Count > 1 && _parameters.Any(p => p.IsOutputDirection)) { throw new NotSupportedException("Commands with multiple queries cannot have out parameters"); } break; case CommandType.TableDirect: _statements.Add(new NpgsqlStatement("SELECT * FROM " + CommandText, new List())); break; case CommandType.StoredProcedure: var inputList = _parameters.Where(p => p.IsInputDirection).ToList(); var numInput = inputList.Count; var sb = new StringBuilder(); sb.Append("SELECT * FROM "); sb.Append(CommandText); sb.Append('('); bool hasWrittenFirst = false; for (var i = 1; i <= numInput; i++) { var param = inputList[i - 1]; if (param.AutoAssignedName || param.CleanName == "") { if (hasWrittenFirst) { sb.Append(','); } sb.Append('$'); sb.Append(i); hasWrittenFirst = true; } } for (var i = 1; i <= numInput; i++) { var param = inputList[i - 1]; if (!param.AutoAssignedName && param.CleanName != "") { if (hasWrittenFirst) { sb.Append(','); } sb.Append('"'); sb.Append(param.CleanName.Replace("\"", "\"\"")); sb.Append("\" := "); sb.Append('$'); sb.Append(i); hasWrittenFirst = true; } } sb.Append(')'); _statements.Add(new NpgsqlStatement(sb.ToString(), inputList)); break; default: throw PGUtil.ThrowIfReached(); } } #endregion #region Execute void Validate() { if (Parameters.Count > 65535) throw new Exception("A command cannot have more than 65535 parameters"); foreach (NpgsqlParameter p in Parameters.Where(p => p.IsInputDirection)) { p.Bind(Connection.Connector.TypeHandlerRegistry); p.LengthCache?.Clear(); p.ValidateAndGetLength(); } } [RewriteAsync] NpgsqlDataReader Execute(CommandBehavior behavior = CommandBehavior.Default) { Validate(); if (!IsPrepared) ProcessRawQuery(); LogCommand(); State = CommandState.InProgress; try { _connector = Connection.Connector; // If a cancellation is in progress, wait for it to "complete" before proceeding (#615) lock (_connector.CancelLock) { } // Send protocol messages for the command // Unless this is a prepared SchemaOnly command, in which case we already have the RowDescriptions // from the Prepare phase (no need to send anything). if (!IsPrepared || (behavior & CommandBehavior.SchemaOnly) == 0) { _connector.UserTimeout = CommandTimeout * 1000; _sendState = SendState.Start; _writeStatementIndex = 0; if (IsPrepared) Send(PopulateExecutePrepared); else if ((behavior & CommandBehavior.SchemaOnly) == 0) Send(PopulateExecuteNonPrepared); else Send(PopulateExecuteSchemaOnly); } var reader = new NpgsqlDataReader(this, behavior, _statements); reader.NextResult(); _connector.CurrentReader = reader; return reader; } catch { State = CommandState.Idle; throw; } } #endregion #region Send delegate bool PopulateMethod(ref DirectBuffer directBuf); [RewriteAsync] void Send(PopulateMethod populateMethod) { while (true) { var directBuf = new DirectBuffer(); var completed = populateMethod(ref directBuf); _connector.SendBuffer(); if (completed) break; // Sent all messages // The following is an optimization hack for writing large byte arrays without passing // through our buffer if (directBuf.Buffer != null) { _connector.WriteBuffer.DirectWrite(directBuf.Buffer, directBuf.Offset, directBuf.Size == 0 ? directBuf.Buffer.Length : directBuf.Size); directBuf.Buffer = null; directBuf.Size = 0; } if (_writeStatementIndex > 0) { // We've send all the messages for the first statement in a multistatement command. // If we continue blocking writes for the rest of the messages, we risk a deadlock where // PostgreSQL sends large results for the first statement, while we're sending large // parameter data for the second. To avoid this, switch to async sends. // See #641 RemainingSendTask = SendRemaining(populateMethod, CancellationToken.None); return; } } } /// /// This method is used to asynchronously sends all remaining protocol messages for statements /// beyond the first one, and *without* waiting for the send to complete. This technique is /// used to avoid the deadlock described in #641 by allowing the user to read query results /// while at the same time sending messages for later statements. /// async Task SendRemaining(PopulateMethod populateMethod, CancellationToken cancellationToken) { Contract.Requires(_writeStatementIndex > 0); try { while (true) { var directBuf = new DirectBuffer(); var completed = populateMethod(ref directBuf); await _connector.SendBufferAsync(cancellationToken).ConfigureAwait(false); if (completed) return; // Sent all messages // The following is an optimization hack for writing large byte arrays without passing // through our buffer if (directBuf.Buffer != null) { await _connector.WriteBuffer.DirectWriteAsync(directBuf.Buffer, directBuf.Offset, directBuf.Size == 0 ? directBuf.Buffer.Length : directBuf.Size, cancellationToken ).ConfigureAwait(false); directBuf.Buffer = null; directBuf.Size = 0; } } } catch (Exception e) { Log.Error("Exception while asynchronously sending remaining messages", e, _connector.Id); } } #endregion #region Message Creation / Population /// /// Populates the send buffer with protocol messages for the execution of non-prepared statement(s). /// /// /// true whether all messages could be populated in the buffer, false otherwise (method needs to be /// called again) /// bool PopulateExecuteNonPrepared(ref DirectBuffer directBuf) { Contract.Requires(_connector != null); var buf = _connector.WriteBuffer; for (; _writeStatementIndex < _statements.Count; _writeStatementIndex++) { var statement = _statements[_writeStatementIndex]; switch (_sendState) { case SendState.Start: _connector.ParseMessage.Populate(statement, _connector.TypeHandlerRegistry); _sendState = SendState.Parse; goto case SendState.Parse; case SendState.Parse: if (!_connector.ParseMessage.Write(buf)) return false; var bind = _connector.BindMessage; bind.Populate(statement.InputParameters); if (AllResultTypesAreUnknown) bind.AllResultTypesAreUnknown = AllResultTypesAreUnknown; else if (_writeStatementIndex == 0 && UnknownResultTypeList != null) bind.UnknownResultTypeList = UnknownResultTypeList; _sendState = SendState.Bind; goto case SendState.Bind; case SendState.Bind: if (!_connector.BindMessage.Write(buf, ref directBuf)) return false; var describe = _connector.DescribeMessage; describe.Populate(StatementOrPortal.Portal); _sendState = SendState.Describe; goto case SendState.Describe; case SendState.Describe: describe = _connector.DescribeMessage; if (describe.Length > buf.WriteSpaceLeft) return false; describe.WriteFully(buf); var execute = _connector.ExecuteMessage; execute.Populate(); _sendState = SendState.Execute; goto case SendState.Execute; case SendState.Execute: execute = _connector.ExecuteMessage; if (execute.Length > buf.WriteSpaceLeft) return false; execute.WriteFully(buf); _sendState = SendState.Start; continue; default: throw new ArgumentOutOfRangeException($"Invalid state {_sendState} in {nameof(PopulateExecuteNonPrepared)}"); } } if (SyncMessage.Instance.Length > buf.WriteSpaceLeft) return false; SyncMessage.Instance.WriteFully(buf); return true; } /// /// Populates the send buffer with protocol messages for the execution of prepared statement(s). /// /// /// true whether all messages could be populated in the buffer, false otherwise (method needs to be /// called again) /// bool PopulateExecutePrepared(ref DirectBuffer directBuf) { Contract.Requires(_connector != null); var buf = _connector.WriteBuffer; for (; _writeStatementIndex < _statements.Count; _writeStatementIndex++) { var statement = _statements[_writeStatementIndex]; switch (_sendState) { case SendState.Start: var bind = _connector.BindMessage; bind.Populate(statement.InputParameters, "", statement.PreparedStatementName); if (AllResultTypesAreUnknown) bind.AllResultTypesAreUnknown = AllResultTypesAreUnknown; else if (_writeStatementIndex == 0 && UnknownResultTypeList != null) bind.UnknownResultTypeList = UnknownResultTypeList; _sendState = SendState.Bind; goto case SendState.Bind; case SendState.Bind: if (!_connector.BindMessage.Write(buf, ref directBuf)) return false; var execute = _connector.ExecuteMessage; execute.Populate(); _sendState = SendState.Execute; goto case SendState.Execute; case SendState.Execute: execute = _connector.ExecuteMessage; if (execute.Length > buf.WriteSpaceLeft) return false; execute.WriteFully(buf); _sendState = SendState.Start; continue; default: throw new ArgumentOutOfRangeException($"Invalid state {_sendState} in {nameof(PopulateExecutePrepared)}"); } } if (SyncMessage.Instance.Length > buf.WriteSpaceLeft) return false; SyncMessage.Instance.WriteFully(buf); return true; } /// /// Populates the send buffer with Parse/Describe protocol messages, used for preparing commands /// and for execution in SchemaOnly mode. /// /// /// true whether all messages could be populated in the buffer, false otherwise (method needs to be /// called again) /// bool PopulatePrepare(ref DirectBuffer directBuf) => PopulateParseDescribe(true); /// /// Populates the send buffer with Parse/Describe protocol messages, used for preparing commands /// and for execution in SchemaOnly mode. /// /// /// true whether all messages could be populated in the buffer, false otherwise (method needs to be /// called again) /// bool PopulateExecuteSchemaOnly(ref DirectBuffer directBuf) => PopulateParseDescribe(false); bool PopulateParseDescribe(bool isPreparing) { Contract.Requires(_connector != null); var buf = _connector.WriteBuffer; for (; _writeStatementIndex < _statements.Count; _writeStatementIndex++) { var statement = _statements[_writeStatementIndex]; switch (_sendState) { case SendState.Start: if (isPreparing) statement.PreparedStatementName = _connector.NextPreparedStatementName(); _connector.ParseMessage.Populate(statement, _connector.TypeHandlerRegistry); _sendState = SendState.Parse; goto case SendState.Parse; case SendState.Parse: if (!_connector.ParseMessage.Write(buf)) return false; var describe = _connector.DescribeMessage; describe.Populate(StatementOrPortal.Statement, statement.PreparedStatementName); _sendState = SendState.Describe; goto case SendState.Describe; case SendState.Describe: describe = _connector.DescribeMessage; if (describe.Length > buf.WriteSpaceLeft) return false; describe.WriteFully(buf); _sendState = SendState.Start; continue; default: throw new ArgumentOutOfRangeException($"Invalid state {_sendState} in {nameof(PopulateParseDescribe)}"); } } if (SyncMessage.Instance.Length > buf.WriteSpaceLeft) return false; SyncMessage.Instance.WriteFully(buf); return true; } bool PopulateDeallocate(ref DirectBuffer directBuf) { Contract.Requires(_connector != null); var buf = _connector.WriteBuffer; for (; _writeStatementIndex < _statements.Count; _writeStatementIndex++) { var statement = _statements[_writeStatementIndex]; var closeMsg = new CloseMessage(StatementOrPortal.Statement, statement.PreparedStatementName); if (closeMsg.Length > buf.WriteSpaceLeft) return false; closeMsg.WriteFully(buf); } if (SyncMessage.Instance.Length > buf.WriteSpaceLeft) return false; SyncMessage.Instance.WriteFully(buf); return true; } #endregion #region Execute Non Query /// /// Executes a SQL statement against the connection and returns the number of rows affected. /// /// The number of rows affected if known; -1 otherwise. public override int ExecuteNonQuery() => ExecuteNonQueryInternal(); /// /// Asynchronous version of /// /// The token to monitor for cancellation requests. /// A task representing the asynchronous operation, with the number of rows affected if known; -1 otherwise. public override async Task ExecuteNonQueryAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (cancellationToken.Register(Cancel)) return await ExecuteNonQueryInternalAsync(cancellationToken).ConfigureAwait(false); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [RewriteAsync] int ExecuteNonQueryInternal() { var connector = CheckReadyAndGetConnector(); using (connector.StartUserAction()) { Log.Trace("ExecuteNonQuery", connector.Id); NpgsqlDataReader reader; using (reader = Execute()) { while (reader.NextResult()) { } } return reader.RecordsAffected; } } #endregion Execute Non Query #region Execute Scalar /// /// 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. [CanBeNull] public override object ExecuteScalar() => ExecuteScalarInternal(); /// /// Asynchronous version of /// /// The token to monitor for cancellation requests. /// A task representing the asynchronous operation, with the first column of the /// first row in the result set, or a null reference if the result set is empty. public override async Task ExecuteScalarAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (cancellationToken.Register(Cancel)) { return await ExecuteScalarInternalAsync(cancellationToken).ConfigureAwait(false); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] [RewriteAsync] [CanBeNull] object ExecuteScalarInternal() { var connector = CheckReadyAndGetConnector(); using (connector.StartUserAction()) { Log.Trace("ExecuteNonScalar", connector.Id); using (var reader = Execute(CommandBehavior.SequentialAccess | CommandBehavior.SingleRow)) return reader.Read() && reader.FieldCount != 0 ? reader.GetValue(0) : null; } } #endregion Execute Scalar #region Execute Reader /// /// Executes the CommandText against the Connection, and returns an DbDataReader. /// /// /// Unlike the ADO.NET method which it replaces, this method returns a Npgsql-specific /// DataReader. /// /// A DbDataReader object. public new NpgsqlDataReader ExecuteReader() => (NpgsqlDataReader) base.ExecuteReader(); /// /// Executes the CommandText against the Connection, and returns an DbDataReader using one /// of the CommandBehavior values. /// /// /// Unlike the ADO.NET method which it replaces, this method returns a Npgsql-specific /// DataReader. /// /// A DbDataReader object. public new NpgsqlDataReader ExecuteReader(CommandBehavior behavior) => (NpgsqlDataReader) base.ExecuteReader(behavior); /// /// Executes the command text against the connection. /// /// An instance of . /// A task representing the operation. /// protected override async Task ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (cancellationToken.Register(Cancel)) return await ExecuteDbDataReaderInternalAsync(behavior, cancellationToken).ConfigureAwait(false); } /// /// Executes the command text against the connection. /// [NotNull] protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => ExecuteDbDataReaderInternal(behavior); [MethodImpl(MethodImplOptions.AggressiveInlining)] [RewriteAsync] NpgsqlDataReader ExecuteDbDataReaderInternal(CommandBehavior behavior) { var connector = CheckReadyAndGetConnector(); connector.StartUserAction(); try { Log.Trace("ExecuteReader", connector.Id); return Execute(behavior); } catch { Connection.Connector?.EndUserAction(); // Close connection if requested even when there is an error. if ((behavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection) _connection.Close(); throw; } } #endregion #region Transactions /// /// DB transaction. /// protected override DbTransaction DbTransaction { get { return Transaction; } set { Transaction = (NpgsqlTransaction) 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 { if (_transaction != null && _transaction.Connection == null) { _transaction = null; } return _transaction; } set { _transaction = value; } } #endregion Transactions #region Cancel /// /// Attempts to cancel the execution of a NpgsqlCommand. /// /// As per the specs, no exception will be thrown by this method in case of failure public override void Cancel() { var connector = Connection?.Connector; if (connector == null) return; if (State != CommandState.InProgress) { Log.Debug($"Skipping cancel because command is in state {State}", connector.Id); return; } Log.Debug("Cancelling command", connector.Id); try { connector.CancelRequest(); } catch (Exception e) { var socketException = e.InnerException as SocketException; if (socketException == null || socketException.SocketErrorCode != SocketError.ConnectionReset) Log.Debug("Exception caught while attempting to cancel command", e, connector.Id); } } #endregion Cancel #region Dispose /// /// Releases the resources used by the NpgsqlCommand. /// protected override void Dispose(bool disposing) { if (State == CommandState.Disposed) return; if (disposing) { // Note: we only actually perform cleanup here if called from Dispose() (disposing=true), and not // if called from a finalizer (disposing=false). This is because we cannot perform any SQL // operations from the finalizer (connection may be in use by someone else). Prepared statements // which aren't explicitly disposed are leaked until the connection is closed. if (IsPrepared) DeallocatePrepared(); } Transaction = null; Connection = null; State = CommandState.Disposed; base.Dispose(disposing); } #endregion #region Misc /// /// Fixes up the text/binary flag on result columns. /// Since Prepare() describes a statement rather than a portal, the resulting RowDescription /// will have text format on all result columns. Fix that up. /// /// /// Note that UnknownResultTypeList only applies to the first query, while AllResultTypesAreUnknown applies /// to all of them. /// internal void FixupRowDescription(RowDescriptionMessage rowDescription, bool isFirst) { for (var i = 0; i < rowDescription.NumFields; i++) rowDescription[i].FormatCode = (UnknownResultTypeList == null || !isFirst ? AllResultTypesAreUnknown : UnknownResultTypeList[i]) ? FormatCode.Text : FormatCode.Binary; } void LogCommand() { if (!Log.IsEnabled(NpgsqlLogLevel.Debug)) return; var sb = new StringBuilder(); sb.Append("Executing statement(s):"); foreach (var s in _statements) sb.AppendLine().Append("\t").Append(s.SQL); if (NpgsqlLogManager.IsParameterLoggingEnabled && Parameters.Any()) { sb.AppendLine().AppendLine("Parameters:"); for (var i = 0; i < Parameters.Count; i++) sb.Append("\t$").Append(i + 1).Append(": ").Append(Convert.ToString(Parameters[i].Value, CultureInfo.InvariantCulture)); } Log.Debug(sb.ToString(), Connection.Connector.Id); } #if NET45 || NET451 /// /// Create a new command based on this one. /// /// A new NpgsqlCommand object. object ICloneable.Clone() { return Clone(); } #endif /// /// Create a new command based on this one. /// /// A new NpgsqlCommand object. [PublicAPI] public NpgsqlCommand Clone() { var clone = new NpgsqlCommand(CommandText, Connection, Transaction) { CommandTimeout = CommandTimeout, CommandType = CommandType, DesignTimeVisible = DesignTimeVisible, _allResultTypesAreUnknown = _allResultTypesAreUnknown, _unknownResultTypeList = _unknownResultTypeList, ObjectResultTypes = ObjectResultTypes }; _parameters.CloneTo(clone._parameters); return clone; } NpgsqlConnector CheckReadyAndGetConnector() { if (State == CommandState.Disposed) throw new ObjectDisposedException(GetType().FullName); if (Connection == null) throw new InvalidOperationException("Connection property has not been initialized."); return Connection.CheckReadyAndGetConnector(); } enum SendState { Start, Parse, Bind, Describe, Execute } #endregion #region Invariants [ContractInvariantMethod] void ObjectInvariants() { Contract.Invariant(!(AllResultTypesAreUnknown && UnknownResultTypeList != null)); Contract.Invariant(Connection != null || !IsPrepared); } #endregion } enum CommandState { Idle, InProgress, Disposed } }
X Tutup