use crate::optimizer::stats::feedback::query::QueryExecutionFeedback;
use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
#[derive(Debug)]
pub struct QueryFeedbackHistory {
feedbacks: RwLock<HashMap<String, Vec<QueryExecutionFeedback>>>,
next_sequence: AtomicU64,
max_history_per_query: usize,
}
impl QueryFeedbackHistory {
pub fn new(max_history_per_query: usize) -> Self {
Self {
feedbacks: RwLock::new(HashMap::new()),
next_sequence: AtomicU64::new(1),
max_history_per_query: max_history_per_query.max(1),
}
}
pub fn add_feedback(&self, mut feedback: QueryExecutionFeedback) {
feedback.sequence = self.next_sequence.fetch_add(1, Ordering::Relaxed);
let mut feedbacks = self.feedbacks.write();
let entry = feedbacks
.entry(feedback.query_fingerprint.clone())
.or_default();
entry.push(feedback);
if entry.len() > self.max_history_per_query {
entry.remove(0);
}
}
pub fn get_feedback_for_query(&self, fingerprint: &str) -> Vec<QueryExecutionFeedback> {
self.feedbacks
.read()
.get(fingerprint)
.cloned()
.unwrap_or_default()
}
pub fn get_feedback_count(&self, fingerprint: &str) -> usize {
self.feedbacks
.read()
.get(fingerprint)
.map(|v| v.len())
.unwrap_or(0)
}
pub fn get_all_fingerprints(&self) -> Vec<String> {
self.feedbacks.read().keys().cloned().collect()
}
pub fn clear_query_history(&self, fingerprint: &str) -> bool {
self.feedbacks.write().remove(fingerprint).is_some()
}
pub fn clear_all(&self) {
self.feedbacks.write().clear();
}
pub fn total_feedback_count(&self) -> usize {
self.feedbacks.read().values().map(|v| v.len()).sum()
}
pub fn query_count(&self) -> usize {
self.feedbacks.read().len()
}
pub fn get_recent_feedbacks(&self, n: usize) -> Vec<QueryExecutionFeedback> {
let feedbacks = self.feedbacks.read();
let mut all_feedbacks: Vec<_> =
feedbacks.values().flat_map(|v| v.iter().cloned()).collect();
all_feedbacks.sort_by(|a, b| {
b.execution_timestamp
.elapsed()
.cmp(&a.execution_timestamp.elapsed())
});
all_feedbacks.into_iter().take(n).collect()
}
pub fn get_avg_row_error(&self, fingerprint: &str) -> Option<f64> {
let feedbacks = self.feedbacks.read();
let query_feedbacks = feedbacks.get(fingerprint)?;
if query_feedbacks.is_empty() {
return None;
}
let total_error: f64 = query_feedbacks
.iter()
.map(|f| f.row_estimation_error())
.sum();
Some(total_error / query_feedbacks.len() as f64)
}
pub fn get_avg_time_error(&self, fingerprint: &str) -> Option<f64> {
let feedbacks = self.feedbacks.read();
let query_feedbacks = feedbacks.get(fingerprint)?;
if query_feedbacks.is_empty() {
return None;
}
let total_error: f64 = query_feedbacks
.iter()
.map(|f| f.time_estimation_error())
.sum();
Some(total_error / query_feedbacks.len() as f64)
}
pub fn cleanup_old_feedbacks(&self, max_age: std::time::Duration) {
let mut feedbacks = self.feedbacks.write();
let now = Instant::now();
for query_feedbacks in feedbacks.values_mut() {
query_feedbacks.retain(|f| now.duration_since(f.execution_timestamp) < max_age);
}
feedbacks.retain(|_, v| !v.is_empty());
}
pub fn set_max_history(&self, max_history: usize) {
let max_history = max_history.max(1);
let mut feedbacks = self.feedbacks.write();
for query_feedbacks in feedbacks.values_mut() {
while query_feedbacks.len() > max_history {
query_feedbacks.remove(0);
}
}
}
}
impl Default for QueryFeedbackHistory {
fn default() -> Self {
Self::new(100)
}
}
impl Clone for QueryFeedbackHistory {
fn clone(&self) -> Self {
Self {
feedbacks: RwLock::new(self.feedbacks.read().clone()),
next_sequence: AtomicU64::new(self.next_sequence.load(Ordering::Relaxed)),
max_history_per_query: self.max_history_per_query,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::optimizer::stats::feedback::query::QueryExecutionFeedback;
#[test]
fn test_query_feedback_history() {
let history = QueryFeedbackHistory::new(10);
let feedback1 = QueryExecutionFeedback::new("fp_123".to_string());
history.add_feedback(feedback1);
let feedbacks = history.get_feedback_for_query("fp_123");
assert_eq!(feedbacks.len(), 1);
let feedback2 = QueryExecutionFeedback::new("fp_123".to_string());
history.add_feedback(feedback2);
assert_eq!(history.get_feedback_count("fp_123"), 2);
assert_eq!(history.total_feedback_count(), 2);
assert_eq!(history.query_count(), 1);
}
#[test]
fn test_history_limit() {
let history = QueryFeedbackHistory::new(3);
for i in 0..4 {
let mut feedback = QueryExecutionFeedback::new("fp_123".to_string());
feedback.actual_rows = i as u64 * 100;
history.add_feedback(feedback);
}
assert_eq!(history.get_feedback_count("fp_123"), 3);
}
#[test]
fn test_clear_history() {
let history = QueryFeedbackHistory::new(10);
let feedback = QueryExecutionFeedback::new("fp_123".to_string());
history.add_feedback(feedback);
assert!(history.clear_query_history("fp_123"));
assert_eq!(history.get_feedback_count("fp_123"), 0);
assert!(!history.clear_query_history("nonexistent"));
}
#[test]
fn test_multiple_queries() {
let history = QueryFeedbackHistory::new(10);
history.add_feedback(QueryExecutionFeedback::new("fp_1".to_string()));
history.add_feedback(QueryExecutionFeedback::new("fp_2".to_string()));
history.add_feedback(QueryExecutionFeedback::new("fp_1".to_string()));
assert_eq!(history.query_count(), 2);
assert_eq!(history.total_feedback_count(), 3);
let fingerprints = history.get_all_fingerprints();
assert_eq!(fingerprints.len(), 2);
}
#[test]
fn test_avg_errors() {
let history = QueryFeedbackHistory::new(10);
let mut feedback1 = QueryExecutionFeedback::new("fp_123".to_string());
feedback1.estimated_rows = 100;
feedback1.actual_rows = 110; history.add_feedback(feedback1);
let mut feedback2 = QueryExecutionFeedback::new("fp_123".to_string());
feedback2.estimated_rows = 100;
feedback2.actual_rows = 90; history.add_feedback(feedback2);
let avg_error = history
.get_avg_row_error("fp_123")
.expect("get_avg_row_error should succeed");
assert!((avg_error - 0.1).abs() < 0.01); }
#[test]
fn test_nonexistent_query() {
let history = QueryFeedbackHistory::new(10);
assert_eq!(history.get_feedback_count("nonexistent"), 0);
assert!(history.get_feedback_for_query("nonexistent").is_empty());
assert!(history.get_avg_row_error("nonexistent").is_none());
}
#[test]
fn test_insert_assigns_monotonic_sequences() {
let history = QueryFeedbackHistory::new(10);
history.add_feedback(QueryExecutionFeedback::new("fp_1".to_string()));
history.add_feedback(QueryExecutionFeedback::new("fp_1".to_string()));
let feedbacks = history.get_feedback_for_query("fp_1");
assert_eq!(feedbacks.len(), 2);
assert!(feedbacks[0].sequence > 0);
assert_eq!(feedbacks[1].sequence, feedbacks[0].sequence + 1);
}
}