#![allow(clippy::cast_precision_loss)]
use serde::{Deserialize, Serialize};
use tracing::debug;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct OperationCostFactors {
#[serde(default = "default_seq_page_cost")]
pub seq_page_cost: f64,
#[serde(default = "default_random_page_cost")]
pub random_page_cost: f64,
#[serde(default = "default_cpu_tuple_cost")]
pub cpu_tuple_cost: f64,
#[serde(default = "default_cpu_index_cost")]
pub cpu_index_cost: f64,
#[serde(default = "default_cpu_distance_cost")]
pub cpu_distance_cost: f64,
#[serde(default = "default_cpu_edge_cost")]
pub cpu_edge_cost: f64,
}
fn default_seq_page_cost() -> f64 {
1.0
}
fn default_random_page_cost() -> f64 {
4.0
}
fn default_cpu_tuple_cost() -> f64 {
0.01
}
fn default_cpu_index_cost() -> f64 {
0.005
}
fn default_cpu_distance_cost() -> f64 {
0.1
}
fn default_cpu_edge_cost() -> f64 {
0.02
}
impl Default for OperationCostFactors {
fn default() -> Self {
Self {
seq_page_cost: default_seq_page_cost(),
random_page_cost: default_random_page_cost(),
cpu_tuple_cost: default_cpu_tuple_cost(),
cpu_index_cost: default_cpu_index_cost(),
cpu_distance_cost: default_cpu_distance_cost(),
cpu_edge_cost: default_cpu_edge_cost(),
}
}
}
pub(crate) struct CostFactorBounds;
impl CostFactorBounds {
pub const SEQ_PAGE_COST: (f64, f64) = (0.01, 10.0);
pub const RANDOM_PAGE_COST: (f64, f64) = (0.1, 20.0);
pub const CPU_TUPLE_COST: (f64, f64) = (0.001, 0.1);
pub const CPU_INDEX_COST: (f64, f64) = (0.001, 0.05);
pub const CPU_DISTANCE_COST: (f64, f64) = (0.01, 1.0);
pub const CPU_EDGE_COST: (f64, f64) = (0.005, 0.2);
}
pub(crate) fn clamp_with_log(name: &str, value: f64, bounds: (f64, f64)) -> f64 {
let clamped = value.clamp(bounds.0, bounds.1);
if (clamped - value).abs() > f64::EPSILON {
debug!(
field = name,
original = value,
clamped = clamped,
"cost factor clamped to bounds"
);
}
clamped
}
impl OperationCostFactors {
#[must_use]
pub fn ssd_optimized() -> Self {
Self {
random_page_cost: 1.5,
..Default::default()
}
}
#[must_use]
pub fn in_memory() -> Self {
Self {
seq_page_cost: 0.1,
random_page_cost: 0.1,
..Default::default()
}
}
#[must_use]
pub fn hdd_optimized() -> Self {
Self {
random_page_cost: 8.0,
..Default::default()
}
}
#[must_use]
pub fn clamped(self) -> Self {
Self {
seq_page_cost: clamp_with_log(
"seq_page_cost",
self.seq_page_cost,
CostFactorBounds::SEQ_PAGE_COST,
),
random_page_cost: clamp_with_log(
"random_page_cost",
self.random_page_cost,
CostFactorBounds::RANDOM_PAGE_COST,
),
cpu_tuple_cost: clamp_with_log(
"cpu_tuple_cost",
self.cpu_tuple_cost,
CostFactorBounds::CPU_TUPLE_COST,
),
cpu_index_cost: clamp_with_log(
"cpu_index_cost",
self.cpu_index_cost,
CostFactorBounds::CPU_INDEX_COST,
),
cpu_distance_cost: clamp_with_log(
"cpu_distance_cost",
self.cpu_distance_cost,
CostFactorBounds::CPU_DISTANCE_COST,
),
cpu_edge_cost: clamp_with_log(
"cpu_edge_cost",
self.cpu_edge_cost,
CostFactorBounds::CPU_EDGE_COST,
),
}
}
#[must_use]
pub fn is_default(&self) -> bool {
*self == Self::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ssd_optimized_factors() {
let factors = OperationCostFactors::ssd_optimized();
assert!(factors.random_page_cost < OperationCostFactors::default().random_page_cost);
}
#[test]
fn test_hdd_optimized_factors() {
let factors = OperationCostFactors::hdd_optimized();
assert!(factors.random_page_cost > OperationCostFactors::default().random_page_cost);
}
#[test]
fn test_in_memory_factors() {
let factors = OperationCostFactors::in_memory();
assert!(factors.seq_page_cost < OperationCostFactors::default().seq_page_cost);
assert!(factors.random_page_cost < OperationCostFactors::default().random_page_cost);
}
#[test]
fn test_is_default() {
assert!(OperationCostFactors::default().is_default());
assert!(!OperationCostFactors::ssd_optimized().is_default());
}
#[test]
fn test_clamped_within_bounds() {
let extreme = OperationCostFactors {
seq_page_cost: 999.0,
random_page_cost: -1.0,
cpu_tuple_cost: 0.0,
cpu_index_cost: 100.0,
cpu_distance_cost: 0.0,
cpu_edge_cost: 0.0,
};
let clamped = extreme.clamped();
assert!((clamped.seq_page_cost - CostFactorBounds::SEQ_PAGE_COST.1).abs() < f64::EPSILON);
assert!(
(clamped.random_page_cost - CostFactorBounds::RANDOM_PAGE_COST.0).abs() < f64::EPSILON
);
assert!((clamped.cpu_tuple_cost - CostFactorBounds::CPU_TUPLE_COST.0).abs() < f64::EPSILON);
assert!((clamped.cpu_index_cost - CostFactorBounds::CPU_INDEX_COST.1).abs() < f64::EPSILON);
}
#[test]
fn test_clamp_with_log_no_change() {
let result = clamp_with_log("test", 5.0, (1.0, 10.0));
assert!((result - 5.0).abs() < f64::EPSILON);
}
#[test]
fn test_clamp_with_log_clamps_low() {
let result = clamp_with_log("test", -1.0, (0.0, 10.0));
assert!(result.abs() < f64::EPSILON);
}
#[test]
fn test_clamp_with_log_clamps_high() {
let result = clamp_with_log("test", 99.0, (0.0, 10.0));
assert!((result - 10.0).abs() < f64::EPSILON);
}
}