using System;
using System.Collections.Generic;
using System.Diagnostics;
using Npgsql.BackendMessages;
namespace Npgsql;
///
/// Internally represents a statement has been prepared, is in the process of being prepared, or is a
/// candidate for preparation (i.e. awaiting further usages).
///
[DebuggerDisplay("{Name} ({State}): {Sql}")]
class PreparedStatement
{
readonly PreparedStatementManager _manager;
internal string Sql { get; }
internal string? Name;
internal RowDescriptionMessage? Description;
internal int Usages;
internal PreparedState State { get; set; }
internal bool IsPrepared => State == PreparedState.Prepared;
///
/// If true, the user explicitly requested this statement be prepared. It does not get closed as part of
/// the automatic preparation LRU mechanism.
///
internal bool IsExplicit { get; }
///
/// If this statement is about to be prepared, but replaces a previous statement which needs to be closed,
/// this holds the name of the previous statement. Otherwise null.
///
internal PreparedStatement? StatementBeingReplaced;
internal int AutoPreparedSlotIndex { get; set; }
internal DateTime LastUsed { get; set; }
///
/// Contains the handler types for a prepared statement's parameters, for overloaded cases (same SQL, different param types)
/// Only populated after the statement has been prepared (i.e. null for candidates).
///
internal Type[]? HandlerParamTypes { get; private set; }
static readonly Type[] EmptyParamTypes = Type.EmptyTypes;
internal static PreparedStatement CreateExplicit(
PreparedStatementManager manager,
string sql,
string name,
List parameters,
PreparedStatement? statementBeingReplaced)
{
var pStatement = new PreparedStatement(manager, sql, true)
{
Name = name,
StatementBeingReplaced = statementBeingReplaced
};
pStatement.SetParamTypes(parameters);
return pStatement;
}
internal static PreparedStatement CreateAutoPrepareCandidate(PreparedStatementManager manager, string sql)
=> new(manager, sql, false);
PreparedStatement(PreparedStatementManager manager, string sql, bool isExplicit)
{
_manager = manager;
Sql = sql;
IsExplicit = isExplicit;
State = PreparedState.NotPrepared;
}
internal void SetParamTypes(List parameters)
{
Debug.Assert(HandlerParamTypes == null);
if (parameters.Count == 0)
{
HandlerParamTypes = EmptyParamTypes;
return;
}
HandlerParamTypes = new Type[parameters.Count];
for (var i = 0; i < parameters.Count; i++)
HandlerParamTypes[i] = parameters[i].Handler!.GetType();
}
internal bool DoParametersMatch(List parameters)
{
if (HandlerParamTypes!.Length != parameters.Count)
return false;
for (var i = 0; i < HandlerParamTypes.Length; i++)
if (HandlerParamTypes[i] != parameters[i].Handler!.GetType())
return false;
return true;
}
internal void AbortPrepare()
{
Debug.Assert(State == PreparedState.BeingPrepared);
// We were planned for preparation, but a failure occurred and we did not carry that out.
// Remove it from the BySql dictionary, and place back the statement we were planned to replace (possibly null), setting
// its state back to prepared.
_manager.BySql.Remove(Sql);
if (!IsExplicit)
{
_manager.AutoPrepared[AutoPreparedSlotIndex] = StatementBeingReplaced;
if (StatementBeingReplaced is not null)
StatementBeingReplaced.State = PreparedState.Prepared;
}
State = PreparedState.Unprepared;
}
internal void CompleteUnprepare()
{
_manager.BySql.Remove(Sql);
_manager.NumPrepared--;
State = PreparedState.Unprepared;
}
public override string ToString() => Sql;
}
///
/// The state of a .
///
enum PreparedState
{
///
/// The statement hasn't been prepared yet, nor is it in the process of being prepared.
/// This is the value for autoprepare candidates which haven't been prepared yet, and is also
/// a temporary state during preparation.
///
NotPrepared,
///
/// The statement is in the process of being prepared.
///
BeingPrepared,
///
/// The statement has been fully prepared and can be executed.
///
Prepared,
///
/// The statement is in the process of being unprepared. This is a temporary state that only occurs during
/// unprepare. Specifically, it means that a Close message for the statement has already been written
/// to the write buffer.
///
BeingUnprepared,
///
/// The statement has been unprepared and is no longer usable.
///
Unprepared
}