internal PreparedStatement TryGetAutoPrepared(NpgsqlStatement statement) { Debug.Assert(_candidates != null); var sql = statement.SQL; if (!BySql.TryGetValue(sql, out var pStatement)) { // New candidate. Find an empty candidate slot or eject a least-used one. int slotIndex = -1, leastUsages = int.MaxValue; var lastUsed = DateTime.MaxValue; for (var i = 0; i < _candidates.Length; i++) { var candidate = _candidates[i]; // ReSharper disable once ConditionIsAlwaysTrueOrFalse // ReSharper disable HeuristicUnreachableCode if (candidate == null) // Found an unused candidate slot, return immediately { slotIndex = i; break; } // ReSharper restore HeuristicUnreachableCode if (candidate.Usages < leastUsages) { leastUsages = candidate.Usages; slotIndex = i; lastUsed = candidate.LastUsed; } else if (candidate.Usages == leastUsages && candidate.LastUsed < lastUsed) { slotIndex = i; lastUsed = candidate.LastUsed; } } var leastUsed = _candidates[slotIndex]; // ReSharper disable once ConditionIsAlwaysTrueOrFalse if (leastUsed != null) { BySql.Remove(leastUsed.Sql); } pStatement = BySql[sql] = _candidates[slotIndex] = PreparedStatement.CreateAutoPrepareCandidate(this, sql); } switch (pStatement.State) { case PreparedState.Prepared: case PreparedState.ToBePrepared: // The statement has already been prepared (explicitly or automatically), or has been selected // for preparation (earlier identical statement in the same command). // We just need to check that the parameter types correspond, since prepared statements are // only keyed by SQL (to prevent pointless allocations). If we have a mismatch, simply run unprepared. return(pStatement.DoParametersMatch(statement.InputParameters) ? pStatement : null); } if (++pStatement.Usages < UsagesBeforePrepare) { // Statement still hasn't passed the usage threshold, no automatic preparation. // Return null for unprepared exection. pStatement.LastUsed = DateTime.UtcNow; return(null); } // Bingo, we've just passed the usage threshold, statement should get prepared Log.Trace($"Automatically preparing statement: {sql}", _connector.Id); pStatement.State = PreparedState.ToBePrepared; RemoveCandidate(pStatement); if (_numAutoPrepared < MaxAutoPrepared) { // We still have free slots _autoPrepared[_numAutoPrepared++] = pStatement; pStatement.Name = "_auto" + _numAutoPrepared; } else { // We already have the maximum number of prepared statements. // Find the least recently used prepared statement and replace it. var oldestTimestamp = DateTime.MaxValue; var oldestIndex = -1; for (var i = 0; i < _autoPrepared.Length; i++) { if (_autoPrepared[i].LastUsed < oldestTimestamp) { oldestIndex = i; oldestTimestamp = _autoPrepared[i].LastUsed; } } var lru = _autoPrepared[oldestIndex]; pStatement.Name = lru.Name; pStatement.StatementBeingReplaced = lru; _autoPrepared[oldestIndex] = pStatement; } // Note that the parameter types are only set at the moment of preparation - in the candidate phase // there's no differentiation between overloaded statements, which are a pretty rare case, saving // allocations. pStatement.SetParamTypes(statement.InputParameters); return(pStatement); }