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 tracing::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
502 .storage
503 .scan_stream_batched(table, None, None, None, TABLE_SCAN_STREAM_BUFFER)
504 .await?;
505
506 let mut rows = Vec::new();
507 while let Some(batch) = scan_stream.recv().await {
508 for (row_key, row_data) in batch? {
509 rows.push(self.storage_data_to_query_row(row_data, &row_key)?);
510 }
511 }
512 Ok(rows)
513 }
514
515 async fn apply_execution_steps(
523 &self,
524 mut rows: Vec<QueryRow>,
525 steps: &[ExecutionStep],
526 ) -> Result<Vec<QueryRow>> {
527 for step in steps {
528 match step.step_type {
529 StepType::Filter => rows = self.apply_filter_step(rows, step)?,
530 StepType::Sort => rows = self.apply_sort_step(rows, step),
531 StepType::Project => rows = self.apply_project_step(rows, step),
532 StepType::Limit
534 | StepType::Aggregate
535 | StepType::Join
536 | StepType::Scan
537 | StepType::Insert => {}
538 }
539 }
540 Ok(rows)
541 }
542
543 fn apply_filter_step(
545 &self,
546 rows: Vec<QueryRow>,
547 step: &ExecutionStep,
548 ) -> Result<Vec<QueryRow>> {
549 let mut filtered_rows = Vec::with_capacity(rows.len());
550 for row in rows {
551 let mut matches = true;
552 for condition in &step.conditions {
553 if !self.evaluate_condition(&row, condition)? {
554 matches = false;
555 break;
556 }
557 }
558 if matches {
559 filtered_rows.push(row);
560 }
561 }
562 Ok(filtered_rows)
563 }
564
565 fn apply_sort_step(&self, mut rows: Vec<QueryRow>, step: &ExecutionStep) -> Vec<QueryRow> {
567 let Some(sort_column) = step.columns.first() else {
568 return rows;
569 };
570
571 rows.sort_by(|a, b| {
572 let a_val = a.values.get(sort_column.as_str()).unwrap_or(&Value::Null);
573 let b_val = b.values.get(sort_column.as_str()).unwrap_or(&Value::Null);
574 self.compare_values(a_val, b_val).unwrap_or(Ordering::Equal)
575 });
576 rows
577 }
578
579 fn apply_project_step(&self, rows: Vec<QueryRow>, step: &ExecutionStep) -> Vec<QueryRow> {
581 rows.into_iter()
582 .map(|row| {
583 let mut projected_values = HashMap::with_capacity(step.columns.len());
584 for column in &step.columns {
585 if let Some(value) = row.values.get(column.as_str()) {
586 projected_values.insert(column.clone(), value.clone());
587 }
588 }
589 QueryRow::with_values(row.key, projected_values)
590 })
591 .collect()
592 }
593
594 fn evaluate_condition(&self, row: &QueryRow, condition: &Condition) -> Result<bool> {
598 let row_value = row
599 .values
600 .get(condition.column.as_str())
601 .unwrap_or(&Value::Null);
602
603 match condition.operator {
604 ComparisonOperator::Equal => Ok(row_value == &condition.value),
605 ComparisonOperator::NotEqual => Ok(row_value != &condition.value),
606 ComparisonOperator::LessThan => Ok(self
607 .predicate_ordering(row_value, &condition.value)?
608 .is_some_and(|o| o == Ordering::Less)),
609 ComparisonOperator::LessThanOrEqual => Ok(self
610 .predicate_ordering(row_value, &condition.value)?
611 .is_some_and(|o| matches!(o, Ordering::Less | Ordering::Equal))),
612 ComparisonOperator::GreaterThan => Ok(self
613 .predicate_ordering(row_value, &condition.value)?
614 .is_some_and(|o| o == Ordering::Greater)),
615 ComparisonOperator::GreaterThanOrEqual => Ok(self
616 .predicate_ordering(row_value, &condition.value)?
617 .is_some_and(|o| matches!(o, Ordering::Greater | Ordering::Equal))),
618 ComparisonOperator::In => Ok(row_value == &condition.value),
620 ComparisonOperator::NotIn => Ok(row_value != &condition.value),
621 ComparisonOperator::Like => match (row_value.as_str(), condition.value.as_str()) {
622 (Some(row_text), Some(pattern)) => Ok(row_text.contains(pattern)),
623 _ => Ok(false),
624 },
625 ComparisonOperator::NotLike => match (row_value.as_str(), condition.value.as_str()) {
626 (Some(row_text), Some(pattern)) => Ok(!row_text.contains(pattern)),
627 _ => Ok(true),
628 },
629 }
630 }
631
632 fn predicate_ordering(&self, a: &Value, b: &Value) -> Result<Option<Ordering>> {
658 use crate::query::select_executor::value_ops::is_nan_value;
659 if is_nan_value(a) || is_nan_value(b) {
660 return Ok(None);
661 }
662 self.compare_values(a, b).map(Some)
663 }
664
665 fn compare_values(&self, a: &Value, b: &Value) -> Result<Ordering> {
667 use crate::float_cmp::cassandra_double_cmp as dcmp;
668 use crate::float_cmp::cassandra_float_cmp as fcmp;
669 match (a, b) {
670 (Value::Integer(a), Value::Integer(b)) => Ok(a.cmp(b)),
671 (Value::Float(a), Value::Float(b)) => Ok(dcmp(*a, *b)), (Value::Float32(a), Value::Float32(b)) => Ok(fcmp(*a, *b)), (Value::Text(a), Value::Text(b)) => Ok(a.cmp(b)),
674 (Value::Boolean(a), Value::Boolean(b)) => Ok(a.cmp(b)),
675 (Value::Uuid(a), Value::Uuid(b)) => Ok(a.cmp(b)),
677 (Value::Null, Value::Null) => Ok(Ordering::Equal),
678 (Value::Null, _) => Ok(Ordering::Less),
679 (_, Value::Null) => Ok(Ordering::Greater),
680 _ => Err(Error::query_execution(
681 "Cannot compare values of different types",
682 )),
683 }
684 }
685
686 fn value_to_row_key(&self, value: &Value) -> Result<RowKey> {
698 match value {
699 Value::Integer(i) => Ok(RowKey::new(i.to_be_bytes().to_vec())),
700 Value::Text(s) => Ok(RowKey::new(s.to_vec())),
701 Value::Float(f) => Ok(RowKey::new(f.to_be_bytes().to_vec())),
702 Value::Boolean(b) => Ok(RowKey::new(vec![u8::from(*b)])),
703 Value::Null => Ok(RowKey::new(vec![0])),
704 Value::Uuid(bytes) => Ok(RowKey::new(bytes.to_vec())),
707 Value::BigInt(i) => Ok(RowKey::new(i.to_be_bytes().to_vec())),
708 Value::Tuple(components) => {
712 let mut result = Vec::new();
713 for component in components {
714 let raw = self.value_to_raw_pk_bytes(component)?;
715 let len = raw.len();
716 if len > u16::MAX as usize {
717 return Err(Error::query_execution(
718 "Composite partition key component too large",
719 ));
720 }
721 result.extend_from_slice(&(len as u16).to_be_bytes());
722 result.extend_from_slice(&raw);
723 result.push(0x00);
724 }
725 Ok(RowKey::new(result))
726 }
727 _ => Err(Error::query_execution("Cannot convert value to row key")),
728 }
729 }
730
731 fn value_to_raw_pk_bytes(&self, value: &Value) -> Result<Vec<u8>> {
735 match value {
736 Value::Integer(i) => Ok(i.to_be_bytes().to_vec()),
737 Value::Text(s) => Ok(s.to_vec()),
738 Value::Float(f) => Ok(f.to_be_bytes().to_vec()),
739 Value::Boolean(b) => Ok(vec![u8::from(*b)]),
740 Value::Null => Ok(Vec::new()),
741 Value::Uuid(bytes) => Ok(bytes.to_vec()),
742 Value::BigInt(i) => Ok(i.to_be_bytes().to_vec()),
743 _ => Err(Error::query_execution(
744 "Cannot serialize value as partition key component",
745 )),
746 }
747 }
748
749 fn condition_to_row_key(&self, condition: &Condition) -> Result<RowKey> {
751 if condition.column == "id" {
753 if let Value::Integer(id) = &condition.value {
754 return Ok(RowKey::new(format!("user_key_{}", id).into_bytes()));
755 }
756 }
757 self.value_to_row_key(&condition.value)
758 }
759
760 fn storage_data_to_query_row(&self, data: ScanRow, key: &RowKey) -> Result<QueryRow> {
762 use std::sync::Arc;
763 let mut values: HashMap<Arc<str>, Value> = HashMap::new();
764
765 match data {
773 ScanRow::Row(cells) => {
774 for (name, cell_value) in cells {
782 values.insert(name, cell_value.into_owned());
783 }
784 }
785 ScanRow::RawRow(bytes) => {
786 values.insert(Arc::from("data"), Value::Blob(bytes.into()));
787 }
788 ScanRow::Marker(_) => {}
789 }
790
791 if values.is_empty() {
793 values.insert(Arc::from("id"), Value::text(format!("{:?}", key)));
794 }
795
796 Ok(QueryRow::with_interned_values(key.clone(), values))
797 }
798
799 #[cfg(feature = "experimental")]
803 async fn execute_insert_operation(&self, plan: &QueryPlan) -> Result<QueryResult> {
804 let table_id = self
805 .require_table(plan)
806 .map_err(|_| Error::query_execution("No table specified in INSERT plan"))?;
807
808 let mut inserted_count: u64 = 0;
809
810 for step in &plan.steps {
811 if !matches!(step.step_type, StepType::Insert) {
812 continue;
813 }
814
815 let mut key_value = format!("test_key_{}", inserted_count);
818 for condition in &step.conditions {
819 if condition.column == "id" {
820 if let Value::Integer(id) = &condition.value {
821 key_value = format!("user_key_{}", id);
822 break;
823 }
824 }
825 }
826
827 let row_key = RowKey::new(key_value.into_bytes());
828
829 let mut value_map: HashMap<String, Value> = step
832 .conditions
833 .iter()
834 .map(|c| (c.column.clone(), c.value.clone()))
835 .collect();
836
837 if value_map.is_empty() {
838 value_map.insert("id".to_string(), Value::Integer(inserted_count as i32 + 1));
839 value_map.insert(
840 "name".to_string(),
841 Value::text(format!("TestUser{}", inserted_count + 1)),
842 );
843 }
844
845 let row_value = map_to_value(value_map);
846
847 self.storage.put(table_id, row_key, row_value).await?;
848 inserted_count += 1;
849 }
850
851 if inserted_count == 0 {
854 let row_key = RowKey::new(b"default_test_key".to_vec());
855 let mut value_map = HashMap::new();
856 value_map.insert("id".to_string(), Value::Integer(1));
857 value_map.insert("name".to_string(), Value::text("DefaultUser".to_string()));
858
859 self.storage
860 .put(table_id, row_key, map_to_value(value_map))
861 .await?;
862 inserted_count = 1;
863 }
864
865 Ok(QueryResult {
866 rows: vec![],
867 rows_affected: inserted_count,
868 execution_time_ms: 0,
869 metadata: super::result::QueryMetadata::default(),
870 })
871 }
872
873 async fn execute_create_table_operation(&self, _plan: &QueryPlan) -> Result<QueryResult> {
875 Ok(QueryResult {
876 rows: vec![],
877 rows_affected: 0,
878 execution_time_ms: 0,
879 metadata: super::result::QueryMetadata::default(),
880 })
881 }
882}
883
884#[cfg(feature = "experimental")]
886fn map_to_value(map: HashMap<String, Value>) -> Value {
887 Value::Map(
888 map.into_iter()
889 .map(|(k, v)| (Value::Text(k.into()), v))
890 .collect(),
891 )
892}
893
894#[cfg(test)]
895mod tests {
896 use super::*;
897 use crate::Config;
898 use std::sync::Arc;
899 use tempfile::TempDir;
900
901 async fn make_executor() -> (TempDir, QueryExecutor, Config) {
903 let temp_dir = TempDir::new().unwrap();
904 let config = Config::default();
905 let platform = Arc::new(crate::platform::Platform::new(&config).await.unwrap());
906 let storage = Arc::new(
907 crate::storage::StorageEngine::open(
908 temp_dir.path(),
909 &config,
910 platform,
911 #[cfg(feature = "state_machine")]
912 None,
913 )
914 .await
915 .unwrap(),
916 );
917 let schema = Arc::new(
918 crate::schema::SchemaManager::new(temp_dir.path())
919 .await
920 .unwrap(),
921 );
922 let executor = QueryExecutor::new(storage, schema, &config);
923 (temp_dir, executor, config)
924 }
925
926 #[tokio::test]
927 async fn test_query_executor_creation() {
928 let (_tmp, executor, config) = make_executor().await;
929 assert_eq!(
930 executor._config.query.query_parallelism,
931 config.query.query_parallelism
932 );
933 }
934
935 #[tokio::test]
936 async fn test_value_comparison() {
937 let (_tmp, executor, _) = make_executor().await;
938
939 let result = executor
940 .compare_values(&Value::Integer(10), &Value::Integer(20))
941 .unwrap();
942 assert_eq!(result, Ordering::Less);
943
944 let result = executor
945 .compare_values(
946 &Value::text("apple".to_string()),
947 &Value::text("banana".to_string()),
948 )
949 .unwrap();
950 assert_eq!(result, Ordering::Less);
951
952 assert_eq!(
955 executor
956 .compare_values(&Value::Float32(1.0), &Value::Float32(2.0))
957 .unwrap(),
958 Ordering::Less
959 );
960 assert_eq!(
962 executor
963 .compare_values(&Value::Float32(-0.0), &Value::Float32(0.0))
964 .unwrap(),
965 Ordering::Less
966 );
967 assert_eq!(
968 executor
969 .compare_values(&Value::Float32(f32::NAN), &Value::Float32(1.0))
970 .unwrap(),
971 Ordering::Greater
972 );
973 assert_eq!(
974 executor
975 .compare_values(&Value::Float32(f32::NAN), &Value::Float32(f32::NAN))
976 .unwrap(),
977 Ordering::Equal
978 );
979 }
980
981 #[tokio::test]
982 async fn test_condition_evaluation() {
983 let (_tmp, executor, _) = make_executor().await;
984
985 let mut row_values = HashMap::new();
986 row_values.insert("id".to_string(), Value::Integer(1));
987 row_values.insert("name".to_string(), Value::text("test".to_string()));
988 let row = QueryRow::with_values(RowKey::new(vec![1]), row_values);
989
990 let condition = Condition {
991 column: "id".to_string(),
992 operator: ComparisonOperator::Equal,
993 value: Value::Integer(1),
994 };
995 assert!(executor.evaluate_condition(&row, &condition).unwrap());
996
997 let condition = Condition {
998 column: "name".to_string(),
999 operator: ComparisonOperator::Like,
1000 value: Value::text("test".to_string()),
1001 };
1002 assert!(executor.evaluate_condition(&row, &condition).unwrap());
1003 }
1004
1005 #[tokio::test]
1011 async fn evaluate_condition_drops_nan_for_all_relations() {
1012 let (_tmp, executor, _) = make_executor().await;
1013
1014 let row_of = |v: Value| {
1015 let mut m = HashMap::new();
1016 m.insert("d".to_string(), v);
1017 QueryRow::with_values(RowKey::new(vec![1]), m)
1018 };
1019 let cond = |op: ComparisonOperator, v: Value| Condition {
1020 column: "d".to_string(),
1021 operator: op,
1022 value: v,
1023 };
1024
1025 for nan in [Value::Float(f64::NAN), Value::Float32(f32::NAN)] {
1026 let bound = match nan {
1027 Value::Float(_) => Value::Float(1.5),
1028 _ => Value::Float32(1.5),
1029 };
1030 let row = row_of(nan.clone());
1031 use ComparisonOperator::*;
1032 for op in [
1033 GreaterThan,
1034 GreaterThanOrEqual,
1035 LessThan,
1036 LessThanOrEqual,
1037 Equal,
1038 ] {
1039 assert!(
1040 !executor
1041 .evaluate_condition(&row, &cond(op.clone(), bound.clone()))
1042 .unwrap(),
1043 "NaN must not satisfy {:?} (issue #2257)",
1044 op
1045 );
1046 }
1047
1048 let two = match nan {
1050 Value::Float(_) => Value::Float(2.0),
1051 _ => Value::Float32(2.0),
1052 };
1053 let finite_row = row_of(two);
1054 assert!(executor
1055 .evaluate_condition(
1056 &finite_row,
1057 &cond(ComparisonOperator::GreaterThan, bound.clone())
1058 )
1059 .unwrap());
1060 assert!(!executor
1061 .evaluate_condition(&finite_row, &cond(ComparisonOperator::LessThan, bound))
1062 .unwrap());
1063 }
1064 }
1065
1066 #[tokio::test]
1073 async fn apply_filter_step_drops_nan_row_for_gt() {
1074 use super::super::planner::{ParallelizationInfo, StepType};
1075 let (_tmp, executor, _) = make_executor().await;
1076
1077 let mk = |d: f64| {
1078 let mut m = HashMap::new();
1079 m.insert("d".to_string(), Value::Float(d));
1080 m.insert("name".to_string(), Value::text("aaa".to_string()));
1081 QueryRow::with_values(RowKey::new(vec![1]), m)
1082 };
1083 let rows = vec![mk(f64::NAN), mk(2.0), mk(0.5)];
1084
1085 let step = ExecutionStep {
1086 step_type: StepType::Filter,
1087 columns: Vec::new(),
1088 conditions: vec![Condition {
1089 column: "d".to_string(),
1090 operator: ComparisonOperator::GreaterThan,
1091 value: Value::Float(1.5),
1092 }],
1093 cost: 0.0,
1094 parallelization: ParallelizationInfo {
1095 can_parallelize: false,
1096 suggested_threads: 1,
1097 partition_key: None,
1098 },
1099 };
1100
1101 let out = executor.apply_filter_step(rows, &step).unwrap();
1102 assert_eq!(out.len(), 1, "only d=2.0 survives d > 1.5");
1105 assert!(matches!(out[0].values.get("d"), Some(Value::Float(x)) if *x == 2.0));
1106 }
1107
1108 #[tokio::test]
1112 async fn sort_order_nan_greatest_unchanged() {
1113 let (_tmp, executor, _) = make_executor().await;
1114 assert_eq!(
1115 executor
1116 .compare_values(&Value::Float(f64::NAN), &Value::Float(1.5))
1117 .unwrap(),
1118 Ordering::Greater,
1119 "sort order still puts NaN last (unchanged by #2257)"
1120 );
1121 assert!(executor
1123 .predicate_ordering(&Value::Float(f64::NAN), &Value::Float(1.5))
1124 .unwrap()
1125 .is_none());
1126 assert_eq!(
1129 executor
1130 .predicate_ordering(&Value::Null, &Value::Integer(5))
1131 .unwrap(),
1132 Some(Ordering::Less),
1133 "Null < value ordering preserved (legacy semantics)"
1134 );
1135 }
1136
1137 #[tokio::test]
1147 async fn predicate_ordering_still_errs_on_cross_numeric_pairs() {
1148 let (_tmp, executor, _) = make_executor().await;
1149
1150 assert!(executor
1153 .predicate_ordering(&Value::Integer(5), &Value::BigInt(5))
1154 .is_err());
1155 assert!(executor
1158 .predicate_ordering(&Value::Integer(5), &Value::Float(5.0))
1159 .is_err());
1160
1161 let mut m = HashMap::new();
1165 m.insert("bigcol".to_string(), Value::Integer(5));
1166 let row = QueryRow::with_values(RowKey::new(vec![1]), m);
1167 let cond = Condition {
1168 column: "bigcol".to_string(),
1169 operator: ComparisonOperator::GreaterThan,
1170 value: Value::BigInt(3),
1171 };
1172 assert!(executor.evaluate_condition(&row, &cond).is_err());
1173 }
1174
1175 use super::super::planner::{IndexSelection, IndexType};
1178
1179 fn plan_with(
1181 plan_type: super::super::planner::PlanType,
1182 selected_indexes: Vec<IndexSelection>,
1183 ) -> QueryPlan {
1184 QueryPlan {
1185 plan_type,
1186 table: None,
1187 estimated_cost: 0.0,
1188 estimated_rows: 0,
1189 selected_indexes,
1190 steps: Vec::new(),
1191 hints: super::super::planner::QueryHints::default(),
1192 }
1193 }
1194
1195 fn primary_index() -> IndexSelection {
1196 IndexSelection {
1197 index_name: "PRIMARY".to_string(),
1198 columns: vec!["id".to_string()],
1199 selectivity: 0.1,
1200 index_type: IndexType::Primary,
1201 }
1202 }
1203
1204 #[test]
1207 fn test_indexes_used_point_lookup_reports_partition_index() {
1208 let plan = plan_with(
1209 super::super::planner::PlanType::PointLookup,
1210 vec![primary_index()],
1211 );
1212 assert_eq!(QueryExecutor::indexes_used_for(&plan), vec!["PRIMARY"]);
1213 }
1214
1215 #[test]
1218 fn test_indexes_used_table_scan_reports_scan_marker() {
1219 let plan = plan_with(super::super::planner::PlanType::TableScan, Vec::new());
1220 assert_eq!(QueryExecutor::indexes_used_for(&plan), vec!["scan"]);
1221 }
1222
1223 #[test]
1228 fn test_indexes_used_insert_and_ddl_table_scan_report_no_scan() {
1229 use super::super::planner::{ParallelizationInfo, StepType};
1230
1231 let insert_step = ExecutionStep {
1233 step_type: StepType::Insert,
1234 columns: Vec::new(),
1235 conditions: Vec::new(),
1236 cost: 0.0,
1237 parallelization: ParallelizationInfo {
1238 can_parallelize: false,
1239 suggested_threads: 1,
1240 partition_key: None,
1241 },
1242 };
1243 let mut insert_plan = plan_with(super::super::planner::PlanType::TableScan, Vec::new());
1244 insert_plan.steps = vec![insert_step];
1245 assert!(
1246 QueryExecutor::indexes_used_for(&insert_plan).is_empty(),
1247 "INSERT must not report a scan access path"
1248 );
1249
1250 let mut ddl_plan = plan_with(super::super::planner::PlanType::TableScan, Vec::new());
1252 ddl_plan.table = Some(TableId::new("t"));
1253 ddl_plan.estimated_rows = 0;
1254 assert!(
1255 QueryExecutor::indexes_used_for(&ddl_plan).is_empty(),
1256 "CREATE TABLE must not report a scan access path"
1257 );
1258 }
1259
1260 #[test]
1262 fn test_indexes_used_range_scan_reports_scan_marker() {
1263 let plan = plan_with(super::super::planner::PlanType::RangeScan, Vec::new());
1264 assert_eq!(QueryExecutor::indexes_used_for(&plan), vec!["scan"]);
1265 }
1266
1267 #[test]
1270 fn test_indexes_used_index_scan_primary_reports_index() {
1271 let plan = plan_with(
1272 super::super::planner::PlanType::IndexScan,
1273 vec![primary_index()],
1274 );
1275 assert_eq!(QueryExecutor::indexes_used_for(&plan), vec!["PRIMARY"]);
1276 }
1277
1278 #[test]
1282 fn test_indexes_used_index_scan_secondary_reports_scan() {
1283 let secondary = IndexSelection {
1284 index_name: "idx_name".to_string(),
1285 columns: vec!["name".to_string()],
1286 selectivity: 0.1,
1287 index_type: IndexType::Secondary,
1288 };
1289 let plan = plan_with(super::super::planner::PlanType::IndexScan, vec![secondary]);
1290 assert_eq!(QueryExecutor::indexes_used_for(&plan), vec!["scan"]);
1291 }
1292
1293 fn parallelizable_step() -> ExecutionStep {
1297 use super::super::planner::{ParallelizationInfo, StepType};
1298 ExecutionStep {
1299 step_type: StepType::Scan,
1300 columns: Vec::new(),
1301 conditions: Vec::new(),
1302 cost: 0.0,
1303 parallelization: ParallelizationInfo {
1304 can_parallelize: true,
1305 suggested_threads: 8,
1306 partition_key: None,
1307 },
1308 }
1309 }
1310
1311 #[test]
1316 fn test_parallelization_table_scan_reports_single_threaded() {
1317 let mut plan = plan_with(super::super::planner::PlanType::TableScan, Vec::new());
1318 plan.steps = vec![parallelizable_step()];
1319
1320 let info = QueryExecutor::parallelization_for(&plan)
1321 .expect("a parallelizable step should still yield metadata");
1322 assert_eq!(info.threads_used, 1);
1323 assert!(!info.effective);
1324 assert!(info.partitions.is_empty());
1325 }
1326
1327 #[test]
1329 fn test_parallelization_absent_when_no_step_parallelizes() {
1330 let plan = plan_with(super::super::planner::PlanType::TableScan, Vec::new());
1331 assert!(QueryExecutor::parallelization_for(&plan).is_none());
1332 }
1333
1334 #[test]
1337 fn test_parallelization_non_scan_reports_planner_threads() {
1338 let mut plan = plan_with(super::super::planner::PlanType::Aggregation, Vec::new());
1339 plan.steps = vec![parallelizable_step()];
1340
1341 let info = QueryExecutor::parallelization_for(&plan)
1342 .expect("a parallelizable step should yield metadata");
1343 assert_eq!(info.threads_used, 8);
1344 assert!(info.effective);
1345 }
1346
1347 #[tokio::test]
1348 async fn test_condition_to_row_key_mapping() {
1349 let (_tmp, executor, _) = make_executor().await;
1350
1351 let id_condition = Condition {
1352 column: "id".to_string(),
1353 operator: ComparisonOperator::Equal,
1354 value: Value::Integer(42),
1355 };
1356 let key = executor
1357 .condition_to_row_key(&id_condition)
1358 .expect("id condition key");
1359 assert_eq!(std::str::from_utf8(key.as_bytes()).unwrap(), "user_key_42");
1360
1361 let name_condition = Condition {
1362 column: "username".to_string(),
1363 operator: ComparisonOperator::Equal,
1364 value: Value::text("carol".to_string()),
1365 };
1366 let key = executor
1367 .condition_to_row_key(&name_condition)
1368 .expect("fallback key");
1369 assert_eq!(key.as_bytes(), b"carol");
1370 }
1371
1372 #[tokio::test]
1381 async fn offset_read_row_surfaces_data_marker_is_suppressed() {
1382 let (_tmp, executor, _) = make_executor().await;
1383 let key = RowKey::new(vec![7]);
1384 let raw = vec![0xde, 0xad, 0xbe, 0xef];
1385
1386 let live = ScanRow::RawRow(raw.clone());
1388 let row = executor
1389 .storage_data_to_query_row(live, &key)
1390 .expect("raw offset-read row must convert");
1391 assert_eq!(
1392 row.values.get("data"),
1393 Some(&Value::blob(raw.clone())),
1394 "a live offset/indexed read must surface its raw value as the \"data\" column"
1395 );
1396
1397 let marker = ScanRow::Marker(Value::Blob(raw.into()));
1401 let suppressed = executor
1402 .storage_data_to_query_row(marker, &key)
1403 .expect("marker row must still convert");
1404 assert!(
1405 !suppressed.values.contains_key("data"),
1406 "a Marker must NOT surface the raw blob (this is the suppression the fix avoids)"
1407 );
1408 }
1409
1410 fn parallelizable_table_scan_plan() -> QueryPlan {
1417 use super::super::planner::{ParallelizationInfo, PlanType, QueryHints, StepType};
1418 QueryPlan {
1419 plan_type: PlanType::TableScan,
1420 table: Some(TableId::new("t")),
1421 estimated_cost: 0.0,
1422 estimated_rows: 1,
1423 selected_indexes: Vec::new(),
1424 steps: vec![ExecutionStep {
1425 step_type: StepType::Scan,
1426 columns: Vec::new(),
1427 conditions: Vec::new(),
1428 cost: 0.0,
1429 parallelization: ParallelizationInfo {
1430 can_parallelize: true,
1431 suggested_threads: 4,
1432 partition_key: None,
1433 },
1434 }],
1435 hints: QueryHints::default(),
1436 }
1437 }
1438
1439 #[tokio::test]
1452 async fn table_scan_parallel_branch_issues_one_whole_table_pass() {
1453 let (_tmp, executor, _) = make_executor().await;
1454 crate::storage::reset_table_scan_calls();
1455
1456 let plan = parallelizable_table_scan_plan();
1457 let _ = executor.execute(&plan).await.expect("table scan executes");
1458
1459 assert_eq!(
1460 crate::storage::table_scan_call_count(),
1461 1,
1462 "the parallelizable TableScan branch must issue exactly ONE whole-table \
1463 scan pass; the retired execute_parallel_table_scan issued one per worker (4×)"
1464 );
1465 }
1466
1467 #[tokio::test]
1472 async fn streaming_scan_branch_returns_all_rows_bounded() {
1473 let (_tmp, executor, _) = make_executor().await;
1474 crate::storage::reset_table_scan_calls();
1475
1476 let plan = parallelizable_table_scan_plan();
1477 let result = executor.execute(&plan).await.expect("table scan executes");
1478
1479 assert!(result.rows.is_empty(), "empty table yields no rows");
1480 assert_eq!(
1481 crate::storage::table_scan_call_count(),
1482 1,
1483 "the streaming drain must not re-issue the scan"
1484 );
1485 }
1486}