use crate::config::config::BehaviorConfig;
use crate::data::data_view::DataView;
use crate::data::query_engine::QueryEngine;
use crate::data::temp_table_registry::TempTableRegistry;
use crate::debug_trace::{DebugContext, DebugLevel, QueryTrace, ScopedTimer};
use crate::execution_plan::ExecutionPlan;
use anyhow::Result;
use std::sync::Arc;
use std::time::Duration;
use tracing::{debug, info};
pub struct QueryExecutionResult {
pub dataview: DataView,
pub stats: QueryStats,
pub hidden_columns: Vec<String>,
pub query: String,
pub execution_plan: Option<ExecutionPlan>,
pub debug_trace: Option<String>,
}
pub struct QueryStats {
pub row_count: usize,
pub column_count: usize,
pub execution_time: Duration,
pub query_engine_time: Duration,
}
pub struct QueryExecutionService {
case_insensitive: bool,
auto_hide_empty: bool,
date_notation: String,
behavior_config: Option<BehaviorConfig>,
}
impl QueryExecutionService {
#[must_use]
pub fn new(case_insensitive: bool, auto_hide_empty: bool) -> Self {
Self {
case_insensitive,
auto_hide_empty,
date_notation: "us".to_string(),
behavior_config: None,
}
}
#[must_use]
pub fn with_behavior_config(behavior_config: BehaviorConfig) -> Self {
let case_insensitive = behavior_config.case_insensitive_default;
let auto_hide_empty = behavior_config.hide_empty_columns;
let date_notation = behavior_config.default_date_notation.clone();
Self {
case_insensitive,
auto_hide_empty,
date_notation,
behavior_config: Some(behavior_config),
}
}
#[must_use]
pub fn with_date_notation(
case_insensitive: bool,
auto_hide_empty: bool,
date_notation: String,
) -> Self {
Self {
case_insensitive,
auto_hide_empty,
date_notation,
behavior_config: None,
}
}
pub fn execute(
&self,
query: &str,
current_dataview: Option<&DataView>,
original_source: Option<&crate::data::datatable::DataTable>,
) -> Result<QueryExecutionResult> {
self.execute_with_debug(query, current_dataview, original_source, None)
}
pub fn execute_with_temp_tables(
&self,
query: &str,
current_dataview: Option<&DataView>,
original_source: Option<&crate::data::datatable::DataTable>,
temp_tables: Option<&TempTableRegistry>,
) -> Result<QueryExecutionResult> {
self.execute_with_temp_tables_and_debug(
query,
current_dataview,
original_source,
temp_tables,
None,
)
}
pub fn execute_with_debug(
&self,
query: &str,
current_dataview: Option<&DataView>,
original_source: Option<&crate::data::datatable::DataTable>,
debug_context: Option<DebugContext>,
) -> Result<QueryExecutionResult> {
self.execute_with_temp_tables_and_debug(
query,
current_dataview,
original_source,
None,
debug_context,
)
}
pub fn execute_with_temp_tables_and_debug(
&self,
query: &str,
current_dataview: Option<&DataView>,
original_source: Option<&crate::data::datatable::DataTable>,
temp_tables: Option<&TempTableRegistry>,
debug_context: Option<DebugContext>,
) -> Result<QueryExecutionResult> {
let debug_enabled = debug_context.is_some();
let mut trace = if let Some(ctx) = debug_context {
QueryTrace::new(ctx)
} else {
QueryTrace::new(DebugContext::new(DebugLevel::Off))
};
use crate::query_plan::{CTEHoister, ExpressionLifter};
use crate::sql::recursive_parser::Parser;
let parse_timer = ScopedTimer::new();
trace.log("PARSER", "START", format!("Parsing query: {}", query));
let mut parser = if debug_enabled {
Parser::new(query).with_debug_trace(true)
} else {
Parser::new(query)
};
let mut statement = parser
.parse()
.map_err(|e| anyhow::anyhow!("Parse error: {}", e))?;
trace.log_timed(
"PARSER",
"COMPLETE",
format!(
"Parsed {} CTEs, FROM: {:?}",
statement.ctes.len(),
statement.from_table
),
parse_timer.elapsed_us(),
);
let lift_timer = ScopedTimer::new();
trace.log("OPTIMIZER", "LIFT_START", "Starting expression lifting");
let mut expr_lifter = ExpressionLifter::new();
let lifted = expr_lifter.lift_expressions(&mut statement);
if !lifted.is_empty() {
info!(
"Applied expression lifting - {} CTEs generated",
lifted.len()
);
trace.log_timed(
"OPTIMIZER",
"LIFT_COMPLETE",
format!("Generated {} lifted CTEs", lifted.len()),
lift_timer.elapsed_us(),
);
} else {
trace.log_timed(
"OPTIMIZER",
"LIFT_COMPLETE",
"No expressions needed lifting",
lift_timer.elapsed_us(),
);
}
let hoist_timer = ScopedTimer::new();
trace.log("OPTIMIZER", "HOIST_START", "Starting CTE hoisting");
let original_cte_count = statement.ctes.len();
statement = CTEHoister::hoist_ctes(statement);
trace.log_timed(
"OPTIMIZER",
"HOIST_COMPLETE",
format!(
"CTEs after hoisting: {} (was {})",
statement.ctes.len(),
original_cte_count
),
hoist_timer.elapsed_us(),
);
let uses_dual = statement
.from_table
.as_ref()
.is_some_and(|t| t.to_uppercase() == "DUAL");
let no_from_clause = statement.from_table.is_none();
let source_table = if uses_dual || no_from_clause {
info!("QueryExecutionService: Using DUAL table for expression evaluation");
crate::data::datatable::DataTable::dual()
} else if let Some(original) = original_source {
info!(
"QueryExecutionService: Using original source with {} columns: {:?}",
original.column_count(),
original.column_names()
);
debug!(
"QueryExecutionService: DEBUG - Using original source with {} columns for query",
original.column_count()
);
original.clone()
} else if let Some(view) = current_dataview {
info!(
"QueryExecutionService: WARNING - No original source, using current view's source with {} columns: {:?}",
view.source().column_count(),
view.source().column_names()
);
debug!(
"QueryExecutionService: DEBUG WARNING - No original source, using view source with {} columns",
view.source().column_count()
);
view.source().clone()
} else {
return Err(anyhow::anyhow!("No data loaded"));
};
let table_arc = Arc::new(source_table);
let query_start = std::time::Instant::now();
trace.log(
"EXECUTOR",
"START",
format!(
"Executing with {} rows, {} columns",
table_arc.row_count(),
table_arc.column_count()
),
);
let engine = if let Some(ref config) = self.behavior_config {
QueryEngine::with_behavior_config(config.clone())
} else {
QueryEngine::with_case_insensitive_and_date_notation(
self.case_insensitive,
self.date_notation.clone(),
)
};
let mut new_dataview = if temp_tables.is_some() {
engine.execute_with_temp_tables(table_arc, query, temp_tables)?
} else {
engine.execute(table_arc, query)?
};
let query_engine_time = query_start.elapsed();
trace.log_timed(
"EXECUTOR",
"COMPLETE",
format!("Query returned {} rows", new_dataview.row_count()),
query_engine_time.as_micros() as u64,
);
let mut hidden_columns = Vec::new();
if self.auto_hide_empty {
let hidden = new_dataview.hide_empty_columns();
if hidden > 0 {
info!("Auto-hidden {} empty columns after query execution", hidden);
hidden_columns = vec![format!("{} columns", hidden)];
}
}
let stats = QueryStats {
row_count: new_dataview.row_count(),
column_count: new_dataview.column_count(),
execution_time: query_start.elapsed(),
query_engine_time,
};
let debug_output = if trace.is_enabled() {
Some(trace.format_output())
} else {
None
};
Ok(QueryExecutionResult {
dataview: new_dataview,
stats,
hidden_columns,
query: query.to_string(),
execution_plan: None,
debug_trace: debug_output,
})
}
pub fn set_case_insensitive(&mut self, case_insensitive: bool) {
self.case_insensitive = case_insensitive;
}
pub fn set_auto_hide_empty(&mut self, auto_hide: bool) {
self.auto_hide_empty = auto_hide;
}
pub fn set_date_notation(&mut self, date_notation: String) {
self.date_notation = date_notation;
}
}
impl QueryExecutionResult {
#[must_use]
pub fn status_message(&self) -> String {
let hidden_msg = if self.hidden_columns.is_empty() {
String::new()
} else {
format!(" ({} auto-hidden)", self.hidden_columns.len())
};
format!(
"Query executed: {} rows, {} columns{} ({} ms)",
self.stats.row_count,
self.stats.column_count,
hidden_msg,
self.stats.execution_time.as_millis()
)
}
#[must_use]
pub fn column_names(&self) -> Vec<String> {
self.dataview.column_names()
}
#[must_use]
pub fn table_name(&self) -> String {
self.dataview.source().name.clone()
}
}