prqlc 0.13.11

PRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement.
Documentation
#![cfg(not(target_family = "wasm"))]
use std::fs;
use std::path::Path;

use insta::assert_debug_snapshot;
use insta::{assert_snapshot, with_settings};
use prqlc::sql::Dialect;
use prqlc::sql::SupportLevel;
use prqlc::{Options, Target};
use test_each_file::test_each_path;

mod lex {
    use super::*;

    test_each_path! { in "./prqlc/prqlc/tests/integration/queries" => run }

    fn run(prql_path: &Path) {
        let test_name = prql_path.file_stem().unwrap().to_str().unwrap();
        let prql = fs::read_to_string(prql_path).unwrap();

        let tokens = prqlc_parser::lexer::lex_source(&prql).unwrap();

        with_settings!({ input_file => prql_path }, {
            assert_debug_snapshot!(test_name, tokens)
        });
    }
}

mod compile {
    use super::*;

    // If this is giving compilation errors saying `expected identifier, found keyword`,
    // rename the filenames in queries to something that's not a keyword in Rust.
    test_each_path! { in "./prqlc/prqlc/tests/integration/queries" => run }

    fn run(prql_path: &Path) {
        let test_name = prql_path.file_stem().unwrap().to_str().unwrap();
        let prql = fs::read_to_string(prql_path).unwrap();
        if prql.contains("generic:skip") {
            return;
        }

        let target = Target::Sql(Some(Dialect::Generic));
        let options = Options::default().no_signature().with_target(target);

        let sql = prqlc::compile(&prql, &options).unwrap();

        with_settings!({ input_file => prql_path }, {
            assert_snapshot!(test_name, &sql, &prql)
        });
    }
}

fn should_run_query(dialect: Dialect, prql: &str) -> bool {
    let dialect_str = dialect.to_string().to_lowercase();

    match dialect.support_level() {
        SupportLevel::Supported => !prql.contains(&format!("{dialect_str}:skip")),
        SupportLevel::Unsupported => prql.contains(&format!("{dialect_str}:test")),
        SupportLevel::Nascent => false,
    }
}

mod compileall {
    use super::*;
    use similar::TextDiff;
    use strum::IntoEnumIterator;

    test_each_path! { in "./prqlc/prqlc/tests/integration/queries" => run }

    fn run(prql_path: &Path) {
        let test_name = prql_path.file_stem().unwrap().to_str().unwrap();
        let prql = fs::read_to_string(prql_path).unwrap();
        if prql.contains("generic:skip") {
            return;
        }

        // first compile with the generic dialect
        let target = Target::Sql(Some(Dialect::Generic));
        let options = Options::default().no_signature().with_target(target);

        let generic_sql = prqlc::compile(&prql, &options).unwrap();

        // next compile with each dialect
        let mut diffsnap = "".to_owned();
        for dialect in Dialect::iter() {
            if !should_run_query(dialect, &prql) {
                continue;
            }

            let dialect_target = Target::Sql(Some(dialect));
            let dialect_options = Options::default()
                .no_signature()
                .with_target(dialect_target);

            let dialect_sql = prqlc::compile(&prql, &dialect_options).unwrap();

            let diff = TextDiff::from_lines(&generic_sql, &dialect_sql);
            diffsnap = format!(
                "{diffsnap}\n{}",
                diff.unified_diff()
                    .context_radius(10)
                    .header("generic", &dialect.to_string())
            );
        }
        with_settings!({ input_file => prql_path }, {
            assert_snapshot!(test_name, diffsnap, &prql)
        });
    }
}

mod fmt {
    use super::*;

    test_each_path! { in "./prqlc/prqlc/tests/integration/queries" => run }

    fn run(prql_path: &Path) {
        let test_name = prql_path.file_stem().unwrap().to_str().unwrap();
        let prql = fs::read_to_string(prql_path).unwrap();

        let pl = prqlc::prql_to_pl(&prql).unwrap();
        let formatted = prqlc::pl_to_prql(&pl).unwrap();

        with_settings!({ input_file => prql_path }, {
            assert_snapshot!(test_name, &formatted, &prql)
        });

        // Check the formatted queries can still compile
        prqlc::prql_to_pl(&formatted).unwrap();
    }
}

#[cfg(feature = "serde_yaml")]
mod debug_lineage {
    use super::*;

    test_each_path! { in "./prqlc/prqlc/tests/integration/queries" => run }

    fn run(prql_path: &Path) {
        let test_name = prql_path.file_stem().unwrap().to_str().unwrap();
        let prql = fs::read_to_string(prql_path).unwrap();

        let pl = prqlc::prql_to_pl(&prql).unwrap();
        let fc = prqlc::internal::pl_to_lineage(pl).unwrap();

        let lineage = serde_yaml::to_string(&fc).unwrap();

        with_settings!({ input_file => prql_path }, {
            assert_snapshot!(test_name, &lineage, &prql)
        });
    }
}

#[cfg(any(feature = "test-dbs", feature = "test-dbs-external"))]
mod results {

    use itertools::Itertools;

    use super::*;
    use crate::dbs::{batch_to_csv, runners};

    test_each_path! { in "./prqlc/prqlc/tests/integration/queries" => run }

    fn run(prql_path: &Path) {
        let test_name = prql_path.file_stem().unwrap().to_str().unwrap();
        let prql = fs::read_to_string(prql_path).unwrap();

        // for each of the runners, get the query
        let results: Vec<(Dialect, String)> = runners()
            .iter()
            .filter_map(|runner| {
                let mut runner = runner.lock().unwrap();
                let dialect = runner.dialect();
                if !should_run_query(dialect, &prql) {
                    return None;
                }

                eprintln!("Executing {test_name} for {dialect}");

                match runner.query(&prql) {
                    Ok(batch) => {
                        let csv = batch_to_csv(batch);
                        Some(Ok((dialect, csv)))
                    }
                    Err(e) => Some(Err(e)),
                }
            })
            .try_collect()
            .unwrap();

        if results.is_empty() {
            panic!("No valid dialects to run the query at {prql_path:#?} against");
        }

        // similar to `insta::allow_duplicates!`, but with reporting of which two cases are
        // not matching.
        let ((first_dialect, first_text), others) = results.split_first().unwrap();

        with_settings!({ input_file => prql_path }, {
            assert_snapshot!(test_name, first_text, &prql)
        });

        for (dialect, text) in others {
            similar_asserts::assert_eq!(
                first_text,
                text,
                "{} {} {}",
                test_name,
                first_dialect,
                dialect
            );
        }
    }
}