1use super::{
18 planner::{ExecutionStep, IndexSelection, QueryPlan, StepType},
19 ComparisonOperator, Condition,
20};
21use crate::{
22 schema::SchemaManager, storage::StorageEngine, Config, Error, Result, RowKey, ScanRow, TableId,
23 Value,
24};
25use std::cmp::Ordering;
26use std::collections::HashMap;
27use std::sync::Arc;
28use std::time::Instant;
29
30pub use super::result::{QueryResult, QueryRow};
32
33const TABLE_SCAN_STREAM_BUFFER: usize = 1024;
42
43#[derive(Debug, Clone)]
45pub struct QueryExecutor {
46 storage: Arc<StorageEngine>,
48 _schema: Arc<SchemaManager>,
50 _config: Config,
52}
53
54impl QueryExecutor {
55 pub fn new(storage: Arc<StorageEngine>, schema: Arc<SchemaManager>, config: &Config) -> Self {
57 Self {
58 storage,
59 _schema: schema,
60 _config: config.clone(),
61 }
62 }
63
64 #[tracing::instrument(
74 name = "query.execute",
75 skip_all,
76 fields(cqlite.query.plan_type = tracing::field::Empty),
77 )]
78 pub async fn execute(&self, plan: &QueryPlan) -> Result<QueryResult> {
79 let start_time = Instant::now();
80 tracing::Span::current().record(
81 crate::observability::catalog::attr::PLAN_TYPE,
82 Self::plan_type_label(&plan.plan_type),
83 );
84
85 let has_insert_step = plan
87 .steps
88 .iter()
89 .any(|step| matches!(step.step_type, StepType::Insert));
90 let is_create_table =
91 plan.steps.is_empty() && plan.table.is_some() && plan.estimated_rows == 0;
92
93 let result = match plan.plan_type {
94 super::planner::PlanType::PointLookup => self.execute_point_lookup(plan).await,
95 super::planner::PlanType::IndexScan => self.execute_index_scan(plan).await,
96 super::planner::PlanType::RangeScan => self.execute_range_scan(plan).await,
97 super::planner::PlanType::TableScan if has_insert_step => {
98 #[cfg(feature = "experimental")]
99 {
100 self.execute_insert_operation(plan).await
101 }
102 #[cfg(not(feature = "experimental"))]
103 {
104 Err(Error::UnsupportedFormat(
105 "INSERT operations require the 'experimental' feature. \
106 Add 'experimental' to your Cargo.toml features."
107 .to_string(),
108 ))
109 }
110 }
111 super::planner::PlanType::TableScan if is_create_table => {
112 self.execute_create_table_operation(plan).await
113 }
114 super::planner::PlanType::TableScan => self.execute_table_scan(plan).await,
115 super::planner::PlanType::Join => self.execute_join(plan).await,
116 super::planner::PlanType::Aggregation => self.execute_aggregation(plan).await,
117 super::planner::PlanType::Subquery => self.execute_subquery(plan).await,
118 };
119
120 let mut query_result = result?;
121 let elapsed_ms = start_time.elapsed().as_millis() as u64;
122
123 query_result.execution_time_ms = elapsed_ms;
124 query_result.metadata.plan_info = Some(super::result::PlanInfo {
125 plan_type: format!("{:?}", plan.plan_type),
126 estimated_cost: plan.estimated_cost,
127 actual_cost: elapsed_ms as f64,
128 indexes_used: Self::indexes_used_for(plan),
130 steps: plan
131 .steps
132 .iter()
133 .map(|s| format!("{:?}", s.step_type))
134 .collect(),
135 parallelization: Self::parallelization_for(plan),
136 });
137 Ok(query_result)
138 }
139
140 fn plan_type_label(plan_type: &super::planner::PlanType) -> &'static str {
146 use super::planner::PlanType;
147 match plan_type {
148 PlanType::TableScan => "table_scan",
149 PlanType::IndexScan => "index_scan",
150 PlanType::PointLookup => "point_lookup",
151 PlanType::RangeScan => "range_scan",
152 PlanType::Join => "join",
153 PlanType::Aggregation => "aggregation",
154 PlanType::Subquery => "subquery",
155 }
156 }
157
158 fn parallelization_for(plan: &QueryPlan) -> Option<super::result::ParallelizationInfo> {
201 use super::planner::PlanType;
202
203 let step = plan
204 .steps
205 .iter()
206 .find(|s| s.parallelization.can_parallelize)?;
207
208 if matches!(plan.plan_type, PlanType::TableScan) {
211 return Some(super::result::ParallelizationInfo {
212 threads_used: 1,
213 effective: false,
214 partitions: Vec::new(),
215 });
216 }
217
218 Some(super::result::ParallelizationInfo {
219 threads_used: step.parallelization.suggested_threads,
220 effective: true,
221 partitions: Vec::new(),
222 })
223 }
224
225 fn indexes_used_for(plan: &QueryPlan) -> Vec<String> {
226 use super::planner::{IndexType, PlanType, StepType};
227
228 let scan = || vec!["scan".to_string()];
230
231 let has_insert_step = plan
236 .steps
237 .iter()
238 .any(|step| matches!(step.step_type, StepType::Insert));
239 let is_create_table =
240 plan.steps.is_empty() && plan.table.is_some() && plan.estimated_rows == 0;
241 if matches!(plan.plan_type, PlanType::TableScan) && (has_insert_step || is_create_table) {
242 return Vec::new();
243 }
244
245 match plan.plan_type {
246 PlanType::PointLookup => match plan.selected_indexes.first() {
248 Some(idx) => vec![idx.index_name.clone()],
249 None => vec!["PRIMARY".to_string()],
252 },
253 PlanType::IndexScan => match plan.selected_indexes.first() {
256 Some(idx) => match idx.index_type {
257 IndexType::Primary | IndexType::BloomFilter => {
258 vec![idx.index_name.clone()]
259 }
260 IndexType::Secondary | IndexType::Composite => scan(),
261 },
262 None => scan(),
263 },
264 PlanType::TableScan | PlanType::RangeScan => scan(),
266 PlanType::Join | PlanType::Aggregation | PlanType::Subquery => Vec::new(),
269 }
270 }
271
272 fn require_table<'a>(&self, plan: &'a QueryPlan) -> Result<&'a TableId> {
274 plan.table
275 .as_ref()
276 .ok_or_else(|| Error::query_execution("Missing table in plan"))
277 }
278
279 fn find_condition<'a>(steps: &'a [ExecutionStep], column: &str) -> Option<&'a Condition> {
281 steps
282 .iter()
283 .flat_map(|s| s.conditions.iter())
284 .find(|c| c.column == column)
285 }
286
287 fn scan_pairs_to_rows(&self, pairs: Vec<(RowKey, ScanRow)>) -> Result<Vec<QueryRow>> {
289 let mut rows = Vec::with_capacity(pairs.len());
290 for (row_key, row_data) in pairs {
291 rows.push(self.storage_data_to_query_row(row_data, &row_key)?);
292 }
293 Ok(rows)
294 }
295
296 async fn full_scan_rows(&self, table: &TableId) -> Result<Vec<QueryRow>> {
298 let scan_results = self.storage.scan(table, None, None, None, None).await?;
299 self.scan_pairs_to_rows(scan_results)
300 }
301
302 async fn point_lookup_rows(
304 &self,
305 table: &TableId,
306 condition: &Condition,
307 ) -> Result<Vec<QueryRow>> {
308 let row_key = self.condition_to_row_key(condition)?;
309 match self.storage.get(table, &row_key).await? {
310 Some(row_data) => Ok(vec![self.storage_data_to_query_row(row_data, &row_key)?]),
311 None => Ok(Vec::new()),
312 }
313 }
314
315 fn make_result(rows: Vec<QueryRow>) -> QueryResult {
317 QueryResult::with_rows(rows)
318 }
319
320 #[tracing::instrument(name = "query.point_lookup", skip_all)]
324 async fn execute_point_lookup(&self, plan: &QueryPlan) -> Result<QueryResult> {
325 let table = self.require_table(plan)?;
326
327 let lookup_condition = plan
329 .steps
330 .iter()
331 .find_map(|step| step.conditions.first())
332 .ok_or_else(|| Error::query_execution("No lookup condition found"))?;
333
334 let row_key = self.condition_to_row_key(lookup_condition)?;
335
336 let mut rows = Vec::new();
337 if let Some(row_data) = self.storage.get(table, &row_key).await? {
338 rows.push(self.storage_data_to_query_row(row_data, &row_key)?);
339 }
340
341 Ok(Self::make_result(rows))
342 }
343
344 #[tracing::instrument(name = "query.index_scan", skip_all)]
346 async fn execute_index_scan(&self, plan: &QueryPlan) -> Result<QueryResult> {
347 let table = self.require_table(plan)?;
348
349 let index_selection = plan
350 .selected_indexes
351 .first()
352 .ok_or_else(|| Error::query_execution("No index selected"))?;
353
354 let mut rows = match index_selection.index_type {
355 super::planner::IndexType::Secondary => {
356 self.execute_secondary_index_scan(table, index_selection, &plan.steps)
357 .await?
358 }
359 super::planner::IndexType::BloomFilter => {
360 self.execute_bloom_filter_scan(table, index_selection, &plan.steps)
361 .await?
362 }
363 super::planner::IndexType::Primary => {
364 self.execute_primary_index_scan(table, index_selection, &plan.steps)
365 .await?
366 }
367 super::planner::IndexType::Composite => {
368 self.execute_composite_index_scan(table, index_selection, &plan.steps)
369 .await?
370 }
371 };
372
373 rows = self.apply_execution_steps(rows, &plan.steps).await?;
374 Ok(Self::make_result(rows))
375 }
376
377 #[tracing::instrument(name = "query.range_scan", skip_all)]
379 async fn execute_range_scan(&self, plan: &QueryPlan) -> Result<QueryResult> {
380 let table = self.require_table(plan)?;
381
382 let mut rows = self.full_scan_rows(table).await?;
385 rows = self.apply_execution_steps(rows, &plan.steps).await?;
386 Ok(Self::make_result(rows))
387 }
388
389 #[tracing::instrument(name = "query.table_scan", skip_all)]
391 async fn execute_table_scan(&self, plan: &QueryPlan) -> Result<QueryResult> {
392 let table = self.require_table(plan)?;
393
394 #[cfg(debug_assertions)]
395 log::debug!("executor: Scanning for table: {:?}", table.name());
396
397 let can_parallelize = plan
402 .steps
403 .iter()
404 .any(|step| step.parallelization.can_parallelize);
405
406 let mut rows = if can_parallelize {
407 self.streaming_scan_rows(table).await?
408 } else {
409 self.full_scan_rows(table).await?
410 };
411
412 rows = self.apply_execution_steps(rows, &plan.steps).await?;
413 Ok(Self::make_result(rows))
414 }
415
416 async fn execute_join(&self, _plan: &QueryPlan) -> Result<QueryResult> {
418 Ok(QueryResult::new())
419 }
420
421 async fn execute_aggregation(&self, _plan: &QueryPlan) -> Result<QueryResult> {
423 Ok(QueryResult::new())
424 }
425
426 async fn execute_subquery(&self, _plan: &QueryPlan) -> Result<QueryResult> {
428 Ok(QueryResult::new())
429 }
430
431 async fn execute_secondary_index_scan(
436 &self,
437 table: &TableId,
438 index_selection: &IndexSelection,
439 steps: &[ExecutionStep],
440 ) -> Result<Vec<QueryRow>> {
441 Self::find_condition(steps, &index_selection.columns[0])
443 .ok_or_else(|| Error::query_execution("No condition found for index"))?;
444 self.full_scan_rows(table).await
445 }
446
447 async fn execute_bloom_filter_scan(
449 &self,
450 table: &TableId,
451 index_selection: &IndexSelection,
452 steps: &[ExecutionStep],
453 ) -> Result<Vec<QueryRow>> {
454 let condition = Self::find_condition(steps, &index_selection.columns[0])
455 .ok_or_else(|| Error::query_execution("No condition found for bloom filter"))?;
456 self.point_lookup_rows(table, condition).await
457 }
458
459 async fn execute_primary_index_scan(
461 &self,
462 table: &TableId,
463 index_selection: &IndexSelection,
464 steps: &[ExecutionStep],
465 ) -> Result<Vec<QueryRow>> {
466 let condition = Self::find_condition(steps, &index_selection.columns[0])
467 .ok_or_else(|| Error::query_execution("No condition found for primary key"))?;
468 self.point_lookup_rows(table, condition).await
469 }
470
471 async fn execute_composite_index_scan(
474 &self,
475 table: &TableId,
476 _index_selection: &IndexSelection,
477 _steps: &[ExecutionStep],
478 ) -> Result<Vec<QueryRow>> {
479 self.full_scan_rows(table).await
480 }
481
482 #[tracing::instrument(name = "query.table_scan_stream", skip_all)]
496 async fn streaming_scan_rows(&self, table: &TableId) -> Result<Vec<QueryRow>> {
497 let mut scan_stream = self
498 .storage
499 .scan_stream(table, None, None, None, TABLE_SCAN_STREAM_BUFFER)
500 .await?;
501
502 let mut rows = Vec::new();
503 while let Some(item) = scan_stream.recv().await {
504 let (row_key, row_data) = item?;
505 rows.push(self.storage_data_to_query_row(row_data, &row_key)?);
506 }
507 Ok(rows)
508 }
509
510 async fn apply_execution_steps(
518 &self,
519 mut rows: Vec<QueryRow>,
520 steps: &[ExecutionStep],
521 ) -> Result<Vec<QueryRow>> {
522 for step in steps {
523 match step.step_type {
524 StepType::Filter => rows = self.apply_filter_step(rows, step)?,
525 StepType::Sort => rows = self.apply_sort_step(rows, step),
526 StepType::Project => rows = self.apply_project_step(rows, step),
527 StepType::Limit
529 | StepType::Aggregate
530 | StepType::Join
531 | StepType::Scan
532 | StepType::Insert => {}
533 }
534 }
535 Ok(rows)
536 }
537
538 fn apply_filter_step(
540 &self,
541 rows: Vec<QueryRow>,
542 step: &ExecutionStep,
543 ) -> Result<Vec<QueryRow>> {
544 let mut filtered_rows = Vec::with_capacity(rows.len());
545 for row in rows {
546 let mut matches = true;
547 for condition in &step.conditions {
548 if !self.evaluate_condition(&row, condition)? {
549 matches = false;
550 break;
551 }
552 }
553 if matches {
554 filtered_rows.push(row);
555 }
556 }
557 Ok(filtered_rows)
558 }
559
560 fn apply_sort_step(&self, mut rows: Vec<QueryRow>, step: &ExecutionStep) -> Vec<QueryRow> {
562 let Some(sort_column) = step.columns.first() else {
563 return rows;
564 };
565
566 rows.sort_by(|a, b| {
567 let a_val = a.values.get(sort_column.as_str()).unwrap_or(&Value::Null);
568 let b_val = b.values.get(sort_column.as_str()).unwrap_or(&Value::Null);
569 self.compare_values(a_val, b_val).unwrap_or(Ordering::Equal)
570 });
571 rows
572 }
573
574 fn apply_project_step(&self, rows: Vec<QueryRow>, step: &ExecutionStep) -> Vec<QueryRow> {
576 rows.into_iter()
577 .map(|row| {
578 let mut projected_values = HashMap::with_capacity(step.columns.len());
579 for column in &step.columns {
580 if let Some(value) = row.values.get(column.as_str()) {
581 projected_values.insert(column.clone(), value.clone());
582 }
583 }
584 QueryRow::with_values(row.key, projected_values)
585 })
586 .collect()
587 }
588
589 fn evaluate_condition(&self, row: &QueryRow, condition: &Condition) -> Result<bool> {
593 let row_value = row
594 .values
595 .get(condition.column.as_str())
596 .unwrap_or(&Value::Null);
597
598 match condition.operator {
599 ComparisonOperator::Equal => Ok(row_value == &condition.value),
600 ComparisonOperator::NotEqual => Ok(row_value != &condition.value),
601 ComparisonOperator::LessThan => Ok(matches!(
602 self.compare_values(row_value, &condition.value)?,
603 Ordering::Less
604 )),
605 ComparisonOperator::LessThanOrEqual => Ok(matches!(
606 self.compare_values(row_value, &condition.value)?,
607 Ordering::Less | Ordering::Equal
608 )),
609 ComparisonOperator::GreaterThan => Ok(matches!(
610 self.compare_values(row_value, &condition.value)?,
611 Ordering::Greater
612 )),
613 ComparisonOperator::GreaterThanOrEqual => Ok(matches!(
614 self.compare_values(row_value, &condition.value)?,
615 Ordering::Greater | Ordering::Equal
616 )),
617 ComparisonOperator::In => Ok(row_value == &condition.value),
619 ComparisonOperator::NotIn => Ok(row_value != &condition.value),
620 ComparisonOperator::Like => match (row_value, &condition.value) {
621 (Value::Text(row_text), Value::Text(pattern)) => Ok(row_text.contains(pattern)),
622 _ => Ok(false),
623 },
624 ComparisonOperator::NotLike => match (row_value, &condition.value) {
625 (Value::Text(row_text), Value::Text(pattern)) => Ok(!row_text.contains(pattern)),
626 _ => Ok(true),
627 },
628 }
629 }
630
631 fn compare_values(&self, a: &Value, b: &Value) -> Result<Ordering> {
633 match (a, b) {
634 (Value::Integer(a), Value::Integer(b)) => Ok(a.cmp(b)),
635 (Value::Float(a), Value::Float(b)) => Ok(a.partial_cmp(b).unwrap_or(Ordering::Equal)),
636 (Value::Text(a), Value::Text(b)) => Ok(a.cmp(b)),
637 (Value::Boolean(a), Value::Boolean(b)) => Ok(a.cmp(b)),
638 (Value::Uuid(a), Value::Uuid(b)) => Ok(a.cmp(b)),
641 (Value::Null, Value::Null) => Ok(Ordering::Equal),
642 (Value::Null, _) => Ok(Ordering::Less),
643 (_, Value::Null) => Ok(Ordering::Greater),
644 _ => Err(Error::query_execution(
645 "Cannot compare values of different types",
646 )),
647 }
648 }
649
650 fn value_to_row_key(&self, value: &Value) -> Result<RowKey> {
662 match value {
663 Value::Integer(i) => Ok(RowKey::new(i.to_be_bytes().to_vec())),
664 Value::Text(s) => Ok(RowKey::new(s.as_bytes().to_vec())),
665 Value::Float(f) => Ok(RowKey::new(f.to_be_bytes().to_vec())),
666 Value::Boolean(b) => Ok(RowKey::new(vec![u8::from(*b)])),
667 Value::Null => Ok(RowKey::new(vec![0])),
668 Value::Uuid(bytes) => Ok(RowKey::new(bytes.to_vec())),
671 Value::BigInt(i) => Ok(RowKey::new(i.to_be_bytes().to_vec())),
672 Value::Tuple(components) => {
676 let mut result = Vec::new();
677 for component in components {
678 let raw = self.value_to_raw_pk_bytes(component)?;
679 let len = raw.len();
680 if len > u16::MAX as usize {
681 return Err(Error::query_execution(
682 "Composite partition key component too large",
683 ));
684 }
685 result.extend_from_slice(&(len as u16).to_be_bytes());
686 result.extend_from_slice(&raw);
687 result.push(0x00);
688 }
689 Ok(RowKey::new(result))
690 }
691 _ => Err(Error::query_execution("Cannot convert value to row key")),
692 }
693 }
694
695 fn value_to_raw_pk_bytes(&self, value: &Value) -> Result<Vec<u8>> {
699 match value {
700 Value::Integer(i) => Ok(i.to_be_bytes().to_vec()),
701 Value::Text(s) => Ok(s.as_bytes().to_vec()),
702 Value::Float(f) => Ok(f.to_be_bytes().to_vec()),
703 Value::Boolean(b) => Ok(vec![u8::from(*b)]),
704 Value::Null => Ok(Vec::new()),
705 Value::Uuid(bytes) => Ok(bytes.to_vec()),
706 Value::BigInt(i) => Ok(i.to_be_bytes().to_vec()),
707 _ => Err(Error::query_execution(
708 "Cannot serialize value as partition key component",
709 )),
710 }
711 }
712
713 fn condition_to_row_key(&self, condition: &Condition) -> Result<RowKey> {
715 if condition.column == "id" {
717 if let Value::Integer(id) = &condition.value {
718 return Ok(RowKey::new(format!("user_key_{}", id).into_bytes()));
719 }
720 }
721 self.value_to_row_key(&condition.value)
722 }
723
724 fn storage_data_to_query_row(&self, data: ScanRow, key: &RowKey) -> Result<QueryRow> {
726 use std::sync::Arc;
727 let mut values: HashMap<Arc<str>, Value> = HashMap::new();
728
729 match data {
737 ScanRow::Row(cells) => {
738 for (name, cell_value) in cells {
739 values.insert(name, cell_value);
740 }
741 }
742 ScanRow::RawRow(bytes) => {
743 values.insert(Arc::from("data"), Value::Blob(bytes));
744 }
745 ScanRow::Marker(_) => {}
746 }
747
748 if values.is_empty() {
750 values.insert(Arc::from("id"), Value::Text(format!("{:?}", key)));
751 }
752
753 Ok(QueryRow::with_interned_values(key.clone(), values))
754 }
755
756 #[cfg(feature = "experimental")]
760 async fn execute_insert_operation(&self, plan: &QueryPlan) -> Result<QueryResult> {
761 let table_id = self
762 .require_table(plan)
763 .map_err(|_| Error::query_execution("No table specified in INSERT plan"))?;
764
765 let mut inserted_count: u64 = 0;
766
767 for step in &plan.steps {
768 if !matches!(step.step_type, StepType::Insert) {
769 continue;
770 }
771
772 let mut key_value = format!("test_key_{}", inserted_count);
775 for condition in &step.conditions {
776 if condition.column == "id" {
777 if let Value::Integer(id) = &condition.value {
778 key_value = format!("user_key_{}", id);
779 break;
780 }
781 }
782 }
783
784 let row_key = RowKey::new(key_value.into_bytes());
785
786 let mut value_map: HashMap<String, Value> = step
789 .conditions
790 .iter()
791 .map(|c| (c.column.clone(), c.value.clone()))
792 .collect();
793
794 if value_map.is_empty() {
795 value_map.insert("id".to_string(), Value::Integer(inserted_count as i32 + 1));
796 value_map.insert(
797 "name".to_string(),
798 Value::Text(format!("TestUser{}", inserted_count + 1)),
799 );
800 }
801
802 let row_value = map_to_value(value_map);
803
804 self.storage.put(table_id, row_key, row_value).await?;
805 inserted_count += 1;
806 }
807
808 if inserted_count == 0 {
811 let row_key = RowKey::new(b"default_test_key".to_vec());
812 let mut value_map = HashMap::new();
813 value_map.insert("id".to_string(), Value::Integer(1));
814 value_map.insert("name".to_string(), Value::Text("DefaultUser".to_string()));
815
816 self.storage
817 .put(table_id, row_key, map_to_value(value_map))
818 .await?;
819 inserted_count = 1;
820 }
821
822 Ok(QueryResult {
823 rows: vec![],
824 rows_affected: inserted_count,
825 execution_time_ms: 0,
826 metadata: super::result::QueryMetadata::default(),
827 })
828 }
829
830 async fn execute_create_table_operation(&self, _plan: &QueryPlan) -> Result<QueryResult> {
832 Ok(QueryResult {
833 rows: vec![],
834 rows_affected: 0,
835 execution_time_ms: 0,
836 metadata: super::result::QueryMetadata::default(),
837 })
838 }
839}
840
841#[cfg(feature = "experimental")]
843fn map_to_value(map: HashMap<String, Value>) -> Value {
844 Value::Map(map.into_iter().map(|(k, v)| (Value::Text(k), v)).collect())
845}
846
847#[cfg(test)]
848mod tests {
849 use super::*;
850 use crate::Config;
851 use std::sync::Arc;
852 use tempfile::TempDir;
853
854 async fn make_executor() -> (TempDir, QueryExecutor, Config) {
856 let temp_dir = TempDir::new().unwrap();
857 let config = Config::default();
858 let platform = Arc::new(crate::platform::Platform::new(&config).await.unwrap());
859 let storage = Arc::new(
860 crate::storage::StorageEngine::open(
861 temp_dir.path(),
862 &config,
863 platform,
864 #[cfg(feature = "state_machine")]
865 None,
866 )
867 .await
868 .unwrap(),
869 );
870 let schema = Arc::new(
871 crate::schema::SchemaManager::new(temp_dir.path())
872 .await
873 .unwrap(),
874 );
875 let executor = QueryExecutor::new(storage, schema, &config);
876 (temp_dir, executor, config)
877 }
878
879 #[tokio::test]
880 async fn test_query_executor_creation() {
881 let (_tmp, executor, config) = make_executor().await;
882 assert_eq!(
883 executor._config.query.query_parallelism,
884 config.query.query_parallelism
885 );
886 }
887
888 #[tokio::test]
889 async fn test_value_comparison() {
890 let (_tmp, executor, _) = make_executor().await;
891
892 let result = executor
893 .compare_values(&Value::Integer(10), &Value::Integer(20))
894 .unwrap();
895 assert_eq!(result, Ordering::Less);
896
897 let result = executor
898 .compare_values(
899 &Value::Text("apple".to_string()),
900 &Value::Text("banana".to_string()),
901 )
902 .unwrap();
903 assert_eq!(result, Ordering::Less);
904 }
905
906 #[tokio::test]
907 async fn test_condition_evaluation() {
908 let (_tmp, executor, _) = make_executor().await;
909
910 let mut row_values = HashMap::new();
911 row_values.insert("id".to_string(), Value::Integer(1));
912 row_values.insert("name".to_string(), Value::Text("test".to_string()));
913 let row = QueryRow::with_values(RowKey::new(vec![1]), row_values);
914
915 let condition = Condition {
916 column: "id".to_string(),
917 operator: ComparisonOperator::Equal,
918 value: Value::Integer(1),
919 };
920 assert!(executor.evaluate_condition(&row, &condition).unwrap());
921
922 let condition = Condition {
923 column: "name".to_string(),
924 operator: ComparisonOperator::Like,
925 value: Value::Text("test".to_string()),
926 };
927 assert!(executor.evaluate_condition(&row, &condition).unwrap());
928 }
929
930 use super::super::planner::{IndexSelection, IndexType};
933
934 fn plan_with(
936 plan_type: super::super::planner::PlanType,
937 selected_indexes: Vec<IndexSelection>,
938 ) -> QueryPlan {
939 QueryPlan {
940 plan_type,
941 table: None,
942 estimated_cost: 0.0,
943 estimated_rows: 0,
944 selected_indexes,
945 steps: Vec::new(),
946 hints: super::super::planner::QueryHints::default(),
947 }
948 }
949
950 fn primary_index() -> IndexSelection {
951 IndexSelection {
952 index_name: "PRIMARY".to_string(),
953 columns: vec!["id".to_string()],
954 selectivity: 0.1,
955 index_type: IndexType::Primary,
956 }
957 }
958
959 #[test]
962 fn test_indexes_used_point_lookup_reports_partition_index() {
963 let plan = plan_with(
964 super::super::planner::PlanType::PointLookup,
965 vec![primary_index()],
966 );
967 assert_eq!(QueryExecutor::indexes_used_for(&plan), vec!["PRIMARY"]);
968 }
969
970 #[test]
973 fn test_indexes_used_table_scan_reports_scan_marker() {
974 let plan = plan_with(super::super::planner::PlanType::TableScan, Vec::new());
975 assert_eq!(QueryExecutor::indexes_used_for(&plan), vec!["scan"]);
976 }
977
978 #[test]
983 fn test_indexes_used_insert_and_ddl_table_scan_report_no_scan() {
984 use super::super::planner::{ParallelizationInfo, StepType};
985
986 let insert_step = ExecutionStep {
988 step_type: StepType::Insert,
989 columns: Vec::new(),
990 conditions: Vec::new(),
991 cost: 0.0,
992 parallelization: ParallelizationInfo {
993 can_parallelize: false,
994 suggested_threads: 1,
995 partition_key: None,
996 },
997 };
998 let mut insert_plan = plan_with(super::super::planner::PlanType::TableScan, Vec::new());
999 insert_plan.steps = vec![insert_step];
1000 assert!(
1001 QueryExecutor::indexes_used_for(&insert_plan).is_empty(),
1002 "INSERT must not report a scan access path"
1003 );
1004
1005 let mut ddl_plan = plan_with(super::super::planner::PlanType::TableScan, Vec::new());
1007 ddl_plan.table = Some(TableId::new("t"));
1008 ddl_plan.estimated_rows = 0;
1009 assert!(
1010 QueryExecutor::indexes_used_for(&ddl_plan).is_empty(),
1011 "CREATE TABLE must not report a scan access path"
1012 );
1013 }
1014
1015 #[test]
1017 fn test_indexes_used_range_scan_reports_scan_marker() {
1018 let plan = plan_with(super::super::planner::PlanType::RangeScan, Vec::new());
1019 assert_eq!(QueryExecutor::indexes_used_for(&plan), vec!["scan"]);
1020 }
1021
1022 #[test]
1025 fn test_indexes_used_index_scan_primary_reports_index() {
1026 let plan = plan_with(
1027 super::super::planner::PlanType::IndexScan,
1028 vec![primary_index()],
1029 );
1030 assert_eq!(QueryExecutor::indexes_used_for(&plan), vec!["PRIMARY"]);
1031 }
1032
1033 #[test]
1037 fn test_indexes_used_index_scan_secondary_reports_scan() {
1038 let secondary = IndexSelection {
1039 index_name: "idx_name".to_string(),
1040 columns: vec!["name".to_string()],
1041 selectivity: 0.1,
1042 index_type: IndexType::Secondary,
1043 };
1044 let plan = plan_with(super::super::planner::PlanType::IndexScan, vec![secondary]);
1045 assert_eq!(QueryExecutor::indexes_used_for(&plan), vec!["scan"]);
1046 }
1047
1048 fn parallelizable_step() -> ExecutionStep {
1052 use super::super::planner::{ParallelizationInfo, StepType};
1053 ExecutionStep {
1054 step_type: StepType::Scan,
1055 columns: Vec::new(),
1056 conditions: Vec::new(),
1057 cost: 0.0,
1058 parallelization: ParallelizationInfo {
1059 can_parallelize: true,
1060 suggested_threads: 8,
1061 partition_key: None,
1062 },
1063 }
1064 }
1065
1066 #[test]
1071 fn test_parallelization_table_scan_reports_single_threaded() {
1072 let mut plan = plan_with(super::super::planner::PlanType::TableScan, Vec::new());
1073 plan.steps = vec![parallelizable_step()];
1074
1075 let info = QueryExecutor::parallelization_for(&plan)
1076 .expect("a parallelizable step should still yield metadata");
1077 assert_eq!(info.threads_used, 1);
1078 assert!(!info.effective);
1079 assert!(info.partitions.is_empty());
1080 }
1081
1082 #[test]
1084 fn test_parallelization_absent_when_no_step_parallelizes() {
1085 let plan = plan_with(super::super::planner::PlanType::TableScan, Vec::new());
1086 assert!(QueryExecutor::parallelization_for(&plan).is_none());
1087 }
1088
1089 #[test]
1092 fn test_parallelization_non_scan_reports_planner_threads() {
1093 let mut plan = plan_with(super::super::planner::PlanType::Aggregation, Vec::new());
1094 plan.steps = vec![parallelizable_step()];
1095
1096 let info = QueryExecutor::parallelization_for(&plan)
1097 .expect("a parallelizable step should yield metadata");
1098 assert_eq!(info.threads_used, 8);
1099 assert!(info.effective);
1100 }
1101
1102 #[tokio::test]
1103 async fn test_condition_to_row_key_mapping() {
1104 let (_tmp, executor, _) = make_executor().await;
1105
1106 let id_condition = Condition {
1107 column: "id".to_string(),
1108 operator: ComparisonOperator::Equal,
1109 value: Value::Integer(42),
1110 };
1111 let key = executor
1112 .condition_to_row_key(&id_condition)
1113 .expect("id condition key");
1114 assert_eq!(std::str::from_utf8(key.as_bytes()).unwrap(), "user_key_42");
1115
1116 let name_condition = Condition {
1117 column: "username".to_string(),
1118 operator: ComparisonOperator::Equal,
1119 value: Value::Text("carol".to_string()),
1120 };
1121 let key = executor
1122 .condition_to_row_key(&name_condition)
1123 .expect("fallback key");
1124 assert_eq!(key.as_bytes(), b"carol");
1125 }
1126
1127 #[tokio::test]
1136 async fn offset_read_row_surfaces_data_marker_is_suppressed() {
1137 let (_tmp, executor, _) = make_executor().await;
1138 let key = RowKey::new(vec![7]);
1139 let raw = vec![0xde, 0xad, 0xbe, 0xef];
1140
1141 let live = ScanRow::RawRow(raw.clone());
1143 let row = executor
1144 .storage_data_to_query_row(live, &key)
1145 .expect("raw offset-read row must convert");
1146 assert_eq!(
1147 row.values.get("data"),
1148 Some(&Value::Blob(raw.clone())),
1149 "a live offset/indexed read must surface its raw value as the \"data\" column"
1150 );
1151
1152 let marker = ScanRow::Marker(Value::Blob(raw));
1156 let suppressed = executor
1157 .storage_data_to_query_row(marker, &key)
1158 .expect("marker row must still convert");
1159 assert!(
1160 !suppressed.values.contains_key("data"),
1161 "a Marker must NOT surface the raw blob (this is the suppression the fix avoids)"
1162 );
1163 }
1164
1165 fn parallelizable_table_scan_plan() -> QueryPlan {
1172 use super::super::planner::{ParallelizationInfo, PlanType, QueryHints, StepType};
1173 QueryPlan {
1174 plan_type: PlanType::TableScan,
1175 table: Some(TableId::new("t")),
1176 estimated_cost: 0.0,
1177 estimated_rows: 1,
1178 selected_indexes: Vec::new(),
1179 steps: vec![ExecutionStep {
1180 step_type: StepType::Scan,
1181 columns: Vec::new(),
1182 conditions: Vec::new(),
1183 cost: 0.0,
1184 parallelization: ParallelizationInfo {
1185 can_parallelize: true,
1186 suggested_threads: 4,
1187 partition_key: None,
1188 },
1189 }],
1190 hints: QueryHints::default(),
1191 }
1192 }
1193
1194 #[tokio::test]
1207 async fn table_scan_parallel_branch_issues_one_whole_table_pass() {
1208 let (_tmp, executor, _) = make_executor().await;
1209 crate::storage::reset_table_scan_calls();
1210
1211 let plan = parallelizable_table_scan_plan();
1212 let _ = executor.execute(&plan).await.expect("table scan executes");
1213
1214 assert_eq!(
1215 crate::storage::table_scan_call_count(),
1216 1,
1217 "the parallelizable TableScan branch must issue exactly ONE whole-table \
1218 scan pass; the retired execute_parallel_table_scan issued one per worker (4×)"
1219 );
1220 }
1221
1222 #[tokio::test]
1227 async fn streaming_scan_branch_returns_all_rows_bounded() {
1228 let (_tmp, executor, _) = make_executor().await;
1229 crate::storage::reset_table_scan_calls();
1230
1231 let plan = parallelizable_table_scan_plan();
1232 let result = executor.execute(&plan).await.expect("table scan executes");
1233
1234 assert!(result.rows.is_empty(), "empty table yields no rows");
1235 assert_eq!(
1236 crate::storage::table_scan_call_count(),
1237 1,
1238 "the streaming drain must not re-issue the scan"
1239 );
1240 }
1241}