use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::RwLock;
use super::factor::FeedbackDrivenFactor;
pub fn format_shape_key(space: Option<&str>, kind: &str, discriminator: Option<&str>) -> String {
let prefix = space.unwrap_or("").to_string();
let mut key = format!("{prefix}:{kind}");
if let Some(discriminator) = discriminator {
if !discriminator.is_empty() {
key.push(':');
key.push_str(discriminator);
}
}
key
}
#[derive(Debug, Default)]
pub struct CardinalityFeedbackManager {
feedbacks: RwLock<HashMap<String, FeedbackDrivenFactor>>,
default_alpha: f64,
default_min_correction: f64,
default_max_correction: f64,
}
impl CardinalityFeedbackManager {
pub fn new() -> Self {
Self::with_params(0.3, 0.1, 10.0)
}
pub fn with_params(alpha: f64, min_correction: f64, max_correction: f64) -> Self {
Self {
feedbacks: RwLock::new(HashMap::new()),
default_alpha: alpha,
default_min_correction: min_correction,
default_max_correction: max_correction,
}
}
pub fn register_key(&self, key: String, estimated_rows: f64) {
let feedback = FeedbackDrivenFactor::with_params(
estimated_rows,
self.default_alpha,
self.default_min_correction,
self.default_max_correction,
);
self.feedbacks.write().entry(key).or_insert(feedback);
}
pub fn refresh_estimated(&self, key: &str, estimated_rows: f64) -> bool {
let mut feedbacks = self.feedbacks.write();
if let Some(feedback) = feedbacks.get_mut(key) {
feedback.set_estimated(estimated_rows);
true
} else {
false
}
}
pub fn corrected_rows(&self, key: &str) -> Option<f64> {
self.feedbacks
.read()
.get(key)
.map(|f| f.corrected())
.filter(|v| *v > 0.0)
}
pub fn correction_factor(&self, key: &str) -> Option<f64> {
self.feedbacks
.read()
.get(key)
.map(|f| f.correction_factor())
}
pub fn update_feedback_ratio(&self, key: &str, ratio: f64) -> bool {
let mut feedbacks = self.feedbacks.write();
if let Some(feedback) = feedbacks.get_mut(key) {
feedback.update_with_ratio(ratio);
true
} else {
false
}
}
pub fn remove_feedback_by_space(&self, space_prefix: &str) -> usize {
let mut feedbacks = self.feedbacks.write();
let mut removed = 0;
feedbacks.retain(|key, _| {
let keep = !key.starts_with(space_prefix);
if !keep {
removed += 1;
}
keep
});
removed
}
pub fn remove_feedback(&self, key: &str) -> Option<FeedbackDrivenFactor> {
self.feedbacks.write().remove(key)
}
pub fn get_all_keys(&self) -> Vec<String> {
self.feedbacks.read().keys().cloned().collect()
}
pub fn feedback_count(&self) -> usize {
self.feedbacks.read().len()
}
pub fn clear_all(&self) {
self.feedbacks.write().clear();
}
pub fn get_feedback(&self, key: &str) -> Option<FeedbackDrivenFactor> {
self.feedbacks.read().get(key).cloned()
}
}
impl Clone for CardinalityFeedbackManager {
fn clone(&self) -> Self {
Self {
feedbacks: RwLock::new(self.feedbacks.read().clone()),
default_alpha: self.default_alpha,
default_min_correction: self.default_min_correction,
default_max_correction: self.default_max_correction,
}
}
}
pub type SharedCardinalityFeedback = Arc<CardinalityFeedbackManager>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_register_and_correct() {
let manager = CardinalityFeedbackManager::new();
manager.register_key("s:ScanVertices:Person".to_string(), 100.0);
assert_eq!(manager.corrected_rows("s:ScanVertices:Person"), Some(100.0));
for _ in 0..50 {
manager.update_feedback_ratio("s:ScanVertices:Person", 3.0);
}
let corrected = manager.corrected_rows("s:ScanVertices:Person").unwrap();
assert!(corrected > 200.0 && corrected <= 1000.0);
}
#[test]
fn test_register_is_idempotent() {
let manager = CardinalityFeedbackManager::new();
manager.register_key("k".into(), 10.0);
manager.register_key("k".into(), 1000.0);
assert_eq!(manager.corrected_rows("k"), Some(10.0));
}
#[test]
fn test_unknown_key_updates_return_false() {
let manager = CardinalityFeedbackManager::new();
assert!(!manager.update_feedback_ratio("missing", 2.0));
}
#[test]
fn test_remove_by_space() {
let manager = CardinalityFeedbackManager::new();
manager.register_key("a:ScanVertices".to_string(), 10.0);
manager.register_key("b:ScanVertices".to_string(), 10.0);
assert_eq!(manager.remove_feedback_by_space("a:"), 1);
assert_eq!(manager.feedback_count(), 1);
}
}