using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Npgsql.BackendMessages; using Npgsql.Internal; namespace Npgsql; /// public sealed class NpgsqlBatchCommand : DbBatchCommand { string _commandText; /// [AllowNull] public override string CommandText { get => _commandText; set { _commandText = value ?? string.Empty; ResetPreparation(); // TODO: Technically should do this also if the parameter list (or type) changes } } /// public override CommandType CommandType { get; set; } = CommandType.Text; /// protected override DbParameterCollection DbParameterCollection => Parameters; /// public new NpgsqlParameterCollection Parameters { get; } = new(); /// /// The number of rows affected or retrieved. /// /// /// See the command tag in the CommandComplete message for the meaning of this value for each , /// https://www.postgresql.org/docs/current/static/protocol-message-formats.html /// public ulong Rows { get; internal set; } /// public override int RecordsAffected { get { switch (StatementType) { case StatementType.Update: case StatementType.Insert: case StatementType.Delete: case StatementType.Copy: case StatementType.Move: return Rows > int.MaxValue ? throw new OverflowException($"The number of records affected exceeds int.MaxValue. Use {nameof(Rows)}.") : (int)Rows; default: return -1; } } } /// /// Specifies the type of query, e.g. SELECT. /// public StatementType StatementType { get; internal set; } /// /// For an INSERT, the object ID of the inserted row if is 1 and /// the target table has OIDs; otherwise 0. /// public uint OID { get; internal set; } /// /// The SQL as it will be sent to PostgreSQL, after any rewriting performed by Npgsql (e.g. named to positional parameter /// placeholders). /// internal string? FinalCommandText { get; set; } /// /// The list of parameters, ordered positionally, as it will be sent to PostgreSQL. /// /// /// If the user provided positional parameters, this references the (in batching mode) or the list /// backing (in non-batching) mode. If the user provided named parameters, this is a /// separate list containing the re-ordered parameters. /// internal List PositionalParameters { get => _inputParameters ??= _ownedInputParameters ??= new(); set => _inputParameters = value; } List? _ownedInputParameters; List? _inputParameters; /// /// The RowDescription message for this query. If null, the query does not return rows (e.g. INSERT) /// internal RowDescriptionMessage? Description { get => PreparedStatement == null ? _description : PreparedStatement.Description; set { if (PreparedStatement == null) _description = value; else PreparedStatement.Description = value; } } RowDescriptionMessage? _description; /// /// If this statement has been automatically prepared, references the . /// Null otherwise. /// internal PreparedStatement? PreparedStatement { get => _preparedStatement != null && _preparedStatement.State == PreparedState.Unprepared ? _preparedStatement = null : _preparedStatement; set => _preparedStatement = value; } PreparedStatement? _preparedStatement; internal NpgsqlConnector? ConnectorPreparedOn { get; set; } internal bool IsPreparing; /// /// Holds the server-side (prepared) statement name. Empty string for non-prepared statements. /// internal string StatementName => PreparedStatement?.Name ?? ""; /// /// Whether this statement has already been prepared (including automatic preparation). /// internal bool IsPrepared => PreparedStatement?.IsPrepared == true; /// /// Returns a prepared statement for this statement (including automatic preparation). /// internal bool TryGetPrepared([NotNullWhen(true)] out PreparedStatement? preparedStatement) { preparedStatement = PreparedStatement; return preparedStatement?.IsPrepared == true; } /// /// Initializes a new . /// public NpgsqlBatchCommand() : this(string.Empty) {} /// /// Initializes a new . /// /// The text of the . public NpgsqlBatchCommand(string commandText) => _commandText = commandText; internal bool ExplicitPrepare(NpgsqlConnector connector) { if (!IsPrepared) { PreparedStatement = connector.PreparedStatementManager.GetOrAddExplicit(this); if (PreparedStatement?.State == PreparedState.NotPrepared) { PreparedStatement.State = PreparedState.BeingPrepared; IsPreparing = true; return true; } } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool TryAutoPrepare(NpgsqlConnector connector) { // If this statement isn't prepared, see if it gets implicitly prepared. // Note that this may return null (not enough usages for automatic preparation). if (!TryGetPrepared(out var preparedStatement)) preparedStatement = PreparedStatement = connector.PreparedStatementManager.TryGetAutoPrepared(this); if (preparedStatement is not null) { if (preparedStatement.State == PreparedState.NotPrepared) { preparedStatement.State = PreparedState.BeingPrepared; IsPreparing = true; } return true; } return false; } internal void Reset() { CommandText = string.Empty; StatementType = StatementType.Select; _description = null; Rows = 0; OID = 0; PreparedStatement = null; if (ReferenceEquals(_inputParameters, _ownedInputParameters)) PositionalParameters.Clear(); else if (_inputParameters is not null) _inputParameters = null; // We're pointing at a user's NpgsqlParameterCollection Debug.Assert(_inputParameters is null || _inputParameters.Count == 0); Debug.Assert(_ownedInputParameters is null || _ownedInputParameters.Count == 0); } internal void ApplyCommandComplete(CommandCompleteMessage msg) { StatementType = msg.StatementType; Rows = msg.Rows; OID = msg.OID; } internal void ResetPreparation() { PreparedStatement = null; ConnectorPreparedOn = null; } /// /// Returns the . /// public override string ToString() => CommandText; }