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 }