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, &condition.value) {
622 (Value::Text(row_text), Value::Text(pattern)) => Ok(row_text.contains(pattern)),
623 _ => Ok(false),
624 },
625 ComparisonOperator::NotLike => match (row_value, &condition.value) {
626 (Value::Text(row_text), Value::Text(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.as_bytes().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.as_bytes().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 {
775 values.insert(name, cell_value);
776 }
777 }
778 ScanRow::RawRow(bytes) => {
779 values.insert(Arc::from("data"), Value::Blob(bytes));
780 }
781 ScanRow::Marker(_) => {}
782 }
783
784 if values.is_empty() {
786 values.insert(Arc::from("id"), Value::Text(format!("{:?}", key)));
787 }
788
789 Ok(QueryRow::with_interned_values(key.clone(), values))
790 }
791
792 #[cfg(feature = "experimental")]
796 async fn execute_insert_operation(&self, plan: &QueryPlan) -> Result<QueryResult> {
797 let table_id = self
798 .require_table(plan)
799 .map_err(|_| Error::query_execution("No table specified in INSERT plan"))?;
800
801 let mut inserted_count: u64 = 0;
802
803 for step in &plan.steps {
804 if !matches!(step.step_type, StepType::Insert) {
805 continue;
806 }
807
808 let mut key_value = format!("test_key_{}", inserted_count);
811 for condition in &step.conditions {
812 if condition.column == "id" {
813 if let Value::Integer(id) = &condition.value {
814 key_value = format!("user_key_{}", id);
815 break;
816 }
817 }
818 }
819
820 let row_key = RowKey::new(key_value.into_bytes());
821
822 let mut value_map: HashMap<String, Value> = step
825 .conditions
826 .iter()
827 .map(|c| (c.column.clone(), c.value.clone()))
828 .collect();
829
830 if value_map.is_empty() {
831 value_map.insert("id".to_string(), Value::Integer(inserted_count as i32 + 1));
832 value_map.insert(
833 "name".to_string(),
834 Value::Text(format!("TestUser{}", inserted_count + 1)),
835 );
836 }
837
838 let row_value = map_to_value(value_map);
839
840 self.storage.put(table_id, row_key, row_value).await?;
841 inserted_count += 1;
842 }
843
844 if inserted_count == 0 {
847 let row_key = RowKey::new(b"default_test_key".to_vec());
848 let mut value_map = HashMap::new();
849 value_map.insert("id".to_string(), Value::Integer(1));
850 value_map.insert("name".to_string(), Value::Text("DefaultUser".to_string()));
851
852 self.storage
853 .put(table_id, row_key, map_to_value(value_map))
854 .await?;
855 inserted_count = 1;
856 }
857
858 Ok(QueryResult {
859 rows: vec![],
860 rows_affected: inserted_count,
861 execution_time_ms: 0,
862 metadata: super::result::QueryMetadata::default(),
863 })
864 }
865
866 async fn execute_create_table_operation(&self, _plan: &QueryPlan) -> Result<QueryResult> {
868 Ok(QueryResult {
869 rows: vec![],
870 rows_affected: 0,
871 execution_time_ms: 0,
872 metadata: super::result::QueryMetadata::default(),
873 })
874 }
875}
876
877#[cfg(feature = "experimental")]
879fn map_to_value(map: HashMap<String, Value>) -> Value {
880 Value::Map(map.into_iter().map(|(k, v)| (Value::Text(k), v)).collect())
881}
882
883#[cfg(test)]
884mod tests {
885 use super::*;
886 use crate::Config;
887 use std::sync::Arc;
888 use tempfile::TempDir;
889
890 async fn make_executor() -> (TempDir, QueryExecutor, Config) {
892 let temp_dir = TempDir::new().unwrap();
893 let config = Config::default();
894 let platform = Arc::new(crate::platform::Platform::new(&config).await.unwrap());
895 let storage = Arc::new(
896 crate::storage::StorageEngine::open(
897 temp_dir.path(),
898 &config,
899 platform,
900 #[cfg(feature = "state_machine")]
901 None,
902 )
903 .await
904 .unwrap(),
905 );
906 let schema = Arc::new(
907 crate::schema::SchemaManager::new(temp_dir.path())
908 .await
909 .unwrap(),
910 );
911 let executor = QueryExecutor::new(storage, schema, &config);
912 (temp_dir, executor, config)
913 }
914
915 #[tokio::test]
916 async fn test_query_executor_creation() {
917 let (_tmp, executor, config) = make_executor().await;
918 assert_eq!(
919 executor._config.query.query_parallelism,
920 config.query.query_parallelism
921 );
922 }
923
924 #[tokio::test]
925 async fn test_value_comparison() {
926 let (_tmp, executor, _) = make_executor().await;
927
928 let result = executor
929 .compare_values(&Value::Integer(10), &Value::Integer(20))
930 .unwrap();
931 assert_eq!(result, Ordering::Less);
932
933 let result = executor
934 .compare_values(
935 &Value::Text("apple".to_string()),
936 &Value::Text("banana".to_string()),
937 )
938 .unwrap();
939 assert_eq!(result, Ordering::Less);
940
941 assert_eq!(
944 executor
945 .compare_values(&Value::Float32(1.0), &Value::Float32(2.0))
946 .unwrap(),
947 Ordering::Less
948 );
949 assert_eq!(
951 executor
952 .compare_values(&Value::Float32(-0.0), &Value::Float32(0.0))
953 .unwrap(),
954 Ordering::Less
955 );
956 assert_eq!(
957 executor
958 .compare_values(&Value::Float32(f32::NAN), &Value::Float32(1.0))
959 .unwrap(),
960 Ordering::Greater
961 );
962 assert_eq!(
963 executor
964 .compare_values(&Value::Float32(f32::NAN), &Value::Float32(f32::NAN))
965 .unwrap(),
966 Ordering::Equal
967 );
968 }
969
970 #[tokio::test]
971 async fn test_condition_evaluation() {
972 let (_tmp, executor, _) = make_executor().await;
973
974 let mut row_values = HashMap::new();
975 row_values.insert("id".to_string(), Value::Integer(1));
976 row_values.insert("name".to_string(), Value::Text("test".to_string()));
977 let row = QueryRow::with_values(RowKey::new(vec![1]), row_values);
978
979 let condition = Condition {
980 column: "id".to_string(),
981 operator: ComparisonOperator::Equal,
982 value: Value::Integer(1),
983 };
984 assert!(executor.evaluate_condition(&row, &condition).unwrap());
985
986 let condition = Condition {
987 column: "name".to_string(),
988 operator: ComparisonOperator::Like,
989 value: Value::Text("test".to_string()),
990 };
991 assert!(executor.evaluate_condition(&row, &condition).unwrap());
992 }
993
994 #[tokio::test]
1000 async fn evaluate_condition_drops_nan_for_all_relations() {
1001 let (_tmp, executor, _) = make_executor().await;
1002
1003 let row_of = |v: Value| {
1004 let mut m = HashMap::new();
1005 m.insert("d".to_string(), v);
1006 QueryRow::with_values(RowKey::new(vec![1]), m)
1007 };
1008 let cond = |op: ComparisonOperator, v: Value| Condition {
1009 column: "d".to_string(),
1010 operator: op,
1011 value: v,
1012 };
1013
1014 for nan in [Value::Float(f64::NAN), Value::Float32(f32::NAN)] {
1015 let bound = match nan {
1016 Value::Float(_) => Value::Float(1.5),
1017 _ => Value::Float32(1.5),
1018 };
1019 let row = row_of(nan.clone());
1020 use ComparisonOperator::*;
1021 for op in [
1022 GreaterThan,
1023 GreaterThanOrEqual,
1024 LessThan,
1025 LessThanOrEqual,
1026 Equal,
1027 ] {
1028 assert!(
1029 !executor
1030 .evaluate_condition(&row, &cond(op.clone(), bound.clone()))
1031 .unwrap(),
1032 "NaN must not satisfy {:?} (issue #2257)",
1033 op
1034 );
1035 }
1036
1037 let two = match nan {
1039 Value::Float(_) => Value::Float(2.0),
1040 _ => Value::Float32(2.0),
1041 };
1042 let finite_row = row_of(two);
1043 assert!(executor
1044 .evaluate_condition(
1045 &finite_row,
1046 &cond(ComparisonOperator::GreaterThan, bound.clone())
1047 )
1048 .unwrap());
1049 assert!(!executor
1050 .evaluate_condition(&finite_row, &cond(ComparisonOperator::LessThan, bound))
1051 .unwrap());
1052 }
1053 }
1054
1055 #[tokio::test]
1062 async fn apply_filter_step_drops_nan_row_for_gt() {
1063 use super::super::planner::{ParallelizationInfo, StepType};
1064 let (_tmp, executor, _) = make_executor().await;
1065
1066 let mk = |d: f64| {
1067 let mut m = HashMap::new();
1068 m.insert("d".to_string(), Value::Float(d));
1069 m.insert("name".to_string(), Value::Text("aaa".to_string()));
1070 QueryRow::with_values(RowKey::new(vec![1]), m)
1071 };
1072 let rows = vec![mk(f64::NAN), mk(2.0), mk(0.5)];
1073
1074 let step = ExecutionStep {
1075 step_type: StepType::Filter,
1076 columns: Vec::new(),
1077 conditions: vec![Condition {
1078 column: "d".to_string(),
1079 operator: ComparisonOperator::GreaterThan,
1080 value: Value::Float(1.5),
1081 }],
1082 cost: 0.0,
1083 parallelization: ParallelizationInfo {
1084 can_parallelize: false,
1085 suggested_threads: 1,
1086 partition_key: None,
1087 },
1088 };
1089
1090 let out = executor.apply_filter_step(rows, &step).unwrap();
1091 assert_eq!(out.len(), 1, "only d=2.0 survives d > 1.5");
1094 assert!(matches!(out[0].values.get("d"), Some(Value::Float(x)) if *x == 2.0));
1095 }
1096
1097 #[tokio::test]
1101 async fn sort_order_nan_greatest_unchanged() {
1102 let (_tmp, executor, _) = make_executor().await;
1103 assert_eq!(
1104 executor
1105 .compare_values(&Value::Float(f64::NAN), &Value::Float(1.5))
1106 .unwrap(),
1107 Ordering::Greater,
1108 "sort order still puts NaN last (unchanged by #2257)"
1109 );
1110 assert!(executor
1112 .predicate_ordering(&Value::Float(f64::NAN), &Value::Float(1.5))
1113 .unwrap()
1114 .is_none());
1115 assert_eq!(
1118 executor
1119 .predicate_ordering(&Value::Null, &Value::Integer(5))
1120 .unwrap(),
1121 Some(Ordering::Less),
1122 "Null < value ordering preserved (legacy semantics)"
1123 );
1124 }
1125
1126 #[tokio::test]
1136 async fn predicate_ordering_still_errs_on_cross_numeric_pairs() {
1137 let (_tmp, executor, _) = make_executor().await;
1138
1139 assert!(executor
1142 .predicate_ordering(&Value::Integer(5), &Value::BigInt(5))
1143 .is_err());
1144 assert!(executor
1147 .predicate_ordering(&Value::Integer(5), &Value::Float(5.0))
1148 .is_err());
1149
1150 let mut m = HashMap::new();
1154 m.insert("bigcol".to_string(), Value::Integer(5));
1155 let row = QueryRow::with_values(RowKey::new(vec![1]), m);
1156 let cond = Condition {
1157 column: "bigcol".to_string(),
1158 operator: ComparisonOperator::GreaterThan,
1159 value: Value::BigInt(3),
1160 };
1161 assert!(executor.evaluate_condition(&row, &cond).is_err());
1162 }
1163
1164 use super::super::planner::{IndexSelection, IndexType};
1167
1168 fn plan_with(
1170 plan_type: super::super::planner::PlanType,
1171 selected_indexes: Vec<IndexSelection>,
1172 ) -> QueryPlan {
1173 QueryPlan {
1174 plan_type,
1175 table: None,
1176 estimated_cost: 0.0,
1177 estimated_rows: 0,
1178 selected_indexes,
1179 steps: Vec::new(),
1180 hints: super::super::planner::QueryHints::default(),
1181 }
1182 }
1183
1184 fn primary_index() -> IndexSelection {
1185 IndexSelection {
1186 index_name: "PRIMARY".to_string(),
1187 columns: vec!["id".to_string()],
1188 selectivity: 0.1,
1189 index_type: IndexType::Primary,
1190 }
1191 }
1192
1193 #[test]
1196 fn test_indexes_used_point_lookup_reports_partition_index() {
1197 let plan = plan_with(
1198 super::super::planner::PlanType::PointLookup,
1199 vec![primary_index()],
1200 );
1201 assert_eq!(QueryExecutor::indexes_used_for(&plan), vec!["PRIMARY"]);
1202 }
1203
1204 #[test]
1207 fn test_indexes_used_table_scan_reports_scan_marker() {
1208 let plan = plan_with(super::super::planner::PlanType::TableScan, Vec::new());
1209 assert_eq!(QueryExecutor::indexes_used_for(&plan), vec!["scan"]);
1210 }
1211
1212 #[test]
1217 fn test_indexes_used_insert_and_ddl_table_scan_report_no_scan() {
1218 use super::super::planner::{ParallelizationInfo, StepType};
1219
1220 let insert_step = ExecutionStep {
1222 step_type: StepType::Insert,
1223 columns: Vec::new(),
1224 conditions: Vec::new(),
1225 cost: 0.0,
1226 parallelization: ParallelizationInfo {
1227 can_parallelize: false,
1228 suggested_threads: 1,
1229 partition_key: None,
1230 },
1231 };
1232 let mut insert_plan = plan_with(super::super::planner::PlanType::TableScan, Vec::new());
1233 insert_plan.steps = vec![insert_step];
1234 assert!(
1235 QueryExecutor::indexes_used_for(&insert_plan).is_empty(),
1236 "INSERT must not report a scan access path"
1237 );
1238
1239 let mut ddl_plan = plan_with(super::super::planner::PlanType::TableScan, Vec::new());
1241 ddl_plan.table = Some(TableId::new("t"));
1242 ddl_plan.estimated_rows = 0;
1243 assert!(
1244 QueryExecutor::indexes_used_for(&ddl_plan).is_empty(),
1245 "CREATE TABLE must not report a scan access path"
1246 );
1247 }
1248
1249 #[test]
1251 fn test_indexes_used_range_scan_reports_scan_marker() {
1252 let plan = plan_with(super::super::planner::PlanType::RangeScan, Vec::new());
1253 assert_eq!(QueryExecutor::indexes_used_for(&plan), vec!["scan"]);
1254 }
1255
1256 #[test]
1259 fn test_indexes_used_index_scan_primary_reports_index() {
1260 let plan = plan_with(
1261 super::super::planner::PlanType::IndexScan,
1262 vec![primary_index()],
1263 );
1264 assert_eq!(QueryExecutor::indexes_used_for(&plan), vec!["PRIMARY"]);
1265 }
1266
1267 #[test]
1271 fn test_indexes_used_index_scan_secondary_reports_scan() {
1272 let secondary = IndexSelection {
1273 index_name: "idx_name".to_string(),
1274 columns: vec!["name".to_string()],
1275 selectivity: 0.1,
1276 index_type: IndexType::Secondary,
1277 };
1278 let plan = plan_with(super::super::planner::PlanType::IndexScan, vec![secondary]);
1279 assert_eq!(QueryExecutor::indexes_used_for(&plan), vec!["scan"]);
1280 }
1281
1282 fn parallelizable_step() -> ExecutionStep {
1286 use super::super::planner::{ParallelizationInfo, StepType};
1287 ExecutionStep {
1288 step_type: StepType::Scan,
1289 columns: Vec::new(),
1290 conditions: Vec::new(),
1291 cost: 0.0,
1292 parallelization: ParallelizationInfo {
1293 can_parallelize: true,
1294 suggested_threads: 8,
1295 partition_key: None,
1296 },
1297 }
1298 }
1299
1300 #[test]
1305 fn test_parallelization_table_scan_reports_single_threaded() {
1306 let mut plan = plan_with(super::super::planner::PlanType::TableScan, Vec::new());
1307 plan.steps = vec![parallelizable_step()];
1308
1309 let info = QueryExecutor::parallelization_for(&plan)
1310 .expect("a parallelizable step should still yield metadata");
1311 assert_eq!(info.threads_used, 1);
1312 assert!(!info.effective);
1313 assert!(info.partitions.is_empty());
1314 }
1315
1316 #[test]
1318 fn test_parallelization_absent_when_no_step_parallelizes() {
1319 let plan = plan_with(super::super::planner::PlanType::TableScan, Vec::new());
1320 assert!(QueryExecutor::parallelization_for(&plan).is_none());
1321 }
1322
1323 #[test]
1326 fn test_parallelization_non_scan_reports_planner_threads() {
1327 let mut plan = plan_with(super::super::planner::PlanType::Aggregation, Vec::new());
1328 plan.steps = vec![parallelizable_step()];
1329
1330 let info = QueryExecutor::parallelization_for(&plan)
1331 .expect("a parallelizable step should yield metadata");
1332 assert_eq!(info.threads_used, 8);
1333 assert!(info.effective);
1334 }
1335
1336 #[tokio::test]
1337 async fn test_condition_to_row_key_mapping() {
1338 let (_tmp, executor, _) = make_executor().await;
1339
1340 let id_condition = Condition {
1341 column: "id".to_string(),
1342 operator: ComparisonOperator::Equal,
1343 value: Value::Integer(42),
1344 };
1345 let key = executor
1346 .condition_to_row_key(&id_condition)
1347 .expect("id condition key");
1348 assert_eq!(std::str::from_utf8(key.as_bytes()).unwrap(), "user_key_42");
1349
1350 let name_condition = Condition {
1351 column: "username".to_string(),
1352 operator: ComparisonOperator::Equal,
1353 value: Value::Text("carol".to_string()),
1354 };
1355 let key = executor
1356 .condition_to_row_key(&name_condition)
1357 .expect("fallback key");
1358 assert_eq!(key.as_bytes(), b"carol");
1359 }
1360
1361 #[tokio::test]
1370 async fn offset_read_row_surfaces_data_marker_is_suppressed() {
1371 let (_tmp, executor, _) = make_executor().await;
1372 let key = RowKey::new(vec![7]);
1373 let raw = vec![0xde, 0xad, 0xbe, 0xef];
1374
1375 let live = ScanRow::RawRow(raw.clone());
1377 let row = executor
1378 .storage_data_to_query_row(live, &key)
1379 .expect("raw offset-read row must convert");
1380 assert_eq!(
1381 row.values.get("data"),
1382 Some(&Value::Blob(raw.clone())),
1383 "a live offset/indexed read must surface its raw value as the \"data\" column"
1384 );
1385
1386 let marker = ScanRow::Marker(Value::Blob(raw));
1390 let suppressed = executor
1391 .storage_data_to_query_row(marker, &key)
1392 .expect("marker row must still convert");
1393 assert!(
1394 !suppressed.values.contains_key("data"),
1395 "a Marker must NOT surface the raw blob (this is the suppression the fix avoids)"
1396 );
1397 }
1398
1399 fn parallelizable_table_scan_plan() -> QueryPlan {
1406 use super::super::planner::{ParallelizationInfo, PlanType, QueryHints, StepType};
1407 QueryPlan {
1408 plan_type: PlanType::TableScan,
1409 table: Some(TableId::new("t")),
1410 estimated_cost: 0.0,
1411 estimated_rows: 1,
1412 selected_indexes: Vec::new(),
1413 steps: vec![ExecutionStep {
1414 step_type: StepType::Scan,
1415 columns: Vec::new(),
1416 conditions: Vec::new(),
1417 cost: 0.0,
1418 parallelization: ParallelizationInfo {
1419 can_parallelize: true,
1420 suggested_threads: 4,
1421 partition_key: None,
1422 },
1423 }],
1424 hints: QueryHints::default(),
1425 }
1426 }
1427
1428 #[tokio::test]
1441 async fn table_scan_parallel_branch_issues_one_whole_table_pass() {
1442 let (_tmp, executor, _) = make_executor().await;
1443 crate::storage::reset_table_scan_calls();
1444
1445 let plan = parallelizable_table_scan_plan();
1446 let _ = executor.execute(&plan).await.expect("table scan executes");
1447
1448 assert_eq!(
1449 crate::storage::table_scan_call_count(),
1450 1,
1451 "the parallelizable TableScan branch must issue exactly ONE whole-table \
1452 scan pass; the retired execute_parallel_table_scan issued one per worker (4×)"
1453 );
1454 }
1455
1456 #[tokio::test]
1461 async fn streaming_scan_branch_returns_all_rows_bounded() {
1462 let (_tmp, executor, _) = make_executor().await;
1463 crate::storage::reset_table_scan_calls();
1464
1465 let plan = parallelizable_table_scan_plan();
1466 let result = executor.execute(&plan).await.expect("table scan executes");
1467
1468 assert!(result.rows.is_empty(), "empty table yields no rows");
1469 assert_eq!(
1470 crate::storage::table_scan_call_count(),
1471 1,
1472 "the streaming drain must not re-issue the scan"
1473 );
1474 }
1475}