void MoveToNextStatement() { _statementIndex++; if (_statements.Count > _statementIndex) { _statement = _statements[_statementIndex]; _statement.Reset(); } else { _statement = new NpgsqlStatement(); _statements.Add(_statement); } _paramIndexMap.Clear(); _rewrittenSql.Clear(); }
internal PreparedStatement GetOrAddExplicit(NpgsqlStatement statement) { var sql = statement.SQL; PreparedStatement statementBeingReplaced = null; if (BySql.TryGetValue(sql, out var pStatement)) { Debug.Assert(pStatement.State != PreparedState.Unprepared); if (pStatement.IsExplicit) { // Great, we've found an explicit prepared statement. // 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); } // We've found an autoprepare statement (candidate or otherwise) switch (pStatement.State) { case PreparedState.NotPrepared: // Found a candidate for autopreparation. Remove it and prepare explicitly. RemoveCandidate(pStatement); break; case PreparedState.Prepared: // The statement has already been autoprepared. We need to "promote" it to explicit. statementBeingReplaced = pStatement; break; case PreparedState.BeingPrepared: throw new InvalidOperationException($"Found autoprepare statement in state {nameof(PreparedState.BeingPrepared)}"); case PreparedState.Unprepared: throw new InvalidOperationException($"Found unprepared statement in {nameof(PreparedStatementManager)}"); default: throw new ArgumentOutOfRangeException(); } } // Statement hasn't been prepared yet return(BySql[sql] = PreparedStatement.CreateExplicit(this, sql, NextPreparedStatementName(), statement.InputParameters, statementBeingReplaced)); }
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); }