Exemplo n.º 1
0
        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);
        }