sql-cli 1.73.1

SQL query tool for CSV/JSON with both interactive TUI and non-interactive CLI modes - perfect for exploration and automation
Documentation
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum DebugLevel {
    Off,
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}

impl DebugLevel {
    pub fn enabled(&self, level: DebugLevel) -> bool {
        *self >= level
    }
}

#[derive(Clone)]
pub struct DebugContext {
    level: DebugLevel,
    component_filter: Option<Vec<String>>,
}

impl Default for DebugContext {
    fn default() -> Self {
        Self::new(DebugLevel::Off)
    }
}

impl DebugContext {
    pub fn new(level: DebugLevel) -> Self {
        Self {
            level,
            component_filter: None,
        }
    }

    pub fn with_filter(mut self, components: Vec<String>) -> Self {
        self.component_filter = Some(components);
        self
    }

    #[inline(always)]
    pub fn is_enabled(&self, level: DebugLevel) -> bool {
        self.level.enabled(level)
    }

    #[inline(always)]
    pub fn should_log(&self, component: &str, level: DebugLevel) -> bool {
        if !self.is_enabled(level) {
            return false;
        }

        if let Some(ref filter) = self.component_filter {
            filter.iter().any(|c| component.starts_with(c))
        } else {
            true
        }
    }
}

pub struct QueryTrace {
    context: DebugContext,
    traces: Vec<TraceEntry>,
}

#[derive(Clone)]
pub struct TraceEntry {
    pub component: String,
    pub phase: String,
    pub message: String,
    pub timing_us: Option<u64>,
}

impl QueryTrace {
    pub fn new(context: DebugContext) -> Self {
        Self {
            context,
            traces: Vec::new(),
        }
    }

    #[inline(always)]
    pub fn is_enabled(&self) -> bool {
        self.context.level != DebugLevel::Off
    }

    #[inline(always)]
    pub fn log<C, P, M>(&mut self, component: C, phase: P, message: M)
    where
        C: Into<String>,
        P: Into<String>,
        M: Into<String>,
    {
        if self.is_enabled() {
            let component = component.into();
            if self.context.should_log(&component, DebugLevel::Debug) {
                self.traces.push(TraceEntry {
                    component,
                    phase: phase.into(),
                    message: message.into(),
                    timing_us: None,
                });
            }
        }
    }

    #[inline(always)]
    pub fn log_timed<C, P, M>(&mut self, component: C, phase: P, message: M, timing_us: u64)
    where
        C: Into<String>,
        P: Into<String>,
        M: Into<String>,
    {
        if self.is_enabled() {
            let component = component.into();
            if self.context.should_log(&component, DebugLevel::Debug) {
                self.traces.push(TraceEntry {
                    component,
                    phase: phase.into(),
                    message: message.into(),
                    timing_us: Some(timing_us),
                });
            }
        }
    }

    pub fn format_output(&self) -> String {
        if self.traces.is_empty() {
            return String::new();
        }

        let mut output = String::from("\n=== QUERY EXECUTION TRACE ===\n");
        let mut current_component = String::new();

        for entry in &self.traces {
            if entry.component != current_component {
                output.push_str(&format!("\n[{}]\n", entry.component));
                current_component = entry.component.clone();
            }

            if let Some(timing) = entry.timing_us {
                output.push_str(&format!(
                    "  {}: {} ({:.3}ms)\n",
                    entry.phase,
                    entry.message,
                    timing as f64 / 1000.0
                ));
            } else {
                output.push_str(&format!("  {}: {}\n", entry.phase, entry.message));
            }
        }

        output.push_str("\n=== END TRACE ===\n");
        output
    }

    pub fn get_traces(&self) -> &[TraceEntry] {
        &self.traces
    }
}

#[macro_export]
macro_rules! trace_log {
    ($trace:expr, $component:expr, $phase:expr, $($arg:tt)*) => {
        if $trace.is_enabled() {
            $trace.log($component, $phase, format!($($arg)*));
        }
    };
}

#[macro_export]
macro_rules! trace_timed {
    ($trace:expr, $component:expr, $phase:expr, $timing:expr, $($arg:tt)*) => {
        if $trace.is_enabled() {
            $trace.log_timed($component, $phase, format!($($arg)*), $timing);
        }
    };
}

pub struct ScopedTimer {
    start: std::time::Instant,
}

impl ScopedTimer {
    pub fn new() -> Self {
        Self {
            start: std::time::Instant::now(),
        }
    }

    pub fn elapsed_us(&self) -> u64 {
        self.start.elapsed().as_micros() as u64
    }

    pub fn elapsed_ms(&self) -> f64 {
        self.start.elapsed().as_secs_f64() * 1000.0
    }
}