use rudb::Database;
use rudb_common::Value;
fn parquet(pattern: &str) -> String {
format!("'{}/../rudb-parquet/testdata/parts/{pattern}'", env!("CARGO_MANIFEST_DIR"))
}
fn csv(pattern: &str) -> String {
format!("'{}/../rudb-csv/testdata/parts/{pattern}'", env!("CARGO_MANIFEST_DIR"))
}
fn one(what: &str, from: &str) -> Value {
let database = Database::new();
let sql = format!("SELECT {what} FROM {from}");
database.value(&sql).unwrap_or_else(|error| panic!("{sql} failed: {error}"))
}
fn refusal(sql: &str) -> String {
let database = Database::new();
database.query(sql).unwrap_err().message().to_string()
}
#[test]
fn a_star_reads_every_file_of_one_directory_that_matches() {
assert_eq!(one("count(*)", &parquet("p*.parquet")), Value::BigInt(7));
assert_eq!(one("sum(a)", &parquet("p*.parquet")), Value::HugeInt(49));
}
#[test]
fn a_star_does_not_cross_a_directory_and_a_middle_one_reaches_exactly_one_level() {
assert_eq!(one("count(*)", &parquet("p*.parquet")), Value::BigInt(7));
assert_eq!(one("count(*)", &parquet("*/p*.parquet")), Value::BigInt(2));
assert_eq!(one("sum(a)", &parquet("*/p*.parquet")), Value::HugeInt(201));
}
#[test]
fn a_double_star_reaches_any_depth_including_none() {
assert_eq!(one("count(*)", &parquet("**/p*.parquet")), Value::BigInt(9));
assert_eq!(one("sum(a)", &parquet("**/p*.parquet")), Value::HugeInt(250));
}
#[test]
fn a_question_mark_stands_for_one_character_and_insists_on_one() {
assert_eq!(one("count(*)", &parquet("p?.parquet")), Value::BigInt(7));
let sql = format!("SELECT count(*) FROM {}", parquet("p??.parquet"));
assert!(refusal(&sql).starts_with("No files found"), "{sql}");
}
#[test]
fn a_class_stands_for_one_of_what_it_lists() {
assert_eq!(one("count(*)", &parquet("p[12].parquet")), Value::BigInt(7));
assert_eq!(one("count(*)", &parquet("p[1].parquet")), Value::BigInt(3));
assert_eq!(one("count(*)", &parquet("p[!1].parquet")), Value::BigInt(4));
}
#[test]
fn braces_are_not_a_pattern_here_because_they_are_not_one_in_duckdb_either() {
let sql = format!("SELECT count(*) FROM {}", parquet("{p1,p2}.parquet"));
assert!(refusal(&sql).starts_with("No files found"), "{sql}");
}
#[test]
fn the_files_are_read_in_the_sorted_order_of_their_whole_paths() {
let database = Database::new();
let sql = format!("SELECT a FROM read_parquet({}) LIMIT 4", parquet("**/p*.parquet"));
let result = database.query(&sql).expect("runs");
let rows: Vec<Value> = (0..result.len()).map(|row| result.value_at(row, 0)).collect();
assert_eq!(
rows,
vec![Value::Integer(100), Value::Integer(101), Value::Integer(0), Value::Integer(1)]
);
}
#[test]
fn a_pattern_that_matches_nothing_is_the_error_a_missing_file_is() {
let sql = format!("SELECT count(*) FROM read_parquet({})", parquet("nothing*.parquet"));
let message = refusal(&sql);
assert!(message.starts_with("No files found that match the pattern"), "{message}");
assert!(message.ends_with("nothing*.parquet\""), "{message}");
}
#[test]
fn a_pattern_with_an_extension_nothing_reads_is_still_a_table_that_does_not_exist() {
let sql = format!("SELECT count(*) FROM {}", parquet("*"));
assert!(refusal(&sql).starts_with("Table with name "), "{sql}");
}
#[test]
fn a_directory_where_a_table_name_goes_is_a_table_that_does_not_exist() {
let sql = format!("SELECT count(*) FROM {}", parquet("deep"));
assert!(refusal(&sql).starts_with("Table with name "), "{sql}");
}
#[test]
fn the_columns_of_a_pattern_answer_to_the_whole_of_what_was_written() {
let database = Database::new();
let sql = format!("SELECT p1.a FROM {} LIMIT 1", parquet("p*.parquet"));
assert!(database.query(&sql).is_err(), "the stem should not name a pattern");
let sql = format!("SELECT q.a FROM {} AS q LIMIT 1", parquet("p*.parquet"));
assert_eq!(database.value(&sql).expect("runs"), Value::Integer(0));
}
#[test]
fn the_first_file_decides_the_types_and_the_ones_after_it_are_cast_to_them() {
let database = Database::new();
let sql = format!("SELECT a FROM read_parquet({})", parquet("widen/*.parquet"));
let result = database.query(&sql).expect("runs");
assert_eq!(result.types()[0].to_string(), "INTEGER");
assert_eq!(result.value_at(0, 0), Value::Integer(1));
assert_eq!(result.value_at(1, 0), Value::Integer(5));
}
#[test]
fn a_column_missing_from_a_later_file_is_duckdbs_own_sentence_about_it() {
let sql = format!("SELECT * FROM read_parquet({})", parquet("odd/*.parquet"));
let message = refusal(&sql);
assert!(message.contains("schema mismatch in glob: column \"s\" was read from the original"));
assert!(message.contains("Candidate names: a"), "{message}");
assert!(message.ends_with("try setting union_by_name=True"), "{message}");
}
#[test]
fn a_pattern_reads_csv_files_the_same_way_it_reads_parquet_ones() {
assert_eq!(one("count(*)", &csv("c*.csv")), Value::BigInt(3));
assert_eq!(one("sum(a)", &csv("c*.csv")), Value::HugeInt(13));
let database = Database::new();
let sql = format!("SELECT count(*) FROM read_csv({})", csv("c?.csv"));
assert_eq!(database.value(&sql).expect("runs"), Value::BigInt(3));
}
#[test]
fn a_pattern_can_be_loaded_into_a_table_like_any_other_query() {
let database = Database::new();
let sql = format!("CREATE TABLE parts AS SELECT * FROM {}", parquet("**/p*.parquet"));
database.execute(&sql).expect("loads");
assert_eq!(database.value("SELECT count(*) FROM parts").expect("runs"), Value::BigInt(9));
assert_eq!(database.value("SELECT sum(a) FROM parts").expect("runs"), Value::HugeInt(250));
}