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
82#[allow(dead_code)]
83fn read_dir_recursive<P: AsRef<Path>>(path: P) -> Vec<PathBuf> {
84    let mut dst = vec![];
85    read_dir_recursive_impl(&mut dst, path.as_ref());
86    dst
87}
88
89fn read_dir_recursive_impl(dst: &mut Vec<PathBuf>, path: &Path) {
90    let push_file = |dst: &mut Vec<PathBuf>, path: PathBuf| {
91        // skip _xxx.slt file
92        if Regex::new(r"/_.*\.slt")
93            .unwrap()
94            .is_match(path.to_str().unwrap())
95        {
96            println!("skip file: {:?}", path);
97        } else {
98            dst.push(path);
99        }
100    };
101
102    if path.is_dir() {
103        let entries = std::fs::read_dir(path).unwrap();
104        for entry in entries {
105            let path = entry.unwrap().path();
106
107            if path.is_dir() {
108                read_dir_recursive_impl(dst, &path);
109            } else {
110                push_file(dst, path);
111            }
112        }
113    } else {
114        push_file(dst, path.to_path_buf());
115    }
116}