alopex-sql 0.8.10

SQL parser components for the Alopex DB dialect
Documentation
use std::sync::{Arc, RwLock};

use alopex_core::kv::memory::MemoryKV;
use alopex_sql::catalog::MemoryCatalog;
use alopex_sql::dialect::AlopexDialect;
use alopex_sql::executor::{ExecutionResult, Executor, QueryResult};
use alopex_sql::parser::Parser;
use alopex_sql::planner::Planner;
use alopex_sql::planner::types::ResolvedType;
use alopex_sql::storage::SqlValue;

struct Harness {
    executor: Executor<MemoryKV, MemoryCatalog>,
    catalog: Arc<RwLock<MemoryCatalog>>,
}

impl Harness {
    fn new() -> Self {
        let catalog = Arc::new(RwLock::new(MemoryCatalog::new()));
        Self {
            executor: Executor::new(Arc::new(MemoryKV::new()), Arc::clone(&catalog)),
            catalog,
        }
    }

    fn query(&mut self, sql: &str) -> QueryResult {
        let statements = Parser::parse_sql(&AlopexDialect, sql).expect("parse SQL");
        statements
            .into_iter()
            .filter_map(|statement| {
                let plan = Planner::new(&*self.catalog.read().expect("catalog read"))
                    .plan(&statement)
                    .unwrap_or_else(|error| panic!("plan {statement:?}: {error:?}"));
                match self.executor.execute(plan).expect("execute SQL") {
                    ExecutionResult::Query(query) => Some(query),
                    _ => None,
                }
            })
            .last()
            .expect("query result")
    }
}

#[test]
fn date_time_interval_casts_are_typed_and_strict() {
    let mut harness = Harness::new();
    let query = harness.query(
        "SELECT CAST('2024-02-29' AS DATE), \
                CAST('23:59:59.123456' AS TIME), \
                INTERVAL '-1 month 2 days 03:04:05.000006'",
    );

    assert_eq!(
        query.rows,
        vec![vec![
            SqlValue::Date(19_782),
            SqlValue::Time(86_399_123_456),
            SqlValue::Interval {
                months: -1,
                days: 2,
                micros: 11_045_000_006,
            },
        ]]
    );

    assert_eq!(
        harness.query("SELECT TRY_CAST('2023-02-29' AS DATE)").rows,
        vec![vec![SqlValue::Null]]
    );
    assert_eq!(
        harness
            .query("SELECT TRY_CAST('12:34:56.1234567' AS TIME)")
            .rows,
        vec![vec![SqlValue::Null]]
    );
}

#[test]
fn portable_temporal_functions_cover_calendar_boundaries() {
    let mut harness = Harness::new();
    let query = harness.query(
        "SELECT MAKE_DATE(2024, 2, 29), \
                MAKE_TIME(23, 59, 59.123456), \
                MAKE_TIMESTAMP(1970, 1, 1, 0, 0, 1.5), \
                MAKE_INTERVAL(0, 1, 0, -2, 3, 4, 5.000006), \
                DATE_ADD(DATE '2024-01-31', INTERVAL '1 month'), \
                DATE_SUB(DATE '2024-03-31', INTERVAL '1 month'), \
                TO_DATE('29/02/2024', 'DD/MM/YYYY'), \
                AGE(DATE '2024-03-01', DATE '2024-02-28'), \
                DATETIME(TIMESTAMP '2024-02-28 12:00:00', '+1 day'), \
                DATE(TIMESTAMP '1970-01-01 00:00:01.5'), \
                TIME(TIMESTAMP '1970-01-01 00:00:01.5'), \
                TIME '23:00:00' + INTERVAL '1 day 2 hours'",
    );

    assert_eq!(
        query.rows,
        vec![vec![
            SqlValue::Date(19_782),
            SqlValue::Time(86_399_123_456),
            SqlValue::Timestamp(1_500_000),
            SqlValue::Interval {
                months: 1,
                days: -2,
                micros: 11_045_000_006,
            },
            SqlValue::Date(19_782),
            SqlValue::Date(19_782),
            SqlValue::Date(19_782),
            SqlValue::Interval {
                months: 0,
                days: 2,
                micros: 0,
            },
            SqlValue::Timestamp(1_709_208_000_000_000),
            SqlValue::Date(0),
            SqlValue::Time(1_500_000),
            SqlValue::Time(3_600_000_000),
        ]]
    );

    let current = harness.query("SELECT CURRENT_DATE, CURRENT_TIME");
    assert!(matches!(current.rows[0][0], SqlValue::Date(_)));
    assert!(matches!(current.rows[0][1], SqlValue::Time(_)));
    assert!(matches!(
        harness.query("SELECT AGE(DATE '2024-01-01')").rows[0][0],
        SqlValue::Interval { .. }
    ));
}

#[test]
fn temporal_storage_comparison_and_month_end_arithmetic_round_trip() {
    assert!(ResolvedType::Text.can_cast_to(&ResolvedType::Date));
    let mut harness = Harness::new();
    let query = harness.query(
        "CREATE TABLE events (d DATE, t TIME, gap INTERVAL); \
         INSERT INTO events VALUES ('2024-01-31', '12:30:00', INTERVAL '1 month'); \
         SELECT d, t, gap, d + gap, d < CAST('2024-02-01' AS DATE), \
                gap > INTERVAL '40 days' FROM events",
    );

    assert_eq!(
        query.rows,
        vec![vec![
            SqlValue::Date(19_753),
            SqlValue::Time(45_000_000_000),
            SqlValue::Interval {
                months: 1,
                days: 0,
                micros: 0,
            },
            SqlValue::Date(19_782),
            SqlValue::Boolean(true),
            SqlValue::Boolean(true),
        ]]
    );
}