quill_sql/tests/
sql_test.rs

1use crate::database::Database;
2use crate::error::QuillSQLError;
3use crate::session::SessionContext;
4use crate::storage::tuple::Tuple;
5use regex::Regex;
6use sqllogictest::{DBOutput, DefaultColumnType};
7use std::path::{Path, PathBuf};
8
9pub struct QuillSQLDB {
10    db: Database,
11    session: SessionContext,
12}
13
14impl Default for QuillSQLDB {
15    fn default() -> Self {
16        Self::new()
17    }
18}
19
20impl QuillSQLDB {
21    pub fn new() -> Self {
22        let db = Database::new_temp().unwrap();
23        let session = SessionContext::new(db.default_isolation());
24        Self { db, session }
25    }
26}
27
28fn tuples_to_sqllogictest_string(tuples: Vec<Tuple>) -> Vec<Vec<String>> {
29    let mut output = vec![];
30    for tuple in tuples.iter() {
31        let mut row = vec![];
32        for value in tuple.data.iter() {
33            row.push(format!("{value}"));
34        }
35        output.push(row);
36    }
37    output
38}
39
40impl sqllogictest::DB for QuillSQLDB {
41    type Error = QuillSQLError;
42    type ColumnType = DefaultColumnType;
43
44    fn run(&mut self, sql: &str) -> Result<DBOutput<Self::ColumnType>, Self::Error> {
45        let is_query_sql = {
46            let lower_sql = sql.trim_start().to_ascii_lowercase();
47            lower_sql.starts_with("select") || lower_sql.starts_with("explain")
48        };
49        let tuples = self.db.run_with_session(&mut self.session, sql)?;
50        if tuples.is_empty() {
51            if is_query_sql {
52                return Ok(DBOutput::Rows {
53                    types: vec![],
54                    rows: vec![],
55                });
56            } else {
57                return Ok(DBOutput::StatementComplete(0));
58            }
59        }
60        let types = vec![DefaultColumnType::Any; tuples[0].schema.column_count()];
61        let rows = tuples_to_sqllogictest_string(tuples);
62        Ok(DBOutput::Rows { types, rows })
63    }
64}
65
66#[test]
67fn sqllogictest() {
68    let test_files = read_dir_recursive("src/tests/sql_example/");
69    println!("test_files: {:?}", test_files);
70
71    for file in test_files {
72        let db = QuillSQLDB::new();
73        let mut tester = sqllogictest::Runner::new(db);
74        println!(
75            "======== start to run file {} ========",
76            file.to_str().unwrap()
77        );
78        tester.run_file(file).unwrap();
79    }
80}
81
82fn read_dir_recursive<P: AsRef<Path>>(path: P) -> Vec<PathBuf> {
83    let mut dst = vec![];
84    read_dir_recursive_impl(&mut dst, path.as_ref());
85    dst
86}
87
88fn read_dir_recursive_impl(dst: &mut Vec<PathBuf>, path: &Path) {
89    let push_file = |dst: &mut Vec<PathBuf>, path: PathBuf| {
90        // skip _xxx.slt file
91        if Regex::new(r"/_.*\.slt")
92            .unwrap()
93            .is_match(path.to_str().unwrap())
94        {
95            println!("skip file: {:?}", path);
96        } else {
97            dst.push(path);
98        }
99    };
100
101    if path.is_dir() {
102        let entries = std::fs::read_dir(path).unwrap();
103        for entry in entries {
104            let path = entry.unwrap().path();
105
106            if path.is_dir() {
107                read_dir_recursive_impl(dst, &path);
108            } else {
109                push_file(dst, path);
110            }
111        }
112    } else {
113        push_file(dst, path.to_path_buf());
114    }
115}