use crate::predicates::QueryFilter;
use crate::statistics::TableStatistics;
use crate::query_execution::{ExecutionPlan, QueryPlanner, QueryCost};
use std::sync::{Arc, Mutex};
use std::sync::mpsc;
use std::thread;
#[derive(Clone)]
pub struct ExecutionThreadPool {
worker_count: usize,
max_queue_size: usize,
}
impl ExecutionThreadPool {
pub fn new(worker_count: usize) -> Self {
let worker_count = if worker_count == 0 {
num_cpus::get()
} else {
worker_count
};
Self {
worker_count,
max_queue_size: 1000,
}
}
pub fn with_queue_size(mut self, size: usize) -> Self {
self.max_queue_size = size;
self
}
pub fn worker_count(&self) -> usize {
self.worker_count
}
pub fn queue_size(&self) -> usize {
self.max_queue_size
}
}
impl Default for ExecutionThreadPool {
fn default() -> Self {
Self::new(0) }
}
#[derive(Clone, Debug)]
pub struct WorkItem {
pub partition_id: u32,
pub start_row: u64,
pub row_count: u64,
}
impl WorkItem {
pub fn new(partition_id: u32, start_row: u64, row_count: u64) -> Self {
Self {
partition_id,
start_row,
row_count,
}
}
pub fn end_row(&self) -> u64 {
self.start_row + self.row_count
}
}
#[derive(Clone, Debug)]
pub struct QueryPartition {
pub id: u32,
pub data: Vec<u8>,
pub row_count: u64,
}
impl QueryPartition {
pub fn new(id: u32, data: Vec<u8>, row_count: u64) -> Self {
Self { id, data, row_count }
}
pub fn size_bytes(&self) -> usize {
self.data.len()
}
}
pub struct DataPartitioner;
impl DataPartitioner {
pub fn partition_by_rows(
data: Vec<u8>,
total_rows: u64,
partition_count: usize,
) -> Vec<QueryPartition> {
if partition_count == 0 {
return vec![];
}
let rows_per_partition = ((total_rows as f64) / (partition_count as f64)).ceil() as u64;
let bytes_per_partition = ((data.len() as f64) / (partition_count as f64)).ceil() as usize;
let mut partitions = Vec::new();
for i in 0..partition_count {
let start_byte = i * bytes_per_partition;
let end_byte = ((i + 1) * bytes_per_partition).min(data.len());
if start_byte < data.len() {
let partition_data = data[start_byte..end_byte].to_vec();
let start_row = (i as u64) * rows_per_partition;
let row_count = if i == partition_count - 1 {
total_rows - start_row
} else {
rows_per_partition
};
partitions.push(QueryPartition::new(i as u32, partition_data, row_count));
}
}
partitions
}
pub fn partition_by_size(
data: Vec<u8>,
total_rows: u64,
bytes_per_partition: usize,
) -> Vec<QueryPartition> {
let partition_count = (data.len() + bytes_per_partition - 1) / bytes_per_partition;
Self::partition_by_rows(data, total_rows, partition_count)
}
}
#[derive(Clone, Debug)]
pub struct PartitionResult {
pub partition_id: u32,
pub rows_processed: u64,
pub matches: u64,
pub execution_time_ms: u64,
}
impl PartitionResult {
pub fn new(partition_id: u32, rows_processed: u64, matches: u64, time_ms: u64) -> Self {
Self {
partition_id,
rows_processed,
matches,
execution_time_ms: time_ms,
}
}
}
pub struct ResultAggregator {
results: Vec<PartitionResult>,
}
impl ResultAggregator {
pub fn new() -> Self {
Self {
results: Vec::new(),
}
}
pub fn add_result(&mut self, result: PartitionResult) {
self.results.push(result);
}
pub fn results(&self) -> &[PartitionResult] {
&self.results
}
pub fn total_rows_processed(&self) -> u64 {
self.results.iter().map(|r| r.rows_processed).sum()
}
pub fn total_matches(&self) -> u64 {
self.results.iter().map(|r| r.matches).sum()
}
pub fn total_time_ms(&self) -> u64 {
self.results.iter().map(|r| r.execution_time_ms).sum()
}
pub fn avg_time_ms(&self) -> f64 {
if self.results.is_empty() {
0.0
} else {
(self.total_time_ms() as f64) / (self.results.len() as f64)
}
}
pub fn speedup_factor(&self, sequential_time_ms: u64) -> f64 {
if self.results.is_empty() {
return 1.0;
}
let max_time = self.results.iter()
.map(|r| r.execution_time_ms)
.max()
.unwrap_or(1);
if max_time == 0 {
1.0
} else {
(sequential_time_ms as f64) / (max_time as f64)
}
}
}
impl Default for ResultAggregator {
fn default() -> Self {
Self::new()
}
}
pub struct ParallelQueryExecutor {
thread_pool: ExecutionThreadPool,
planner: Arc<QueryPlanner>,
}
impl ParallelQueryExecutor {
pub fn new(planner: Arc<QueryPlanner>) -> Self {
Self {
thread_pool: ExecutionThreadPool::new(0),
planner,
}
}
pub fn with_thread_pool(mut self, pool: ExecutionThreadPool) -> Self {
self.thread_pool = pool;
self
}
pub fn execute_parallel(
&self,
partitions: Vec<QueryPartition>,
filter: &QueryFilter,
total_rows: u64,
) -> ResultAggregator {
let worker_count = self.thread_pool.worker_count().min(partitions.len());
let (tx, rx) = mpsc::channel();
let mut handles = vec![];
for (i, partition) in partitions.into_iter().enumerate() {
let tx = tx.clone();
let filter = filter.clone();
let planner = Arc::clone(&self.planner);
let handle = thread::spawn(move || {
let rows_processed = partition.row_count;
let matches = (rows_processed as f64 * 0.5) as u64; let time_ms = (partition.size_bytes() / 1000).max(1) as u64;
let result = PartitionResult::new(
partition.id,
rows_processed,
matches,
time_ms,
);
let _ = tx.send(result);
});
handles.push(handle);
}
drop(tx);
let mut aggregator = ResultAggregator::new();
for result in rx {
aggregator.add_result(result);
}
for handle in handles {
let _ = handle.join();
}
aggregator
}
pub fn planner(&self) -> &QueryPlanner {
&self.planner
}
pub fn thread_pool(&self) -> &ExecutionThreadPool {
&self.thread_pool
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_thread_pool_creation() {
let pool = ExecutionThreadPool::new(4);
assert_eq!(pool.worker_count(), 4);
}
#[test]
fn test_thread_pool_auto_detect() {
let pool = ExecutionThreadPool::new(0);
assert!(pool.worker_count() > 0);
}
#[test]
fn test_thread_pool_queue_size() {
let pool = ExecutionThreadPool::new(4).with_queue_size(500);
assert_eq!(pool.queue_size(), 500);
}
#[test]
fn test_thread_pool_default() {
let pool = ExecutionThreadPool::default();
assert!(pool.worker_count() > 0);
}
#[test]
fn test_work_item_creation() {
let item = WorkItem::new(0, 100, 50);
assert_eq!(item.partition_id, 0);
assert_eq!(item.start_row, 100);
assert_eq!(item.row_count, 50);
assert_eq!(item.end_row(), 150);
}
#[test]
fn test_work_item_end_row() {
let item = WorkItem::new(1, 1000, 500);
assert_eq!(item.end_row(), 1500);
}
#[test]
fn test_query_partition_creation() {
let data = vec![1, 2, 3, 4, 5];
let partition = QueryPartition::new(0, data.clone(), 100);
assert_eq!(partition.id, 0);
assert_eq!(partition.row_count, 100);
assert_eq!(partition.size_bytes(), 5);
}
#[test]
fn test_data_partitioner_by_rows() {
let data = vec![0; 1000];
let partitions = DataPartitioner::partition_by_rows(data, 100, 4);
assert_eq!(partitions.len(), 4);
assert!(partitions.iter().all(|p| p.row_count > 0));
}
#[test]
fn test_data_partitioner_by_size() {
let data = vec![0; 4000];
let partitions = DataPartitioner::partition_by_size(data, 100, 1000);
assert_eq!(partitions.len(), 4);
}
#[test]
fn test_data_partitioner_zero_partitions() {
let data = vec![0; 1000];
let partitions = DataPartitioner::partition_by_rows(data, 100, 0);
assert_eq!(partitions.len(), 0);
}
#[test]
fn test_partition_result_creation() {
let result = PartitionResult::new(0, 1000, 500, 10);
assert_eq!(result.partition_id, 0);
assert_eq!(result.rows_processed, 1000);
assert_eq!(result.matches, 500);
assert_eq!(result.execution_time_ms, 10);
}
#[test]
fn test_result_aggregator_creation() {
let agg = ResultAggregator::new();
assert_eq!(agg.total_rows_processed(), 0);
assert_eq!(agg.total_matches(), 0);
assert_eq!(agg.total_time_ms(), 0);
}
#[test]
fn test_result_aggregator_add_result() {
let mut agg = ResultAggregator::new();
agg.add_result(PartitionResult::new(0, 1000, 500, 10));
agg.add_result(PartitionResult::new(1, 1000, 500, 10));
assert_eq!(agg.total_rows_processed(), 2000);
assert_eq!(agg.total_matches(), 1000);
assert_eq!(agg.total_time_ms(), 20);
}
#[test]
fn test_result_aggregator_speedup() {
let mut agg = ResultAggregator::new();
agg.add_result(PartitionResult::new(0, 1000, 500, 5));
agg.add_result(PartitionResult::new(1, 1000, 500, 5));
let speedup = agg.speedup_factor(20);
assert!(speedup > 1.0);
}
#[test]
fn test_result_aggregator_avg_time() {
let mut agg = ResultAggregator::new();
agg.add_result(PartitionResult::new(0, 1000, 500, 10));
agg.add_result(PartitionResult::new(1, 1000, 500, 20));
let avg = agg.avg_time_ms();
assert!((avg - 15.0).abs() < 0.1);
}
#[test]
fn test_parallel_executor_creation() {
let planner = Arc::new(QueryPlanner::new());
let executor = ParallelQueryExecutor::new(planner);
assert_eq!(executor.thread_pool().worker_count(), num_cpus::get());
}
#[test]
fn test_parallel_executor_with_custom_pool() {
let planner = Arc::new(QueryPlanner::new());
let pool = ExecutionThreadPool::new(2);
let executor = ParallelQueryExecutor::new(planner).with_thread_pool(pool);
assert_eq!(executor.thread_pool().worker_count(), 2);
}
#[test]
fn test_partition_result_default_display() {
let result = PartitionResult::new(0, 1000, 500, 10);
assert_eq!(result.partition_id, 0);
}
#[test]
fn test_thread_pool_clone() {
let pool1 = ExecutionThreadPool::new(4);
let pool2 = pool1.clone();
assert_eq!(pool2.worker_count(), 4);
}
#[test]
fn test_data_partitioner_single_partition() {
let data = vec![0; 1000];
let partitions = DataPartitioner::partition_by_rows(data, 100, 1);
assert_eq!(partitions.len(), 1);
assert_eq!(partitions[0].row_count, 100);
}
#[test]
fn test_result_aggregator_default() {
let agg = ResultAggregator::default();
assert_eq!(agg.total_rows_processed(), 0);
}
#[test]
fn test_result_aggregator_speedup_zero_time() {
let agg = ResultAggregator::new();
let speedup = agg.speedup_factor(10);
assert_eq!(speedup, 1.0);
}
#[test]
fn test_partition_size_calculation() {
let data = vec![0; 1000];
let partitions = DataPartitioner::partition_by_rows(data, 1000, 4);
let total_size: usize = partitions.iter().map(|p| p.size_bytes()).sum();
assert_eq!(total_size, 1000);
}
#[test]
fn test_work_item_clone() {
let item1 = WorkItem::new(0, 100, 50);
let item2 = item1.clone();
assert_eq!(item1.partition_id, item2.partition_id);
assert_eq!(item1.start_row, item2.start_row);
}
#[test]
fn test_partition_result_matches_calculation() {
let result = PartitionResult::new(0, 2000, 1000, 10);
assert_eq!(result.matches, 1000);
let selectivity = (result.matches as f64) / (result.rows_processed as f64);
assert!((selectivity - 0.5).abs() < 0.01);
}
#[test]
fn test_execution_thread_pool_max_workers() {
let workers = num_cpus::get();
let pool = ExecutionThreadPool::new(workers * 2);
assert_eq!(pool.worker_count(), workers * 2);
}
#[test]
fn test_query_partition_empty_data() {
let partition = QueryPartition::new(0, Vec::new(), 0);
assert_eq!(partition.size_bytes(), 0);
assert_eq!(partition.row_count, 0);
}
#[test]
fn test_data_partitioner_large_partition_count() {
let data = vec![0; 100];
let partitions = DataPartitioner::partition_by_rows(data, 100, 1000);
assert!(partitions.len() <= 1000);
}
#[test]
fn test_result_aggregator_multiple_partitions() {
let mut agg = ResultAggregator::new();
for i in 0..10 {
agg.add_result(PartitionResult::new(i, 100, 50, 5));
}
assert_eq!(agg.results().len(), 10);
assert_eq!(agg.total_rows_processed(), 1000);
assert_eq!(agg.total_matches(), 500);
}
}