use std::time::Duration;
use rudb_common::{Field, LogicalType, Span, Value, days_from_civil};
use crate::{Config, Database, VECTOR_SIZE, arrow};
fn database() -> Database {
let db = Database::new();
db.create_table(
"t",
vec![Field::new("x", LogicalType::Integer), Field::new("s", LogicalType::Varchar)],
)
.unwrap();
db.append(
"t",
&[
vec![Value::Integer(3), Value::Varchar("a".to_string())],
vec![Value::Integer(1), Value::Null],
vec![Value::Integer(2), Value::Varchar("c".to_string())],
vec![Value::Integer(1), Value::Varchar("a".to_string())],
],
)
.unwrap();
db.create_table("empty", vec![Field::new("x", LogicalType::Integer)]).unwrap();
db
}
fn rows(db: &Database, sql: &str) -> Vec<Vec<Value>> {
db.query(sql).unwrap().rows().collect()
}
#[test]
fn non_recursive_ctes_run_as_inlined_queries() {
let db = Database::new();
assert_eq!(
rows(
&db,
"WITH a AS (SELECT 2 AS x), b(y) AS NOT MATERIALIZED (SELECT x + 1 FROM a) SELECT y FROM b",
),
vec![vec![Value::Integer(3)]]
);
assert_eq!(
rows(&db, "WITH t AS (SELECT 1 AS x) SELECT left_t.x + right_t.x FROM t left_t, t right_t",),
vec![vec![Value::Integer(2)]]
);
}
fn first_key(row: &[Value]) -> i64 {
match row[0] {
Value::BigInt(key) => key,
ref other => panic!("the key of this query is a BIGINT, not {other:?}"),
}
}
fn failure(db: &Database, sql: &str) -> String {
db.query(sql).unwrap_err().message().to_string()
}
fn integer(value: i32) -> Value {
Value::Integer(value)
}
fn text(value: &str) -> Value {
Value::Varchar(value.to_string())
}
fn list(values: &[i32]) -> Value {
Value::List {
element: LogicalType::Integer,
values: values.iter().map(|&v| Value::Integer(v)).collect(),
}
}
#[test]
fn a_star_reads_every_column_in_order() {
let db = database();
assert_eq!(
rows(&db, "SELECT * FROM t"),
vec![
vec![integer(3), text("a")],
vec![integer(1), Value::Null],
vec![integer(2), text("c")],
vec![integer(1), text("a")],
]
);
}
#[test]
fn a_filter_keeps_the_rows_the_predicate_is_true_for() {
let db = database();
assert_eq!(rows(&db, "SELECT x FROM t WHERE x > 1"), vec![vec![integer(3)], vec![integer(2)]]);
}
#[test]
fn a_null_predicate_drops_the_row() {
let db = database();
assert_eq!(rows(&db, "SELECT x FROM t WHERE s = 'c'"), vec![vec![integer(2)]]);
assert_eq!(
rows(&db, "SELECT x FROM t WHERE s <> 'c'"),
vec![vec![integer(3)], vec![integer(1)]]
);
}
#[test]
fn a_query_with_no_table_still_runs() {
let db = database();
assert_eq!(rows(&db, "SELECT 1 + 1"), vec![vec![integer(2)]]);
}
#[test]
fn an_expression_takes_the_name_it_was_aliased_to() {
let db = database();
let result = db.query("SELECT x + 10 AS bumped FROM t WHERE x = 2").unwrap();
assert_eq!(result.names(), ["bumped"]);
assert_eq!(result.types(), [LogicalType::Integer]);
assert_eq!(result.value_at(0, 0), integer(12));
}
#[test]
fn an_aggregate_over_no_rows_is_zero_and_null() {
let db = database();
assert_eq!(rows(&db, "SELECT count(*) FROM empty"), vec![vec![Value::BigInt(0)]]);
assert_eq!(rows(&db, "SELECT sum(x) FROM empty"), vec![vec![Value::Null]]);
}
#[test]
fn count_star_and_count_of_a_column_disagree_about_nulls() {
let db = database();
assert_eq!(rows(&db, "SELECT count(*) FROM t"), vec![vec![Value::BigInt(4)]]);
assert_eq!(rows(&db, "SELECT count(s) FROM t"), vec![vec![Value::BigInt(3)]]);
}
#[test]
fn count_with_no_arguments_counts_rows_the_way_a_star_does() {
let db = database();
assert_eq!(rows(&db, "SELECT count() FROM t"), vec![vec![Value::BigInt(4)]]);
assert_eq!(rows(&db, "SELECT COUNT() FROM t"), vec![vec![Value::BigInt(4)]]);
assert_eq!(db.query("SELECT count() FROM t").unwrap().names(), ["count_star()"]);
assert_eq!(rows(&db, "SELECT count() OVER () FROM t LIMIT 1"), vec![vec![Value::BigInt(4)]]);
assert_eq!(db.query("SELECT count() OVER () FROM t").unwrap().names(), ["count() OVER ()"]);
}
#[test]
fn a_filter_narrows_one_aggregate_and_leaves_the_rest_of_the_query_alone() {
let db = scripted(&[
"CREATE TABLE f (k INTEGER, v INTEGER)",
"INSERT INTO f VALUES (1, 1), (1, 2), (1, NULL), (2, 3), (2, 4), (2, NULL)",
]);
assert_eq!(
rows(&db, "SELECT sum(v) FILTER (WHERE v > 1) FROM f"),
vec![vec![Value::HugeInt(9)]]
);
assert_eq!(
rows(&db, "SELECT count(*) FILTER (WHERE v > 1) FROM f"),
vec![vec![Value::BigInt(3)]]
);
assert_eq!(
rows(&db, "SELECT count(v) FILTER (WHERE v IS NULL) FROM f"),
vec![vec![Value::BigInt(0)]]
);
assert_eq!(
rows(&db, "SELECT count(*) FILTER (WHERE v IS NULL) FROM f"),
vec![vec![Value::BigInt(2)]]
);
assert_eq!(
rows(&db, "SELECT count(*) FILTER (WHERE v > 1), count(*) FROM f"),
vec![vec![Value::BigInt(3), Value::BigInt(6)]]
);
assert_eq!(
rows(
&db,
"SELECT k, count(*) FILTER (WHERE v > 2), sum(v) FILTER (WHERE v > 2) \
FROM f GROUP BY k ORDER BY k"
),
vec![
vec![integer(1), Value::BigInt(0), Value::Null],
vec![integer(2), Value::BigInt(2), Value::HugeInt(7)],
]
);
assert_eq!(rows(&db, "SELECT sum(v) FILTER (WHERE false) FROM f"), vec![vec![Value::Null]]);
assert_eq!(
rows(&db, "SELECT sum(DISTINCT v) FILTER (WHERE v > 1) FROM f"),
vec![vec![Value::HugeInt(9)]]
);
assert_eq!(rows(&db, "SELECT sum(v) FILTER (WHERE v) FROM f"), vec![vec![Value::HugeInt(10)]]);
}
#[test]
fn a_group_by_produces_one_row_per_distinct_value() {
let db = database();
let mut answer = rows(&db, "SELECT x, count(*) FROM t GROUP BY x");
answer.sort_by_key(|row| format!("{:?}", row[0]));
assert_eq!(
answer,
vec![
vec![integer(1), Value::BigInt(2)],
vec![integer(2), Value::BigInt(1)],
vec![integer(3), Value::BigInt(1)],
]
);
}
#[test]
fn grouped_smallint_sum_and_avg_keep_nulls() {
let db = scripted(&[
"CREATE TABLE numbers (k INTEGER, a SMALLINT, b SMALLINT)",
"INSERT INTO numbers VALUES (1, 1, 2), (1, NULL, 4), (1, 2, NULL), \
(2, NULL, NULL), (2, NULL, NULL)",
]);
assert_eq!(
rows(&db, "SELECT k, COUNT(*), SUM(a), AVG(b) FROM numbers GROUP BY k ORDER BY k"),
vec![
vec![integer(1), Value::BigInt(3), Value::HugeInt(3), Value::Double(3.0)],
vec![integer(2), Value::BigInt(2), Value::Null, Value::Null],
]
);
}
#[test]
fn an_unordered_limit_keeps_only_the_groups_it_can_return() {
let db = database();
let full =
db.query("SELECT range % 10000 AS k, count(*) FROM range(100000) GROUP BY k").unwrap();
let full_peak = full.metrics().unwrap().resource.peak_bytes;
drop(full);
let limited = db
.query("SELECT range % 10000 AS k, count(*) FROM range(100000) GROUP BY k LIMIT 10")
.unwrap();
assert_eq!(
limited.rows().collect::<Vec<_>>(),
(0..10).map(|k| vec![Value::BigInt(k), Value::BigInt(10)]).collect::<Vec<_>>()
);
let limited_peak = limited.metrics().unwrap().resource.peak_bytes;
assert!(limited_peak * 10 < full_peak, "{limited_peak} was not far below {full_peak}");
}
#[test]
fn a_having_clause_filters_groups_rather_than_rows() {
let db = database();
assert_eq!(
rows(&db, "SELECT x, count(*) FROM t GROUP BY x HAVING count(*) > 1"),
vec![vec![integer(1), Value::BigInt(2)]]
);
}
#[test]
fn order_by_puts_nulls_where_the_clause_says() {
let db = database();
assert_eq!(
rows(&db, "SELECT s FROM t ORDER BY s NULLS LAST"),
vec![vec![text("a")], vec![text("a")], vec![text("c")], vec![Value::Null]]
);
assert_eq!(
rows(&db, "SELECT s FROM t ORDER BY s NULLS FIRST"),
vec![vec![Value::Null], vec![text("a")], vec![text("a")], vec![text("c")]]
);
}
#[test]
fn order_by_and_limit_and_offset_compose() {
let db = database();
assert_eq!(
rows(&db, "SELECT x FROM t ORDER BY x DESC LIMIT 2 OFFSET 1"),
vec![vec![integer(2)], vec![integer(1)]]
);
}
#[test]
fn an_order_by_with_a_limit_answers_what_the_sort_would_have() {
let db = Database::new();
db.create_table("many", vec![Field::new("x", LogicalType::Integer)]).unwrap();
let counted: Vec<Vec<Value>> = (0..5000).rev().map(|x| vec![Value::Integer(x)]).collect();
db.append("many", &counted).unwrap();
let wanted: Vec<Vec<Value>> = (7..17).map(|x| vec![Value::Integer(x)]).collect();
assert_eq!(rows(&db, "SELECT x FROM many ORDER BY x LIMIT 10 OFFSET 7"), wanted);
db.execute("SET disabled_optimizers = 'top_n'").unwrap();
assert_eq!(rows(&db, "SELECT x FROM many ORDER BY x LIMIT 10 OFFSET 7"), wanted);
}
#[test]
fn grouping_a_string_column_counts_each_string_once_however_the_rows_are_ordered() {
let db = Database::new();
db.create_table("words", vec![Field::new("s", LogicalType::Varchar)]).unwrap();
let shapes = [Some(""), Some("a"), None, Some("a longer one"), None, Some("ab")];
let written: Vec<Vec<Value>> = (0..6000)
.map(|at| match shapes[at % shapes.len()] {
Some(word) => vec![Value::Varchar(word.to_string())],
None => vec![Value::Null],
})
.collect();
db.append("words", &written).unwrap();
let mut answer = rows(&db, "SELECT s, count(*) FROM words GROUP BY s");
answer.sort_by_key(|row| format!("{:?}", row[0]));
assert_eq!(
answer,
vec![
vec![Value::Null, Value::BigInt(2000)],
vec![text(""), Value::BigInt(1000)],
vec![text("a longer one"), Value::BigInt(1000)],
vec![text("a"), Value::BigInt(1000)],
vec![text("ab"), Value::BigInt(1000)],
]
);
assert_eq!(
rows(&db, "SELECT count(DISTINCT s) FROM words"),
vec![vec![Value::BigInt(4)]],
"a distinct inside an aggregate keys on the same buffer"
);
assert_eq!(
rows(&db, "SELECT count(*) FROM (SELECT DISTINCT s FROM words)"),
vec![vec![Value::BigInt(5)]],
"and duplicate elimination counts the null group as a row where count(DISTINCT) does not"
);
}
#[test]
fn grouping_a_string_that_came_out_of_a_cross_product_still_counts_each_string_once() {
let db = Database::new();
db.execute("CREATE TABLE w AS SELECT i AS k, 'word' || (i % 3) AS s FROM range(9) t(i)")
.unwrap();
db.execute("CREATE TABLE n AS SELECT i AS k FROM range(9) t(i)").unwrap();
let mut answer = rows(&db, "SELECT s, count(*) FROM w, n WHERE w.k = n.k GROUP BY s");
answer.sort_by_key(|row| format!("{:?}", row[0]));
assert_eq!(
answer,
vec![
vec![text("word0"), Value::BigInt(3)],
vec![text("word1"), Value::BigInt(3)],
vec![text("word2"), Value::BigInt(3)],
]
);
assert_eq!(
rows(&db, "SELECT count(*) FROM (SELECT DISTINCT s FROM w, n WHERE w.k = n.k)"),
vec![vec![Value::BigInt(3)]],
"and duplicate elimination keys on the same table"
);
}
#[test]
fn distinct_collapses_equal_rows() {
let db = database();
let mut answer = rows(&db, "SELECT DISTINCT x FROM t");
answer.sort_by_key(|row| format!("{:?}", row[0]));
assert_eq!(answer, vec![vec![integer(1)], vec![integer(2)], vec![integer(3)]]);
}
#[test]
fn counting_a_distinct_counts_the_distinct_values() {
let db = database();
assert_eq!(
rows(&db, "SELECT count(*) FROM (SELECT DISTINCT x FROM t)"),
vec![vec![Value::BigInt(3)]]
);
assert_eq!(
rows(&db, "SELECT count(*) FROM (SELECT DISTINCT x, s FROM t)"),
vec![vec![Value::BigInt(4)]]
);
}
#[test]
fn a_join_matches_on_its_condition() {
let db = database();
assert_eq!(
rows(&db, "SELECT a.x, b.s FROM t AS a JOIN t AS b ON a.x = b.x WHERE a.x = 2"),
vec![vec![integer(2), text("c")]]
);
}
#[test]
fn a_left_join_keeps_the_left_row_and_pads_the_right() {
let db = database();
assert_eq!(
rows(&db, "SELECT t.x, e.x FROM t LEFT JOIN empty AS e ON t.x = e.x WHERE t.x = 3"),
vec![vec![integer(3), Value::Null]]
);
}
#[test]
fn an_equality_is_looked_up_and_answers_what_the_loop_answers() {
let db = Database::new();
db.execute("CREATE TABLE l (k INTEGER, j INTEGER, tag VARCHAR)").unwrap();
db.execute(
"INSERT INTO l VALUES (1, 1, 'one'), (1, 1, 'one again'), (2, 2, 'two'), \
(NULL, 1, 'null key'), (3, NULL, 'null second'), (4, 4, 'left only')",
)
.unwrap();
db.execute("CREATE TABLE r (k INTEGER, j INTEGER, tag VARCHAR)").unwrap();
db.execute(
"INSERT INTO r VALUES (1, 1, 'a'), (1, 1, 'b'), (1, 1, 'c'), (2, 9, 'wrong second'), \
(NULL, 1, 'null key'), (5, 5, 'right only')",
)
.unwrap();
for kind in ["INNER", "LEFT", "RIGHT", "FULL"] {
for on in ["l.k = r.k", "l.k = r.k AND l.j = r.j"] {
let listing = |condition: &str| {
let sql = format!(
"SELECT l.tag, r.tag FROM l {kind} JOIN r ON {condition} \
ORDER BY l.tag NULLS FIRST, r.tag NULLS FIRST"
);
rows(&db, &sql)
};
let looked_up = listing(on);
let looped = listing(&format!("{on} AND l.k >= r.k"));
assert_eq!(looked_up, looped, "{kind} JOIN ON {on}");
}
}
}
#[test]
fn a_null_safe_equality_is_looked_up_and_answers_what_the_loop_answers() {
let db = Database::new();
db.execute("CREATE TABLE l (k INTEGER, j INTEGER, tag VARCHAR)").unwrap();
db.execute(
"INSERT INTO l VALUES (1, 1, 'one'), (1, 1, 'one again'), (2, 2, 'two'), \
(NULL, 1, 'null key'), (NULL, 2, 'null key again'), (4, 4, 'left only')",
)
.unwrap();
db.execute("CREATE TABLE r (k INTEGER, j INTEGER, tag VARCHAR)").unwrap();
db.execute(
"INSERT INTO r VALUES (1, 1, 'a'), (1, 1, 'b'), (2, 9, 'wrong second'), \
(NULL, 1, 'null key'), (NULL, 9, 'null key again'), (5, 5, 'right only')",
)
.unwrap();
let conditions = [
("l.k IS NOT DISTINCT FROM r.k", "coalesce(l.k, -1) = coalesce(r.k, -1)"),
(
"l.k IS NOT DISTINCT FROM r.k AND l.j IS NOT DISTINCT FROM r.j",
"coalesce(l.k, -1) = coalesce(r.k, -1) AND coalesce(l.j, -1) = coalesce(r.j, -1)",
),
(
"l.k IS NOT DISTINCT FROM r.k AND l.j = r.j",
"coalesce(l.k, -1) = coalesce(r.k, -1) AND l.j = r.j",
),
];
for kind in ["INNER", "LEFT", "RIGHT", "FULL", "SEMI", "ANTI"] {
for (on, oracle) in conditions {
let one_sided = kind == "SEMI" || kind == "ANTI";
let listing = |condition: &str| {
let columns = if one_sided { "l.tag" } else { "l.tag, r.tag" };
let order =
if one_sided { "1 NULLS FIRST" } else { "1 NULLS FIRST, 2 NULLS FIRST" };
let sql = format!(
"SELECT {columns} FROM l {kind} JOIN r ON {condition} ORDER BY {order}"
);
rows(&db, &sql)
};
assert_eq!(listing(on), listing(oracle), "{kind} JOIN ON {on}");
}
}
}
#[test]
fn a_semi_an_anti_and_a_single_join_answer_what_the_loop_answers() {
let db = Database::new();
db.execute("CREATE TABLE l (k INTEGER, tag VARCHAR)").unwrap();
db.execute(
"INSERT INTO l VALUES (1, 'one'), (1, 'one again'), (2, 'two'), (NULL, 'null key'), \
(4, 'left only')",
)
.unwrap();
db.execute("CREATE TABLE r (k INTEGER, v INTEGER)").unwrap();
db.execute("INSERT INTO r VALUES (1, 10), (1, 11), (2, 20), (NULL, 30), (5, 50)").unwrap();
let shapes = [
"SELECT l.tag FROM l SEMI JOIN r ON r.k = l.k{extra}",
"SELECT l.tag FROM l ANTI JOIN r ON r.k = l.k{extra}",
"SELECT tag, (SELECT v FROM r WHERE r.k = l.k{extra} AND r.k <> 1) FROM l",
];
for shape in shapes {
let listing = |extra: &str| {
rows(&db, &format!("{} ORDER BY 1 NULLS FIRST", shape.replace("{extra}", extra)))
};
assert_eq!(listing(""), listing(" AND r.k >= l.k"), "{shape}");
}
}
#[test]
fn a_star_over_a_semi_or_an_anti_join_is_the_left_side_alone() {
let db = Database::new();
db.execute("CREATE TABLE a (k INTEGER, v INTEGER)").unwrap();
db.execute("INSERT INTO a VALUES (1, 10), (2, 20)").unwrap();
db.execute("CREATE TABLE b (k INTEGER, w INTEGER)").unwrap();
db.execute("INSERT INTO b VALUES (1, 100)").unwrap();
let matched = vec![vec![integer(1), integer(10)]];
assert_eq!(rows(&db, "SELECT * FROM a SEMI JOIN b ON a.k = b.k"), matched);
assert_eq!(rows(&db, "SELECT * FROM a SEMI JOIN b USING (k)"), matched);
assert_eq!(rows(&db, "SELECT * FROM a NATURAL SEMI JOIN b"), matched);
assert_eq!(rows(&db, "SELECT * FROM (SELECT * FROM a SEMI JOIN b ON a.k = b.k) t"), matched);
assert_eq!(
rows(&db, "SELECT * FROM a ANTI JOIN b ON a.k = b.k"),
vec![vec![integer(2), integer(20)]]
);
assert!(
failure(&db, "SELECT b.w FROM a SEMI JOIN b ON a.k = b.k").contains("\"b\""),
"the right side should be out of scope after a semi join"
);
}
#[test]
fn a_driving_row_that_matches_more_rows_than_a_chunk_holds_produces_all_of_them() {
let db = Database::new();
db.execute("CREATE TABLE l (k INTEGER, tag VARCHAR)").unwrap();
db.execute("INSERT INTO l VALUES (1, 'popular'), (2, 'rare'), (3, 'absent')").unwrap();
db.execute("CREATE TABLE r (k INTEGER, v BIGINT)").unwrap();
db.execute("INSERT INTO r SELECT 1, i FROM range(5000) AS series(i)").unwrap();
db.execute("INSERT INTO r VALUES (2, -1)").unwrap();
assert_eq!(
rows(&db, "SELECT count(*), count(DISTINCT r.v), sum(r.v) FROM l JOIN r ON l.k = r.k"),
vec![vec![Value::BigInt(5001), Value::BigInt(5001), Value::HugeInt(12_497_499)]]
);
assert_eq!(
rows(&db, "SELECT l.tag, count(*) FROM l LEFT JOIN r ON l.k = r.k GROUP BY 1 ORDER BY 1"),
vec![
vec![text("absent"), Value::BigInt(1)],
vec![text("popular"), Value::BigInt(5000)],
vec![text("rare"), Value::BigInt(1)],
]
);
}
#[test]
fn a_union_deduplicates_and_union_all_does_not() {
let db = database();
assert_eq!(rows(&db, "SELECT 1 UNION ALL SELECT 1"), vec![vec![integer(1)], vec![integer(1)]]);
assert_eq!(rows(&db, "SELECT 1 UNION SELECT 1"), vec![vec![integer(1)]]);
}
#[test]
fn a_subquery_in_the_from_clause_is_a_table() {
let db = database();
assert_eq!(
rows(&db, "SELECT inner_query.total FROM (SELECT count(*) AS total FROM t) AS inner_query"),
vec![vec![Value::BigInt(4)]]
);
}
#[test]
fn a_case_expression_picks_the_first_arm_that_holds() {
let db = database();
assert_eq!(
rows(&db, "SELECT CASE WHEN x > 2 THEN 'big' ELSE 'small' END FROM t ORDER BY x"),
vec![vec![text("small")], vec![text("small")], vec![text("small")], vec![text("big")]]
);
}
#[test]
fn a_comparison_with_a_string_happens_in_the_other_type() {
let db = database();
assert_eq!(
rows(&db, "SELECT CAST('2013-07-15' AS DATE) = '2013-7-15'"),
vec![vec![Value::Boolean(true)]]
);
assert_eq!(
rows(&db, "SELECT CAST('2013-07-15' AS DATE) >= '2013-07-01'"),
vec![vec![Value::Boolean(true)]]
);
assert_eq!(rows(&db, "SELECT 10 > '9'"), vec![vec![Value::Boolean(true)]]);
assert_eq!(rows(&db, "SELECT TRUE = 'true'"), vec![vec![Value::Boolean(true)]]);
}
fn moment(hours: i64, minutes: i64, seconds: i64) -> Value {
let day = i64::from(days_from_civil(2013, 7, 15)) * 86_400_000_000;
Value::Timestamp(day + hours * 3_600_000_000 + minutes * 60_000_000 + seconds * 1_000_000)
}
#[test]
fn a_timestamp_can_be_taken_apart_and_grouped_by() {
let db = Database::new();
db.create_table("hits", vec![Field::new("ts", LogicalType::Timestamp)]).unwrap();
db.append("hits", &[vec![moment(10, 23, 45)], vec![moment(10, 23, 7)], vec![moment(11, 5, 0)]])
.unwrap();
assert_eq!(
rows(&db, "SELECT extract(minute FROM ts) AS m, COUNT(*) FROM hits GROUP BY m ORDER BY m"),
vec![vec![Value::BigInt(5), Value::BigInt(1)], vec![Value::BigInt(23), Value::BigInt(2)],]
);
assert_eq!(
rows(
&db,
"SELECT DATE_TRUNC('hour', ts) AS h, COUNT(*) AS n FROM hits \
GROUP BY DATE_TRUNC('hour', ts) ORDER BY DATE_TRUNC('hour', ts)"
),
vec![vec![moment(10, 0, 0), Value::BigInt(2)], vec![moment(11, 0, 0), Value::BigInt(1)],]
);
}
#[test]
fn the_integers_the_benchmark_stores_become_the_dates_the_benchmark_queries() {
let db = Database::new();
db.create_table(
"hits",
vec![
Field::new("EventDate", LogicalType::Integer),
Field::new("EventTime", LogicalType::BigInt),
],
)
.unwrap();
let day = i64::from(days_from_civil(2013, 7, 15));
db.append(
"hits",
&[
vec![
Value::Integer(days_from_civil(2013, 7, 15)),
Value::BigInt(day * 86_400 + 37_425),
],
vec![Value::Integer(days_from_civil(2013, 7, 2)), Value::BigInt(day * 86_400 + 37_387)],
],
)
.unwrap();
assert_eq!(
rows(
&db,
"SELECT make_date(EventDate) AS d FROM hits WHERE make_date(EventDate) >= '2013-07-10' \
ORDER BY d"
),
vec![vec![Value::Date(days_from_civil(2013, 7, 15))]]
);
assert_eq!(
rows(
&db,
"SELECT extract(minute FROM epoch_ms(EventTime * 1000)) AS m, COUNT(*) FROM hits \
GROUP BY m ORDER BY m"
),
vec![vec![Value::BigInt(23), Value::BigInt(2)]]
);
}
#[test]
fn a_star_can_replace_some_of_what_it_stands_for() {
let db = Database::new();
db.create_table(
"hits",
vec![
Field::new("EventDate", LogicalType::Integer),
Field::new("UserID", LogicalType::BigInt),
],
)
.unwrap();
db.append("hits", &[vec![Value::Integer(days_from_civil(2013, 7, 15)), Value::BigInt(7)]])
.unwrap();
let result =
db.query("SELECT * REPLACE (make_date(EventDate) AS eventdate) FROM hits").unwrap();
assert_eq!(result.names(), &["eventdate", "UserID"]);
assert_eq!(
result.rows().collect::<Vec<_>>(),
vec![vec![Value::Date(days_from_civil(2013, 7, 15)), Value::BigInt(7)]]
);
assert_eq!(
rows(&db, "SELECT hits.* REPLACE (UserID + 1 AS UserID) FROM hits"),
vec![vec![Value::Integer(days_from_civil(2013, 7, 15)), Value::BigInt(8)]]
);
assert_eq!(
failure(&db, "SELECT * REPLACE (nope + 1 AS nope) FROM hits"),
"Column \"nope\" in REPLACE list not found in FROM clause Candidate bindings: \
\"EventDate\", \"UserID\""
);
}
#[test]
fn a_date_part_reads_its_specifier_three_ways_and_refuses_a_fourth() {
let db = database();
let stamp = "CAST('2013-07-15 10:23:45' AS TIMESTAMP)";
for sql in [
format!("SELECT extract(minute FROM {stamp})"),
format!("SELECT extract('minute' FROM {stamp})"),
format!("SELECT date_part('minute', {stamp})"),
] {
assert_eq!(rows(&db, &sql), vec![vec![Value::BigInt(23)]], "{sql}");
}
assert!(failure(&db, &format!("SELECT date_part('qtr', {stamp})")).contains("qtr"));
}
#[test]
fn a_part_of_an_interval_is_one_of_its_three_fields() {
let db = database();
let lengths = "FROM (VALUES (INTERVAL '14 months'), (INTERVAL '-14 months'), \
(CAST(NULL AS INTERVAL))) t(length)";
let one = |value: i64| vec![vec![Value::BigInt(value)]];
assert_eq!(rows(&db, "SELECT date_part('hour', INTERVAL '36 hours')"), one(36));
assert_eq!(rows(&db, "SELECT date_part('day', INTERVAL '36 hours')"), one(0));
assert_eq!(rows(&db, "SELECT extract(month FROM INTERVAL '14 months')"), one(2));
assert_eq!(
rows(&db, &format!("SELECT date_part('month', length) {lengths}")),
vec![vec![Value::BigInt(2)], vec![Value::BigInt(-2)], vec![Value::Null]]
);
let refused = failure(&db, &format!("SELECT date_part('week', length) {lengths}"));
assert_eq!(refused, "\"interval\" units \"week\" not recognized");
let refused = failure(&db, "SELECT date_part('week', INTERVAL '5 days')");
assert_eq!(refused, "\"interval\" units \"week\" not recognized");
}
#[test]
fn a_gap_in_calendar_fields_is_not_the_same_as_a_difference() {
let db = database();
let gap = |months: i32, days: i32| vec![vec![Value::Interval { months, days, micros: 0 }]];
let late = "TIMESTAMP '2020-07-01'";
let early = "TIMESTAMP '2020-02-28'";
assert_eq!(rows(&db, &format!("SELECT age({late}, {early})")), gap(4, 2));
assert_eq!(rows(&db, &format!("SELECT age({early}, {late})")), gap(-4, -2));
assert_eq!(rows(&db, &format!("SELECT {late} - {early}")), gap(0, 124));
assert_eq!(rows(&db, "SELECT age(DATE '2020-07-01', DATE '2020-02-28')"), gap(4, 2));
let moments = "FROM (VALUES (TIMESTAMP '2020-04-30', TIMESTAMP '2020-03-31'), \
(TIMESTAMP '2020-01-01', NULL)) t(late, early)";
assert_eq!(
rows(&db, &format!("SELECT age(late, early) {moments}")),
vec![vec![Value::Interval { months: 0, days: 30, micros: 0 }], vec![Value::Null]]
);
let refused = failure(&db, "SELECT age(TIME '10:00:00', TIME '09:00:00')");
assert!(refused.contains("age(TIME, TIME)"), "{refused}");
}
#[test]
fn the_two_parts_that_carry_a_fraction_are_doubles() {
let db = database();
let stamp = "CAST('2020-01-01 12:00:00' AS TIMESTAMP)";
let one = |value: f64| vec![vec![Value::Double(value)]];
assert_eq!(rows(&db, &format!("SELECT date_part('epoch', {stamp})")), one(1_577_880_000.0));
assert_eq!(rows(&db, &format!("SELECT date_part('julian', {stamp})")), one(2_458_850.5));
assert_eq!(rows(&db, "SELECT date_part('jd', DATE '2020-01-01')"), one(2_458_850.0));
assert_eq!(rows(&db, "SELECT date_part('epoch', INTERVAL '1 year')"), one(31_557_600.0));
assert_eq!(
rows(&db, &format!("SELECT date_part('year', {stamp})")),
vec![vec![Value::BigInt(2_020)]]
);
let parts = "FROM (VALUES ('year'), ('epoch')) t(part)";
assert_eq!(
rows(&db, &format!("SELECT date_part(part, DATE '2020-01-01') {parts}")),
vec![vec![Value::Double(2_020.0)], vec![Value::Double(1_577_836_800.0)]]
);
}
#[test]
fn truncating_an_interval_keeps_the_fields_above_the_part() {
let db = database();
let length = "INTERVAL '14 months 10 days 06:07:08.9'";
let months = Value::Interval { months: 14, days: 0, micros: 0 };
assert_eq!(rows(&db, &format!("SELECT date_trunc('month', {length})")), vec![vec![months]]);
let week = Value::Interval { months: 14, days: 7, micros: 0 };
assert_eq!(rows(&db, &format!("SELECT date_trunc('week', {length})")), vec![vec![week]]);
let lengths = format!("FROM (VALUES ({length}), (CAST(NULL AS INTERVAL))) t(length)");
let hour = Value::Interval { months: 14, days: 10, micros: 6 * 3_600 * 1_000_000 };
assert_eq!(
rows(&db, &format!("SELECT date_trunc('hour', length) {lengths}")),
vec![vec![hour], vec![Value::Null]]
);
let refused = failure(&db, &format!("SELECT date_trunc('era', {length})"));
assert_eq!(refused, "Specifier type not implemented for DATETRUNC");
}
#[test]
fn a_referer_can_be_cut_down_to_its_host_and_grouped_by() {
let db = Database::new();
db.create_table("hits", vec![Field::new("Referer", LogicalType::Varchar)]).unwrap();
db.append(
"hits",
&[
vec![text("http://www.example.com/a/b")],
vec![text("https://example.com/")],
vec![text("http://other.org/x?y=1")],
vec![text("")],
],
)
.unwrap();
assert_eq!(
rows(
&db,
"SELECT REGEXP_REPLACE(Referer, '^https?://(?:www\\.)?([^/]+)/.*$', '\\1') AS k, \
COUNT(*) AS c FROM hits WHERE Referer <> '' GROUP BY k ORDER BY c DESC, k"
),
vec![
vec![text("example.com"), Value::BigInt(2)],
vec![text("other.org"), Value::BigInt(1)]
]
);
}
#[test]
fn the_regular_expression_functions_answer_the_way_duckdb_does() {
let db = database();
assert_eq!(rows(&db, "SELECT regexp_matches('abc', 'b')"), vec![vec![Value::Boolean(true)]]);
assert_eq!(
rows(&db, "SELECT regexp_full_match('abc', 'a')"),
vec![vec![Value::Boolean(false)]]
);
assert_eq!(
rows(&db, "SELECT regexp_extract('abc123', '([a-z]+)([0-9]+)', 2)"),
vec![vec![text("123")]]
);
assert_eq!(rows(&db, "SELECT regexp_extract('abc', 'z')"), vec![vec![text("")]]);
assert_eq!(
rows(&db, "SELECT regexp_replace('aXbXc', 'X', '-', 'g')"),
vec![vec![text("a-b-c")]]
);
assert_eq!(rows(&db, "SELECT regexp_replace(NULL, 'a', 'b')"), vec![vec![Value::Null]]);
assert_eq!(
rows(&db, "SELECT s FROM t WHERE regexp_matches(s, '^[ac]$')"),
vec![vec![text("a")], vec![text("c")], vec![text("a")]]
);
assert!(failure(&db, "SELECT regexp_matches('a', '(')").contains("missing )"));
assert!(
failure(&db, "SELECT regexp_replace('a', 'a', 'b', 'q')")
.contains("Unrecognized Regex option q")
);
}
#[test]
fn a_function_that_is_not_implemented_does_not_answer_its_own_argument() {
let db = database();
let message = failure(&db, "SELECT row(1)");
assert!(message.contains("not supported yet"), "{message}");
assert!(message.ends_with("RowExpression"), "{message}");
assert!(failure(&db, "SELECT length(try('a'))").contains("not supported yet"));
}
#[test]
fn the_string_keywords_answer_the_way_duckdb_does() {
let db = database();
let one = |sql: &str| match rows(&db, sql).as_slice() {
[row] => row.clone(),
other => panic!("one row, not {}", other.len()),
};
assert_eq!(
one("SELECT substring('abcdef', 2, 3), substring('abcdef' FROM 2 FOR 3)"),
vec![text("bcd"), text("bcd")]
);
assert_eq!(
one("SELECT substring('abcdef', 2), substring('abcdef' FOR 3), substr('abcdef', 2, 3)"),
vec![text("bcdef"), text("abc"), text("bcd")]
);
assert_eq!(
one(
"SELECT substring('abcdef', 0, 3), substring('abcdef', -1, 3), substring('abcdef', 4, -2)"
),
vec![text("ab"), text("f"), text("bc")]
);
assert_eq!(
one(
"SELECT substring('abcdef', 10, 3), substring('abcdef', -10, 3), substring('abcdef', -10)"
),
vec![text(""), text(""), text("abcdef")]
);
assert_eq!(
one("SELECT position('c' IN 'abcdef'), strpos('abcdef', 'z'), instr('abcdef', 'abc')"),
vec![Value::BigInt(3), Value::BigInt(0), Value::BigInt(1)]
);
assert_eq!(
one("SELECT trim(' a '), trim(BOTH 'x' FROM 'xxaxx'), trim('xyaxy', 'xy')"),
vec![text("a"), text("a"), text("a")]
);
assert_eq!(
one("SELECT trim(LEADING FROM ' a '), trim(TRAILING FROM ' a '), ltrim('xxaxx', 'x')"),
vec![text("a "), text(" a"), text("axx")]
);
assert_eq!(
one(
"SELECT overlay('abcdef' PLACING 'X' FROM 2 FOR 1), overlay('abcdef' PLACING 'XY' FROM 2)"
),
vec![text("aXcdef"), text("aXYdef")]
);
assert_eq!(
one(
"SELECT overlay('abcdef' PLACING 'XY' FROM 2 FOR 0), overlay('abcdef' PLACING 'XY' FROM 2 FOR -1)"
),
vec![text("aXYbcdef"), text("aXYdef")]
);
assert_eq!(
one("SELECT substring(NULL, 1, 2), trim(NULL), strpos('a', NULL)"),
vec![Value::Null, Value::Null, Value::Null]
);
assert_eq!(
one("SELECT typeof(substring('abcdef', 2)), typeof(strpos('a', 'b'))"),
vec![text("VARCHAR"), text("BIGINT")]
);
assert_eq!(
rows(&db, "SELECT substring(s, 1, 1) FROM t"),
vec![vec![text("a")], vec![Value::Null], vec![text("c")], vec![text("a")]]
);
assert_eq!(
db.query("SELECT substring(s FROM 2 FOR 3) FROM t").unwrap().names(),
&["\"substring\"(s, 2, 3)".to_string()]
);
assert_eq!(
db.query("SELECT position('c' IN s) FROM t").unwrap().names(),
&["\"position\"(s, 'c')".to_string()]
);
assert_eq!(
db.query("SELECT trim(LEADING FROM s) FROM t").unwrap().names(),
&["ltrim(s)".to_string()]
);
let message = failure(&db, "SELECT substring('abcdef', 2.5, 3)");
assert!(message.contains("\"substring\"(col0 VARCHAR, col1 BIGINT, col2 BIGINT) -> VARCHAR"));
assert!(failure(&db, "SELECT trim(123)").contains("\"trim\"(col0 VARCHAR) -> VARCHAR"));
assert!(failure(&db, "SELECT strpos('abcdef')").contains("strpos(col0 VARCHAR, col1 VARCHAR)"));
}
#[test]
fn a_generated_name_quotes_an_identifier_where_duckdb_quotes_one() {
let db = Database::new();
let fields = ["name", "alias", "UserID", "my col", "9x"]
.iter()
.map(|name| Field::new(*name, LogicalType::Integer))
.collect();
db.create_table("q", fields).unwrap();
let names = |sql: &str| db.query(sql).unwrap().names().to_vec();
assert_eq!(names("SELECT min(name) FROM q"), &["min(\"name\")".to_string()]);
assert_eq!(names("SELECT min(alias) FROM q"), &["min(alias)".to_string()]);
assert_eq!(names("SELECT min(\"UserID\") FROM q"), &["min(UserID)".to_string()]);
assert_eq!(names("SELECT min(\"my col\") FROM q"), &["min(\"my col\")".to_string()]);
assert_eq!(names("SELECT min(\"9x\") FROM q"), &["min(\"9x\")".to_string()]);
assert_eq!(names("SELECT name FROM q"), &["name".to_string()]);
assert_eq!(names("SELECT name + 1 FROM q"), &["(\"name\" + 1)".to_string()]);
assert_eq!(
names("SELECT CAST(name AS VARCHAR) FROM q"),
&["CAST(\"name\" AS VARCHAR)".to_string()]
);
assert_eq!(names("SELECT name IS NULL FROM q"), &["(\"name\" IS NULL)".to_string()]);
}
#[test]
fn the_five_string_functions_answer_the_way_duckdb_does() {
let db = database();
assert_eq!(rows(&db, "SELECT chr(65), chr(233)"), vec![vec![text("A"), text("é")]]);
assert_eq!(rows(&db, "SELECT length(chr(0))"), vec![vec![Value::BigInt(1)]]);
assert_eq!(
rows(&db, "SELECT left('héllo', 2), right('héllo', 2)"),
vec![vec![text("hé"), text("lo")]]
);
assert_eq!(
rows(&db, "SELECT left('abc', -1), right('abc', -1), left('abc', 0), right('abc', 99)"),
vec![vec![text("ab"), text("bc"), text(""), text("abc")]]
);
assert_eq!(
rows(&db, "SELECT replace('abc', 'b', 'x'), replace('aaa', '', 'x')"),
vec![vec![text("axc"), text("aaa")]]
);
assert_eq!(rows(&db, "SELECT concat('a', 1, NULL)"), vec![vec![text("a1")]]);
assert_eq!(rows(&db, "SELECT concat(NULL)"), vec![vec![text("")]]);
assert_eq!(
rows(&db, "SELECT replace('abc', 'b', NULL), left(NULL, 2), chr(NULL)"),
vec![vec![Value::Null, Value::Null, Value::Null]]
);
assert_eq!(
db.query("SELECT LEFT('abc', 2), chr(65)").unwrap().names(),
&["\"left\"('abc', 2)".to_string(), "chr(65)".to_string()]
);
assert_eq!(failure(&db, "SELECT chr(55296)"), "Invalid UTF8 Codepoint 55296");
assert!(failure(&db, "SELECT chr(65.9)").contains("chr(col0 INTEGER) -> VARCHAR"));
assert!(
failure(&db, "SELECT left('abc')")
.contains("\"left\"(col0 VARCHAR, col1 BIGINT) -> VARCHAR")
);
assert!(failure(&db, "SELECT concat()").contains("concat(col0 ANY, [ANY...]) -> ANY"));
}
#[test]
fn an_interval_literal_is_the_call_duckdb_rewrites_it_into() {
let db = database();
let interval = |months, days, micros| Value::Interval { months, days, micros };
assert_eq!(
db.query("SELECT INTERVAL 1 DAY").unwrap().names(),
&["to_days(CAST(trunc(CAST(1 AS DOUBLE)) AS INTEGER))".to_string()]
);
assert_eq!(
db.query("SELECT INTERVAL 90 SECOND").unwrap().names(),
&["to_seconds(CAST(90 AS DOUBLE))".to_string()]
);
assert_eq!(rows(&db, "SELECT INTERVAL 1 DAY"), vec![vec![interval(0, 1, 0)]]);
assert_eq!(rows(&db, "SELECT INTERVAL 1 DAYS"), vec![vec![interval(0, 1, 0)]]);
assert_eq!(rows(&db, "SELECT INTERVAL 2 MONTHS"), vec![vec![interval(2, 0, 0)]]);
assert_eq!(rows(&db, "SELECT INTERVAL 1 WEEK"), vec![vec![interval(0, 7, 0)]]);
assert_eq!(rows(&db, "SELECT INTERVAL 1 MILLENNIUM"), vec![vec![interval(12_000, 0, 0)]]);
assert_eq!(rows(&db, "SELECT INTERVAL (1+1) DAY"), vec![vec![interval(0, 2, 0)]]);
assert_eq!(rows(&db, "SELECT INTERVAL (-1) DAY"), vec![vec![interval(0, -1, 0)]]);
assert_eq!(rows(&db, "SELECT INTERVAL 1.5 DAY"), vec![vec![interval(0, 1, 0)]]);
assert_eq!(rows(&db, "SELECT INTERVAL 2.7 SECOND"), vec![vec![interval(0, 0, 2_700_000)]]);
assert_eq!(rows(&db, "SELECT INTERVAL 1 MILLISECOND"), vec![vec![interval(0, 0, 1_000)]]);
assert_eq!(rows(&db, "SELECT to_days(1)"), vec![vec![interval(0, 1, 0)]]);
assert_eq!(rows(&db, "SELECT to_quarters(5)"), vec![vec![interval(15, 0, 0)]]);
assert_eq!(rows(&db, "SELECT to_days(NULL)"), vec![vec![Value::Null]]);
assert_eq!(rows(&db, "SELECT to_hours(25)"), vec![vec![interval(0, 0, 90_000_000_000)]]);
assert_eq!(rows(&db, "SELECT to_seconds(1.5)"), vec![vec![interval(0, 0, 1_500_000)]]);
assert!(failure(&db, "SELECT to_days(1.7)").contains("to_days(col0 INTEGER) -> INTERVAL"));
assert_eq!(failure(&db, "SELECT INTERVAL 1 DAYS TO HOURS"), "DAY TO HOUR is not supported");
assert_eq!(
failure(&db, "SELECT to_years(2147483647)"),
"Interval value 2147483647 years out of range"
);
}
#[test]
fn the_null_checks_answer_the_way_duckdb_does() {
let db = database();
assert_eq!(rows(&db, "SELECT COALESCE(NULL, 1)"), vec![vec![Value::Integer(1)]]);
assert_eq!(rows(&db, "SELECT coalesce(NULL, NULL, 3, 4)"), vec![vec![Value::Integer(3)]]);
assert_eq!(rows(&db, "SELECT coalesce(NULL, NULL)"), vec![vec![Value::Null]]);
assert_eq!(rows(&db, "SELECT coalesce(2)"), vec![vec![Value::Integer(2)]]);
assert_eq!(
rows(&db, "SELECT ifnull(NULL, 3), ifnull(1, 3)"),
vec![vec![Value::Integer(3), Value::Integer(1)]]
);
assert_eq!(
rows(&db, "SELECT nullif(1, 2), nullif(2, 2)"),
vec![vec![Value::Integer(1), Value::Null]]
);
assert_eq!(
rows(&db, "SELECT nullif(1, NULL), nullif(NULL, 1)"),
vec![vec![Value::Integer(1), Value::Null]]
);
assert_eq!(
rows(&db, "SELECT nullif('a', 'a'), nullif('a', 'b')"),
vec![vec![Value::Null, text("a")]]
);
assert_eq!(rows(&db, "SELECT nullif(TRUE, FALSE)"), vec![vec![Value::Boolean(true)]]);
assert_eq!(
rows(&db, "SELECT nullif(2, 2.5), typeof(nullif(2, 2.5))"),
vec![vec![Value::Integer(2), text("INTEGER")]]
);
assert_eq!(
rows(&db, "SELECT typeof(coalesce(1, 2.5)), typeof(nullif(1::BIGINT, 2::SMALLINT))"),
vec![vec![text("DECIMAL(11,1)"), text("BIGINT")]]
);
assert_eq!(
rows(&db, "SELECT coalesce(s, 'none') FROM t"),
vec![vec![text("a")], vec![text("none")], vec![text("c")], vec![text("a")]]
);
assert_eq!(
rows(&db, "SELECT nullif(x, 1) FROM t"),
vec![
vec![Value::Integer(3)],
vec![Value::Null],
vec![Value::Integer(2)],
vec![Value::Null]
]
);
assert_eq!(
db.query("SELECT coalesce(x, 1) FROM t").unwrap().names(),
&["COALESCE(x, 1)".to_string()]
);
assert_eq!(
db.query("SELECT IFNULL(x, 1) FROM t").unwrap().names(),
&["COALESCE(x, 1)".to_string()]
);
assert_eq!(
db.query("SELECT NULLIF(x, 1) FROM t").unwrap().names(),
&["\"nullif\"(x, 1)".to_string()]
);
assert!(failure(&db, "SELECT nullif(1)").contains("syntax error at or near \")\""));
assert!(failure(&db, "SELECT nullif(1, 2, 3)").contains("syntax error at or near \",\""));
assert!(failure(&db, "SELECT coalesce()").contains("syntax error at or near \")\""));
assert_eq!(failure(&db, "SELECT ifnull(1)"), "Wrong number of arguments to IFNULL.");
assert_eq!(failure(&db, "SELECT ifnull(1, 2, 3)"), "Wrong number of arguments to IFNULL.");
}
#[test]
fn typeof_answers_the_name_of_the_type() {
let db = database();
let named = |sql: &str| match rows(&db, sql).as_slice() {
[row] => row.clone(),
other => panic!("one row, not {}", other.len()),
};
assert_eq!(
named("SELECT typeof(1), typeof(1.5), typeof('a'), typeof(TRUE), typeof(NULL)"),
vec![
text("INTEGER"),
text("DECIMAL(2,1)"),
text("VARCHAR"),
text("BOOLEAN"),
text("\"NULL\"")
]
);
assert_eq!(
named("SELECT typeof(1::BIGINT), typeof('2024-01-01'::DATE), typeof(1 + 2), typeof(1 / 2)"),
vec![text("BIGINT"), text("DATE"), text("INTEGER"), text("DOUBLE")]
);
assert_eq!(
named("SELECT DISTINCT typeof(x), typeof(s), typeof(x + 1) FROM t"),
vec![text("INTEGER"), text("VARCHAR"), text("INTEGER")]
);
assert_eq!(
named("SELECT typeof(count(*)), typeof(sum(x)), typeof(avg(x)) FROM t"),
vec![text("BIGINT"), text("HUGEINT"), text("DOUBLE")]
);
assert_eq!(db.query("SELECT typeof(x) FROM t").unwrap().names(), &["typeof(x)".to_string()]);
assert!(failure(&db, "SELECT typeof(1, 2)").contains("typeof(col0 ANY) -> VARCHAR"));
}
#[test]
fn a_bracket_on_a_string_indexes_it_by_character() {
let db = database();
assert_eq!(rows(&db, "SELECT 'abcdef'[2]"), vec![vec![text("b")]]);
assert_eq!(rows(&db, "SELECT 'abcdef'[-1]"), vec![vec![text("f")]]);
assert_eq!(rows(&db, "SELECT 'abcdef'[0]"), vec![vec![text("")]]);
assert_eq!(rows(&db, "SELECT 'abcdef'[9]"), vec![vec![text("")]]);
assert_eq!(rows(&db, "SELECT 'héllo'[2]"), vec![vec![text("é")]]);
assert_eq!(rows(&db, "SELECT 'abcdef'[2:4]"), vec![vec![text("bcd")]]);
assert_eq!(rows(&db, "SELECT 'abcdef'[:3]"), vec![vec![text("abc")]]);
assert_eq!(rows(&db, "SELECT 'abcdef'[4:]"), vec![vec![text("def")]]);
assert_eq!(rows(&db, "SELECT 'abcdef'[:]"), vec![vec![text("abcdef")]]);
assert_eq!(rows(&db, "SELECT 'abcdef'[1:-]"), vec![vec![text("abcdef")]]);
assert_eq!(rows(&db, "SELECT 'abcdef'[3:99]"), vec![vec![text("cdef")]]);
assert_eq!(rows(&db, "SELECT 'abcdef'[4:2]"), vec![vec![text("")]]);
assert_eq!(
rows(&db, "SELECT s[1], s[1:1] FROM t"),
vec![
vec![text("a"), text("a")],
vec![Value::Null, Value::Null],
vec![text("c"), text("c")],
vec![text("a"), text("a")],
]
);
assert_eq!(rows(&db, "SELECT array_extract('abcdef', 2)"), vec![vec![text("b")]]);
assert_eq!(rows(&db, "SELECT list_extract('abcdef', 2)"), vec![vec![text("b")]]);
assert_eq!(rows(&db, "SELECT list_element('abcdef', 2)"), vec![vec![text("b")]]);
assert_eq!(rows(&db, "SELECT list_slice('abcdef', 2, 4)"), vec![vec![text("bcd")]]);
}
#[test]
fn a_bracket_on_a_list_picks_one_element_out_of_it() {
let db = database();
assert_eq!(rows(&db, "SELECT [1,2,3][2]"), vec![vec![integer(2)]]);
assert_eq!(rows(&db, "SELECT [1,2,3][-1]"), vec![vec![integer(3)]]);
assert_eq!(rows(&db, "SELECT [1,2,3][0]"), vec![vec![Value::Null]]);
assert_eq!(rows(&db, "SELECT [1,2,3][4]"), vec![vec![Value::Null]]);
assert_eq!(rows(&db, "SELECT list_extract([1,2,3], 2)"), vec![vec![integer(2)]]);
}
#[test]
fn a_slice_of_a_list_answers_a_list() {
let db = database();
assert_eq!(rows(&db, "SELECT [1,2,3][1:2]"), vec![vec![list(&[1, 2])]]);
assert_eq!(rows(&db, "SELECT [1,2,3][2:]"), vec![vec![list(&[2, 3])]]);
assert_eq!(rows(&db, "SELECT [1,2,3][:2]"), vec![vec![list(&[1, 2])]]);
assert_eq!(rows(&db, "SELECT [1,2,3][:]"), vec![vec![list(&[1, 2, 3])]]);
assert_eq!(rows(&db, "SELECT [1,2,3][0:2]"), vec![vec![list(&[1, 2])]]);
assert_eq!(rows(&db, "SELECT [1,2,3][-2:-1]"), vec![vec![list(&[2, 3])]]);
assert_eq!(rows(&db, "SELECT [1,2,3][2:99]"), vec![vec![list(&[2, 3])]]);
assert_eq!(rows(&db, "SELECT [1,2,3][-99:99]"), vec![vec![list(&[1, 2, 3])]]);
assert_eq!(rows(&db, "SELECT [1,2,3][3:1]"), vec![vec![list(&[])]]);
assert_eq!(rows(&db, "SELECT array_slice([1,2,3], 2, 3)"), vec![vec![list(&[2, 3])]]);
assert_eq!(rows(&db, "SELECT list_slice([1,2,3], 1, 1)"), vec![vec![list(&[1])]]);
assert_eq!(rows(&db, "SELECT [1,2,3][NULL:2]"), vec![vec![Value::Null]]);
assert_eq!(rows(&db, "SELECT (NULL::INT[])[1:2]"), vec![vec![Value::Null]]);
}
#[test]
fn a_list_column_keeps_an_empty_list_and_a_null_apart() {
let db = database();
db.execute("CREATE TABLE lists (a INTEGER[])").unwrap();
db.execute("INSERT INTO lists VALUES ([1,2,3]), (NULL), ([]), ([4])").unwrap();
assert_eq!(
rows(&db, "SELECT a FROM lists"),
vec![vec![list(&[1, 2, 3])], vec![Value::Null], vec![list(&[])], vec![list(&[4])],]
);
assert_eq!(rows(&db, "SELECT count(a) FROM lists"), vec![vec![Value::BigInt(3)]]);
assert_eq!(
rows(&db, "SELECT a IS NULL FROM lists"),
vec![
vec![Value::Boolean(false)],
vec![Value::Boolean(true)],
vec![Value::Boolean(false)],
vec![Value::Boolean(false)],
]
);
}
#[test]
fn a_list_can_be_written_over_columns_and_not_only_over_constants() {
let db = database();
assert_eq!(rows(&db, "SELECT [x, 2] FROM t WHERE x = 3"), vec![vec![list(&[3, 2])]]);
assert_eq!(
rows(&db, "SELECT typeof([x, 2.5::DOUBLE]) FROM t WHERE x = 3"),
vec![vec![text("DOUBLE[]")]]
);
assert_eq!(rows(&db, "SELECT typeof([])"), vec![vec![text("\"NULL\"[]")]]);
assert_eq!(rows(&db, "SELECT list_pack(1, 2)"), vec![vec![list(&[1, 2])]]);
}
#[test]
fn a_list_casts_by_casting_every_element_of_it() {
let db = database();
assert_eq!(
rows(&db, "SELECT CAST([1, 2] AS BIGINT[])"),
vec![vec![Value::List {
element: LogicalType::BigInt,
values: vec![Value::BigInt(1), Value::BigInt(2)],
}]]
);
assert_eq!(
rows(&db, "SELECT [1, 2]::VARCHAR[]"),
vec![vec![Value::List {
element: LogicalType::Varchar,
values: vec![text("1"), text("2")]
}]]
);
assert_eq!(rows(&db, "SELECT CAST([] AS INTEGER[])"), vec![vec![list(&[])]]);
assert_eq!(
rows(&db, "SELECT CAST([[1], [2]] AS VARCHAR[][])"),
vec![vec![Value::List {
element: LogicalType::list(LogicalType::Varchar),
values: vec![
Value::List { element: LogicalType::Varchar, values: vec![text("1")] },
Value::List { element: LogicalType::Varchar, values: vec![text("2")] },
],
}]]
);
}
#[test]
fn a_try_cast_of_a_list_keeps_the_elements_that_went() {
let db = database();
assert_eq!(
rows(&db, "SELECT TRY_CAST(['x', '2'] AS INTEGER[])"),
vec![vec![Value::List {
element: LogicalType::Integer,
values: vec![Value::Null, integer(2)],
}]]
);
assert_eq!(
failure(&db, "SELECT CAST(['x'] AS INTEGER[])"),
"Could not convert string 'x' to INT32"
);
assert_eq!(
failure(&db, "SELECT CAST([1, 2] AS BLOB[])"),
"Unimplemented type for cast (INTEGER -> BLOB)"
);
assert_eq!(
failure(&db, "SELECT CAST([1, 2] AS INTEGER)"),
"Unimplemented type for cast (INTEGER[] -> INTEGER)"
);
}
#[test]
fn a_list_compares_element_by_element_and_then_by_length() {
let db = database();
let yes = vec![vec![Value::Boolean(true)]];
let no = vec![vec![Value::Boolean(false)]];
assert_eq!(rows(&db, "SELECT [1, 2] = [1, 2]"), yes);
assert_eq!(rows(&db, "SELECT [1, 2] = [1, 3]"), no);
assert_eq!(rows(&db, "SELECT [1, 2] < [1, 3]"), yes);
assert_eq!(rows(&db, "SELECT ['a'] < ['b']"), yes);
assert_eq!(rows(&db, "SELECT [[1], [2]] < [[1], [3]]"), yes);
assert_eq!(rows(&db, "SELECT [1, 2] < [1, 2, 3]"), yes);
assert_eq!(rows(&db, "SELECT [1, 2] = [1, 2, 3]"), no);
assert_eq!(rows(&db, "SELECT [] < [1]"), yes);
assert_eq!(rows(&db, "SELECT [] = []"), yes);
}
#[test]
fn a_null_inside_a_list_is_the_largest_element_and_a_null_list_is_still_null() {
let db = database();
let yes = vec![vec![Value::Boolean(true)]];
let no = vec![vec![Value::Boolean(false)]];
assert_eq!(rows(&db, "SELECT [1, NULL] = [1, NULL]"), yes);
assert_eq!(rows(&db, "SELECT [1, NULL] = [1, 2]"), no);
assert_eq!(rows(&db, "SELECT [1, NULL] > [1, 2]"), yes);
assert_eq!(rows(&db, "SELECT [NULL] < [1]"), no);
assert_eq!(rows(&db, "SELECT [1, 2] IS DISTINCT FROM [1, NULL]"), yes);
assert_eq!(rows(&db, "SELECT NULL::INT[] = [1]"), vec![vec![Value::Null]]);
assert_eq!(rows(&db, "SELECT NULL::INT[] IS NOT DISTINCT FROM NULL::INT[]"), yes);
}
#[test]
fn a_list_sorts_and_groups_and_maxes_like_any_other_value() {
let db = database();
db.execute("CREATE TABLE ls (x INTEGER[])").unwrap();
db.execute("INSERT INTO ls VALUES ([1, NULL]), ([1, 2]), ([NULL]), (NULL), ([])").unwrap();
assert_eq!(
rows(&db, "SELECT x FROM ls ORDER BY x"),
vec![
vec![list(&[])],
vec![list(&[1, 2])],
vec![Value::List {
element: LogicalType::Integer,
values: vec![integer(1), Value::Null],
}],
vec![Value::List { element: LogicalType::Integer, values: vec![Value::Null] }],
vec![Value::Null],
]
);
assert_eq!(
rows(&db, "SELECT max(x) FROM ls"),
vec![vec![Value::List { element: LogicalType::Integer, values: vec![Value::Null] }]]
);
assert_eq!(
rows(&db, "SELECT count(*) FROM (SELECT DISTINCT x FROM ls)"),
vec![vec![Value::BigInt(5)]]
);
assert_eq!(rows(&db, "SELECT [1, 2] IN ([1, 2], [3])"), vec![vec![Value::Boolean(true)]]);
}
#[test]
fn two_lists_concatenate_with_the_operator_and_with_the_name() {
let db = database();
assert_eq!(rows(&db, "SELECT [1, 2] || [3]"), vec![vec![list(&[1, 2, 3])]]);
assert_eq!(rows(&db, "SELECT [1] || [2] || [3]"), vec![vec![list(&[1, 2, 3])]]);
assert_eq!(rows(&db, "SELECT [] || [1]"), vec![vec![list(&[1])]]);
assert_eq!(rows(&db, "SELECT [1] || []"), vec![vec![list(&[1])]]);
assert_eq!(rows(&db, "SELECT typeof([1] || [2.5::DOUBLE])"), vec![vec![text("DOUBLE[]")]]);
assert_eq!(rows(&db, "SELECT typeof([1, 2] || [3.5])"), vec![vec![text("DECIMAL(11,1)[]")]]);
assert_eq!(rows(&db, "SELECT typeof([] || [])"), vec![vec![text("\"NULL\"[]")]]);
assert_eq!(
rows(&db, "SELECT [1] || [NULL]"),
vec![vec![Value::List {
element: LogicalType::Integer,
values: vec![integer(1), Value::Null],
}]]
);
assert_eq!(rows(&db, "SELECT list_concat([1], [2], [3])"), vec![vec![list(&[1, 2, 3])]]);
assert_eq!(rows(&db, "SELECT list_cat([1], [2])"), vec![vec![list(&[1, 2])]]);
assert_eq!(rows(&db, "SELECT array_concat([1], [2])"), vec![vec![list(&[1, 2])]]);
assert_eq!(rows(&db, "SELECT array_cat([1], [2])"), vec![vec![list(&[1, 2])]]);
db.execute("CREATE TABLE cs (a INTEGER[], b INTEGER[])").unwrap();
db.execute("INSERT INTO cs VALUES ([1], [2]), ([], [3, 4])").unwrap();
assert_eq!(
rows(&db, "SELECT a || b FROM cs ORDER BY 1"),
vec![vec![list(&[1, 2])], vec![list(&[3, 4])]]
);
}
#[test]
fn the_length_of_a_list_is_how_many_elements_it_has() {
let db = database();
let count = |sql: &str| rows(&db, sql);
assert_eq!(count("SELECT length([1, 2, 3])"), vec![vec![Value::BigInt(3)]]);
assert_eq!(count("SELECT length([])"), vec![vec![Value::BigInt(0)]]);
assert_eq!(count("SELECT length([1, NULL])"), vec![vec![Value::BigInt(2)]]);
assert_eq!(count("SELECT length([[1], [2]])"), vec![vec![Value::BigInt(2)]]);
assert_eq!(count("SELECT length(NULL::INT[])"), vec![vec![Value::Null]]);
assert_eq!(count("SELECT len([1, 2])"), vec![vec![Value::BigInt(2)]]);
assert_eq!(count("SELECT char_length([1, 2])"), vec![vec![Value::BigInt(2)]]);
assert_eq!(count("SELECT length('héllo')"), vec![vec![Value::BigInt(5)]]);
assert_eq!(count("SELECT length(NULL)"), vec![vec![Value::Null]]);
db.execute("CREATE TABLE ns (x INTEGER[])").unwrap();
db.execute("INSERT INTO ns VALUES ([1, 2]), ([]), (NULL)").unwrap();
assert_eq!(
rows(&db, "SELECT length(x) FROM ns"),
vec![vec![Value::BigInt(2)], vec![Value::BigInt(0)], vec![Value::Null]]
);
assert!(failure(&db, "SELECT length(123)").contains("length(col0 ANY[]) -> BIGINT"));
}
#[test]
fn array_length_counts_a_list_along_its_first_dimension_and_only_that_one() {
let db = database();
assert_eq!(rows(&db, "SELECT array_length([1, 2, 3])"), vec![vec![Value::BigInt(3)]]);
assert_eq!(rows(&db, "SELECT array_length([[1, 2], [3]], 1)"), vec![vec![Value::BigInt(2)]]);
assert_eq!(rows(&db, "SELECT array_length([1, 2, 3], NULL)"), vec![vec![Value::Null]]);
assert_eq!(rows(&db, "SELECT array_length(NULL, 1)"), vec![vec![Value::Null]]);
assert_eq!(
failure(&db, "SELECT array_length([1, 2], 2)"),
"array_length for lists with dimensions other than 1 not implemented"
);
for refused in ["SELECT array_length('abc')", "SELECT array_length([1, 2], 1.5)"] {
assert!(
failure(&db, refused).contains("array_length(col0 ANY[], col1 BIGINT) -> BIGINT"),
"{refused}"
);
}
}
#[test]
fn concatenating_a_list_with_something_that_is_not_one_is_refused() {
let db = database();
assert_eq!(
failure(&db, "SELECT [1, 2] || 3"),
"Cannot concatenate types INTEGER[] and INTEGER - an explicit cast is required"
);
assert_eq!(
failure(&db, "SELECT 'a' || ['b']"),
"Cannot concatenate types VARCHAR and VARCHAR[] - an explicit cast is required"
);
assert_eq!(
failure(&db, "SELECT [1] || ['a']"),
"Cannot concatenate lists of types INTEGER[] and VARCHAR[] - an explicit cast is required"
);
assert_eq!(
failure(&db, "SELECT list_concat([1], ['a'])"),
"Cannot concatenate lists of types INTEGER[] and VARCHAR[] - an explicit cast is required"
);
assert!(
failure(&db, "SELECT list_concat([1], 2)").contains("list_concat([ANY[]...]) -> ANY[]")
);
assert_eq!(rows(&db, "SELECT 1 || 'a'"), vec![vec![text("1a")]]);
}
#[test]
fn a_null_stops_the_operator_and_is_skipped_by_the_name() {
let db = database();
assert_eq!(rows(&db, "SELECT [1, 2] || NULL::INT[]"), vec![vec![Value::Null]]);
assert_eq!(rows(&db, "SELECT [1, 2] || NULL"), vec![vec![Value::Null]]);
assert_eq!(rows(&db, "SELECT list_concat([1], NULL::INT[])"), vec![vec![list(&[1])]]);
assert_eq!(rows(&db, "SELECT list_concat([1], NULL)"), vec![vec![list(&[1])]]);
assert_eq!(rows(&db, "SELECT typeof(list_concat([1], NULL))"), vec![vec![text("INTEGER[]")]]);
assert_eq!(rows(&db, "SELECT list_concat([1], [2], NULL, [3])"), vec![vec![list(&[1, 2, 3])]]);
assert_eq!(rows(&db, "SELECT list_concat(NULL, NULL)"), vec![vec![Value::Null]]);
assert_eq!(
rows(&db, "SELECT list_concat([], [])"),
vec![vec![Value::List { element: LogicalType::Null, values: Vec::new() }]]
);
}
#[test]
fn appending_to_a_list_is_concatenating_a_list_of_one() {
let db = database();
let one = |sql: &str| rows(&db, sql);
assert_eq!(one("SELECT list_append([1, 2], 3)"), vec![vec![list(&[1, 2, 3])]]);
assert_eq!(one("SELECT array_append([1, 2], 3)"), vec![vec![list(&[1, 2, 3])]]);
assert_eq!(one("SELECT array_push_back([1, 2], 3)"), vec![vec![list(&[1, 2, 3])]]);
assert_eq!(one("SELECT list_prepend(0, [1, 2])"), vec![vec![list(&[0, 1, 2])]]);
assert_eq!(one("SELECT array_prepend(0, [1])"), vec![vec![list(&[0, 1])]]);
assert_eq!(one("SELECT array_push_front([1], 0)"), vec![vec![list(&[0, 1])]]);
assert_eq!(one("SELECT list_append([], 1)"), vec![vec![list(&[1])]]);
assert_eq!(
one("SELECT typeof(list_append([1, 2], 3.5::DOUBLE))"),
vec![vec![text("DOUBLE[]")]]
);
assert_eq!(
one("SELECT list_append([[1]], [2])"),
vec![vec![Value::List {
element: LogicalType::list(LogicalType::Integer),
values: vec![list(&[1]), list(&[2])],
}]]
);
assert_eq!(one("SELECT list_append(NULL::INT[], 3)"), vec![vec![list(&[3])]]);
assert_eq!(one("SELECT list_append(NULL, 3)"), vec![vec![list(&[3])]]);
assert_eq!(one("SELECT list_prepend(1, NULL)"), vec![vec![list(&[1])]]);
assert_eq!(
one("SELECT list_append([1, 2], NULL)"),
vec![vec![Value::List {
element: LogicalType::Integer,
values: vec![integer(1), integer(2), Value::Null],
}]]
);
assert_eq!(
failure(&db, "SELECT list_append([1], 'x'::VARCHAR)"),
"Cannot concatenate lists of types INTEGER[] and VARCHAR[] - an explicit cast is required"
);
assert_eq!(
failure(&db, "SELECT list_append([1])"),
"Macro list_append() does not support the supplied arguments. You might need to add \
explicit type casts.\nCandidate macros:\n\tlist_append(l, e)"
);
db.execute("CREATE TABLE ap (x INTEGER[], y INTEGER)").unwrap();
db.execute("INSERT INTO ap VALUES ([1], 2), (NULL, 3)").unwrap();
assert_eq!(
rows(&db, "SELECT list_append(x, y) FROM ap"),
vec![vec![list(&[1, 2])], vec![list(&[3])]]
);
}
#[test]
fn a_lambda_runs_over_every_element() {
let db = database();
let one = |sql: &str| rows(&db, sql);
let bigints = |values: &[i64]| Value::List {
element: LogicalType::BigInt,
values: values.iter().map(|&v| Value::BigInt(v)).collect(),
};
assert_eq!(
one("SELECT list_transform([1, 2, 3], lambda x: x + 1)"),
vec![vec![list(&[2, 3, 4])]]
);
assert_eq!(
db.query("SELECT list_transform([1, 2, 3], lambda x: x + 1)").unwrap().names(),
["list_transform(list_value(1, 2, 3), (lambda x: (x + 1)))"]
);
assert_eq!(
db.query("SELECT list_transform([1, 2], lambda X: x + 1)").unwrap().names(),
["list_transform(list_value(1, 2), (lambda X: (x + 1)))"]
);
assert_eq!(
one("SELECT list_transform([1, 2, 3], lambda x, i: x * i)"),
vec![vec![bigints(&[1, 4, 9])]]
);
assert_eq!(one("SELECT list_filter([1, 2, 3, 4], lambda x: x % 2)"), vec![vec![list(&[1, 3])]]);
assert_eq!(one("SELECT list_filter([1, 2, NULL], lambda x: NULL)"), vec![vec![list(&[])]]);
assert_eq!(
one("SELECT apply([1, 2], lambda x: x::VARCHAR)"),
vec![vec![Value::List {
element: LogicalType::Varchar,
values: vec![text("1"), text("2")]
}]]
);
assert_eq!(
one("SELECT typeof(list_transform([1, 2], lambda x: x::DOUBLE))"),
vec![vec![text("DOUBLE[]")]]
);
assert_eq!(one("SELECT list_transform(NULL, lambda x: x)"), vec![vec![Value::Null]]);
assert_eq!(
one("SELECT typeof(list_transform(NULL, lambda x: x))"),
vec![vec![text("\"NULL\"")]]
);
assert_eq!(one("SELECT list_transform([], lambda x: x + 1)"), vec![vec![list(&[])]]);
assert_eq!(
one("SELECT list_transform([[1, 2], [3]], lambda x: list_transform(x, lambda y: y * 10))"),
vec![vec![Value::List {
element: LogicalType::list(LogicalType::Integer),
values: vec![list(&[10, 20]), list(&[30])],
}]]
);
assert_eq!(
one("SELECT list_transform([1, 2], lambda x: list_transform([10, 20], lambda y: x + y))"),
vec![vec![Value::List {
element: LogicalType::list(LogicalType::Integer),
values: vec![list(&[11, 21]), list(&[12, 22])],
}]]
);
let long: Vec<String> = (1..=3000).map(|n| n.to_string()).collect();
assert_eq!(
one(&format!("SELECT list_transform([{}], lambda x, i: i)[2999:3000]", long.join(", "))),
vec![vec![bigints(&[2999, 3000])]]
);
assert_eq!(
one(&format!("SELECT list_filter([{}], lambda x: x > 2998)", long.join(", "))),
vec![vec![list(&[2999, 3000])]]
);
}
#[test]
fn a_lambda_reads_the_columns_of_its_row() {
let db = database();
db.execute("CREATE TABLE lt (l INTEGER[], k INTEGER)").unwrap();
db.execute("INSERT INTO lt VALUES ([1, 2], 10), (NULL, 20), ([], 30), ([3], 40)").unwrap();
assert_eq!(
rows(&db, "SELECT list_transform(l, lambda x: x + k) FROM lt"),
vec![vec![list(&[11, 12])], vec![Value::Null], vec![list(&[])], vec![list(&[43])]]
);
assert_eq!(
rows(&db, "SELECT list_filter(l, lambda x, i: i > 1) FROM lt"),
vec![vec![list(&[2])], vec![Value::Null], vec![list(&[])], vec![list(&[])]]
);
assert_eq!(
rows(&db, "SELECT list_transform([1, 2], lambda x: x + sum(k)) FROM lt"),
vec![vec![Value::List {
element: LogicalType::HugeInt,
values: vec![Value::HugeInt(101), Value::HugeInt(102)],
}]]
);
}
#[test]
fn a_lambda_is_refused_the_way_the_pin_refuses_it() {
let db = database();
assert_eq!(
failure(&db, "SELECT list_transform([1, 0], lambda x: 10 // (x - 1))"),
"Division by zero in expression (10 // (x - 1)). Use TRY(...) to return NULL for this \
expression, or SET null_on_division_by_zero=true to return NULL for all divisions by zero."
);
assert_eq!(
failure(&db, "SELECT list_transform([1], lambda x, y, z: x)"),
"This lambda function only supports up to two lambda parameters!"
);
assert_eq!(
failure(&db, "SELECT list_transform([1], lambda x, x: x)"),
"table \"0_macro_parameters(x, x)\" has duplicate column name \"x\""
);
assert_eq!(
failure(&db, "SELECT list_transform(1, lambda x: x)"),
"Invalid LIST argument during lambda function binding!"
);
assert_eq!(
failure(&db, "SELECT list_transform([1])"),
"No function matches the given name and argument types 'list_transform(INTEGER[])'. You \
might need to add explicit type casts.\n\tCandidate functions:\n\tlist_transform(col0 \
ANY[], col1 LAMBDA) -> ANY[]\n"
);
assert_eq!(
failure(&db, "SELECT list_transform([1], x -> x)"),
"Deprecated lambda arrow (->) detected. Please transition to the new lambda syntax, i.e.., \
lambda x, i: x + i, before DuckDB's next release.\nUse SET \
lambda_syntax='ENABLE_SINGLE_ARROW' to revert to the deprecated behavior.\nFor more \
information, see https://duckdb.org/docs/current/sql/functions/lambda.html."
);
assert_eq!(
failure(&db, "SELECT abs(lambda x: x)"),
"This scalar function does not support lambdas!"
);
assert_eq!(
failure(&db, "SELECT list_transform([1], lambda x: (SELECT 1))"),
"subqueries in lambda expressions are not supported"
);
}
#[test]
fn a_reduction_folds_a_list_into_one_value() {
let db = database();
let one = |sql: &str| rows(&db, sql);
assert_eq!(one("SELECT list_reduce([1, 2, 3], lambda x, y: x + y)"), vec![vec![integer(6)]]);
assert_eq!(
db.query("SELECT list_reduce([1, 2, 3], lambda x, y: x + y)").unwrap().names(),
["list_reduce(list_value(1, 2, 3), (lambda x, y: (x + y)))"]
);
assert_eq!(
one("SELECT list_reduce([1, 2, 3], lambda x, y, i: x + y * i)"),
vec![vec![Value::BigInt(14)]]
);
assert_eq!(
one("SELECT list_reduce([1, 2, 3], lambda x, y: x + y, 100)"),
vec![vec![integer(106)]]
);
assert_eq!(
one("SELECT list_reduce([1, 2, 3], lambda x, y: x || y::VARCHAR, '')"),
vec![vec![text("123")]]
);
assert_eq!(one("SELECT list_reduce(['a', 'b'], lambda x, y: x || y)"), vec![vec![text("ab")]]);
assert_eq!(one("SELECT list_reduce([5], lambda x, y: x + y)"), vec![vec![integer(5)]]);
assert_eq!(one("SELECT list_reduce([]::INT[], lambda x, y: x + y, 7)"), vec![vec![integer(7)]]);
assert_eq!(
one("SELECT list_reduce([1, NULL, 3], lambda x, y: x + y)"),
vec![vec![Value::Null]]
);
assert_eq!(one("SELECT list_reduce(NULL, lambda x, y: x + y)"), vec![vec![Value::Null]]);
assert_eq!(
one("SELECT typeof(list_reduce(NULL, lambda x, y: x + y))"),
vec![vec![text("\"NULL\"")]]
);
assert_eq!(
one("SELECT list_reduce([1, 2], lambda x, y: x + y, NULL)"),
vec![vec![Value::Null]]
);
assert_eq!(
one("SELECT typeof(list_reduce([1, 2], lambda x, y: x + y, NULL))"),
vec![vec![text("INTEGER")]]
);
assert_eq!(
one("SELECT list_reduce([1, 2, 3], lambda x, y, i: i)"),
vec![vec![Value::BigInt(3)]]
);
assert_eq!(
one("SELECT list_reduce([1, 2, 3], lambda x, y, i: i, 0)"),
vec![vec![Value::BigInt(3)]]
);
assert_eq!(one("SELECT list_reduce([1, 2, 3], lambda x, y: x > y)"), vec![vec![integer(0)]]);
assert_eq!(
one("SELECT typeof(list_reduce([1, 2], lambda x, y: x > y))"),
vec![vec![text("INTEGER")]]
);
for (sql, ty, answer) in [
("list_reduce([1.5, 2, 3], lambda x, y: x + y)", "DECIMAL(13,1)", "6.5"),
("list_reduce([1, 2, 3], lambda x, y: x + y, 1.5)", "DECIMAL(12,1)", "7.5"),
("list_reduce([1000, 2000, 3000], lambda x, y: x + y, 1.5)", "DECIMAL(12,1)", "6001.5"),
("list_reduce([1, 2, 3], lambda x, y: x * 1.5)", "DECIMAL(14,2)", "2.25"),
("list_reduce([1, 2, 3], lambda x, y: x + y + 0.5)", "DECIMAL(14,1)", "7.0"),
] {
assert_eq!(one(&format!("SELECT typeof({sql})")), vec![vec![text(ty)]], "{sql}");
assert_eq!(one(&format!("SELECT ({sql})::VARCHAR")), vec![vec![text(answer)]], "{sql}");
}
assert_eq!(
one("SELECT list_reduce([[1], [2, 3]], lambda x, y: list_concat(x, y))"),
vec![vec![list(&[1, 2, 3])]]
);
assert_eq!(
one("SELECT array_reduce([1, 2], lambda x, y: x * y), reduce([1, 2], lambda x, y: x * y)"),
vec![vec![integer(2), integer(2)]]
);
let long: Vec<String> = (1..=3000).map(|n| n.to_string()).collect();
assert_eq!(
one(&format!("SELECT list_reduce([{}], lambda x, y: x + y)", long.join(", "))),
vec![vec![integer(4_501_500)]]
);
}
#[test]
fn a_reduction_runs_per_row() {
let db = database();
db.execute("CREATE TABLE lr (l INTEGER[], k INTEGER)").unwrap();
db.execute("INSERT INTO lr VALUES ([1, 2], 10), (NULL, 20), ([], 30), ([3], 40)").unwrap();
assert_eq!(
rows(&db, "SELECT list_reduce(l, lambda x, y: x + y, k) FROM lr"),
vec![vec![integer(13)], vec![Value::Null], vec![integer(30)], vec![integer(43)]]
);
assert_eq!(
db.query("SELECT list_reduce(l, lambda x, y: x + y, k) FROM lr").unwrap().names(),
["list_reduce(l, (lambda x, y: (x + y)), k)"]
);
assert_eq!(
rows(&db, "SELECT list_reduce(l, lambda x, y: x + y, NULL::INT) FROM lr"),
vec![vec![Value::Null]; 4]
);
assert_eq!(
rows(&db, "SELECT list_reduce(l, lambda x, y: x + y + k) FROM lr WHERE k <> 30"),
vec![vec![integer(13)], vec![Value::Null], vec![integer(3)]]
);
let error = db.query("SELECT list_reduce(l, lambda x, y: x + y + k) FROM lr").unwrap_err();
assert_eq!(error.code(), rudb_common::ErrorCode::ParameterNotAllowed);
assert_eq!(error.message(), "Cannot perform list_reduce on an empty input list");
}
#[test]
fn a_reduction_is_refused_the_way_the_pin_refuses_it() {
let db = database();
assert_eq!(
failure(&db, "SELECT list_reduce([], lambda x, y: x + y)"),
"Cannot perform list_reduce on an empty input list"
);
assert_eq!(
failure(&db, "SELECT list_reduce([1, 2], lambda x: x)"),
"list_reduce expects a function with 2 or 3 arguments"
);
assert_eq!(
failure(&db, "SELECT list_reduce([1, 2], lambda x, y, z, w: x)"),
"This lambda function only supports up to three lambda parameters!"
);
assert_eq!(
failure(&db, "SELECT list_reduce([1, 2])"),
"No function matches the given name and argument types 'list_reduce(INTEGER[])'. You \
might need to add explicit type casts.\n\tCandidate functions:\n\tlist_reduce(col0 \
ANY[], col1 LAMBDA) -> ANY\n\tlist_reduce(col0 ANY[], col1 LAMBDA, col2 ANY) -> ANY\n"
);
assert_eq!(
failure(&db, "SELECT list_reduce([1, 2], lambda x, y: [x, y])"),
"No common super type between list element type INTEGER and lambda return type INTEGER[]"
);
assert_eq!(
failure(&db, "SELECT list_reduce([1, 2], lambda x, y: [x], [0])"),
"No common super type between initial value type INTEGER[] and lambda return type \
INTEGER[][]"
);
assert_eq!(
failure(&db, "SELECT list_reduce([1, 0], lambda x, y: x // y)"),
"Division by zero in expression (x // y). Use TRY(...) to return NULL for this \
expression, or SET null_on_division_by_zero=true to return NULL for all divisions by zero."
);
assert_eq!(
failure(&db, "SELECT list_reduce([100, 100, 100]::TINYINT[], lambda x, y: x + y)"),
"Overflow in addition of INT8 (100 + 100)!"
);
}
#[test]
fn an_invoked_lambda_runs_over_its_arguments() {
let db = database();
let one = |sql: &str| rows(&db, sql);
assert_eq!(one("SELECT invoke(lambda x: x * x, 2)"), vec![vec![integer(4)]]);
assert_eq!(
db.query("SELECT invoke(lambda x: x * x, 2)").unwrap().names(),
["invoke((lambda x: (x * x)), 2)"]
);
assert_eq!(one("SELECT invoke(lambda x, y: x * y + y, 3, 4)"), vec![vec![integer(16)]]);
assert_eq!(
one("SELECT invoke(lambda x, y, z, w, v: x + v, 1, 2, 3, 4, 5)"),
vec![vec![integer(6)]]
);
assert_eq!(
one("SELECT invoke(lambda a: invoke(lambda b, c: a + b + c, 4, 5), 3)"),
vec![vec![integer(12)]]
);
assert_eq!(
one("SELECT invoke(lambda x: invoke(lambda y: y + 5, x + 3), 2)"),
vec![vec![integer(10)]]
);
assert_eq!(
one("SELECT invoke(lambda name: invoke(lambda age: name || age, 30), 'Alice, ')"),
vec![vec![text("Alice, 30")]]
);
assert_eq!(
one("SELECT invoke(lambda x: list_transform([10, 20], lambda y, i: x + y + i), 100)"),
vec![vec![Value::List {
element: LogicalType::BigInt,
values: vec![Value::BigInt(111), Value::BigInt(122)]
}]]
);
assert_eq!(one("SELECT invoke(lambda x: x IS NULL, NULL)"), vec![vec![Value::Boolean(true)]]);
assert_eq!(one("SELECT typeof(invoke(lambda x: x, NULL))"), vec![vec![text("\"NULL\"")]]);
assert_eq!(
one("SELECT typeof(invoke(lambda x: x, [1, 2]::INT[2]))"),
vec![vec![text("INTEGER[2]")]]
);
db.execute("CREATE TABLE ti AS SELECT * FROM (VALUES (1, 10), (2, NULL), (NULL, 30)) t(a, b)")
.unwrap();
assert_eq!(
one("SELECT invoke(lambda x, y: coalesce(x, 0) + coalesce(y, 0) + a, a, b) FROM ti"),
vec![vec![integer(12)], vec![integer(4)], vec![Value::Null]]
);
assert_eq!(
one(
"SELECT invoke(lambda x: x + sum(a), 1)::INTEGER, typeof(invoke(lambda x: x + sum(a), 1)) FROM ti"
),
vec![vec![integer(4), text("HUGEINT")]]
);
assert_eq!(
one("SELECT i FROM range(0, 10) r(i) WHERE invoke(lambda x: x, i % 2 = 0) ORDER BY i"),
[0, 2, 4, 6, 8].map(|i| vec![Value::BigInt(i)])
);
let long: Vec<Vec<Value>> = (0..3000).map(|i| vec![Value::BigInt(i * 2)]).collect();
assert_eq!(one("SELECT invoke(lambda x: x * 2, i) FROM range(3000) r(i)"), long);
}
#[test]
fn an_invoked_lambda_is_refused_the_way_the_pin_refuses_it() {
let db = database();
let candidates = "You might need to add explicit type casts.\n\tCandidate functions:\n\tinvoke(col0 \
LAMBDA, col1 ANY, [ANY...]) -> ANY\n";
assert_eq!(
failure(&db, "SELECT invoke()"),
format!("No function matches the given name and argument types 'invoke()'. {candidates}")
);
assert_eq!(
failure(&db, "SELECT invoke(NULL)"),
format!(
"No function matches the given name and argument types 'invoke(\"NULL\")'. {candidates}"
)
);
assert_eq!(
failure(&db, "SELECT invoke(NULL, 1)"),
"Invalid lambda expression passed to 'invoke' function."
);
assert_eq!(
failure(&db, "SELECT invoke(1, lambda x: x)"),
"This scalar function requires a lambda expression!"
);
assert_eq!(
failure(&db, "SELECT invoke(lambda x: x + x, 2, 4, 6)"),
"The number of lambda parameters does not match the number of arguments passed to the \
'invoke' function, expected 1, got 3."
);
assert_eq!(
failure(&db, "SELECT invoke(lambda x, y, z: x, 1)"),
"The number of lambda parameters does not match the number of arguments passed to the \
'invoke' function, expected at least 2, got 1."
);
assert_eq!(
failure(&db, "SELECT invoke(lambda x: x + 1)"),
"The number of lambda parameters does not match the number of arguments passed to the \
'invoke' function, expected at least 1, got 0."
);
assert_eq!(
failure(&db, "SELECT invoke(lambda x, x: x, 1, 2)"),
"table \"0_macro_parameters(x, x)\" has duplicate column name \"x\""
);
assert!(
failure(&db, "SELECT invoke(lambda x: x::INTEGER, 'abc')")
.starts_with("Could not convert string 'abc' to INT32")
);
assert_eq!(
failure(&db, "SELECT invoke(lambda x: x * 2, 9223372036854775807::BIGINT)"),
"Overflow in multiplication of INT64 (9223372036854775807 * 2)!"
);
}
#[test]
fn a_null_struct_is_a_value_now_rather_than_an_unwritten_vector() {
let db = database();
assert_eq!(rows(&db, "SELECT NULL::STRUCT(a INT)"), vec![vec![Value::Null]]);
assert_eq!(
rows(&db, "SELECT typeof(NULL::STRUCT(a INT, b VARCHAR))"),
vec![vec![Value::Varchar("STRUCT(a INTEGER, b VARCHAR)".to_string())]]
);
db.execute("CREATE TABLE structs (a STRUCT(x INTEGER, y VARCHAR))").unwrap();
assert_eq!(rows(&db, "SELECT count(*) FROM structs"), vec![vec![Value::BigInt(0)]]);
assert!(rows(&db, "SELECT a FROM structs").is_empty());
}
#[test]
fn a_null_map_is_a_value_now_rather_than_an_unwritten_vector() {
let db = database();
assert_eq!(rows(&db, "SELECT NULL::MAP(VARCHAR, VARCHAR)"), vec![vec![Value::Null]]);
assert_eq!(
rows(&db, "SELECT typeof(NULL::MAP(INT, INT))"),
vec![vec![Value::Varchar("MAP(INTEGER, INTEGER)".to_string())]]
);
db.execute("CREATE TABLE maps (tags MAP(VARCHAR, VARCHAR))").unwrap();
assert_eq!(rows(&db, "SELECT count(*) FROM maps"), vec![vec![Value::BigInt(0)]]);
assert!(rows(&db, "SELECT tags FROM maps").is_empty());
}
#[test]
fn the_types_table_answers_a_query_a_client_would_actually_write() {
let db = database();
assert_eq!(rows(&db, "SELECT count(*) FROM duckdb_types()"), vec![vec![Value::BigInt(93)]]);
assert_eq!(
rows(&db, "SELECT logical_type, type_size FROM duckdb_types() WHERE type_name = 'hugeint'"),
vec![vec![Value::Varchar("HUGEINT".to_string()), Value::BigInt(16)]]
);
assert_eq!(
rows(&db, "SELECT DISTINCT database_name, schema_name FROM DuckDB_Types()"),
vec![vec![Value::Varchar("memory".to_string()), Value::Varchar("main".to_string())]]
);
assert_eq!(
rows(&db, "SELECT tags FROM duckdb_types() WHERE type_name = 'boolean'"),
vec![vec![Value::map(LogicalType::Varchar, LogicalType::Varchar, Vec::new())]]
);
}
#[test]
fn the_functions_table_answers_the_question_a_client_asks_it() {
let db = database();
assert_eq!(
rows(&db, "SELECT count(*) FROM duckdb_functions() WHERE function_name = 'sqrt'"),
vec![vec![Value::BigInt(0)]]
);
assert_eq!(
rows(&db, "SELECT function_type FROM duckdb_functions() WHERE function_name = 'avg'"),
vec![vec![Value::Varchar("aggregate".to_string())]]
);
assert_eq!(
rows(&db, "SELECT DISTINCT database_name, schema_name FROM duckdb_functions()"),
vec![vec![Value::Varchar("system".to_string()), Value::Varchar("main".to_string())]]
);
assert!(
rows(&db, "SELECT function_name FROM duckdb_functions() WHERE NOT internal").is_empty()
);
assert_eq!(
rows(&db, "SELECT count(*) FROM duckdb_functions() WHERE function_name LIKE 'duckdb_%'"),
vec![vec![Value::BigInt(13)]]
);
}
#[test]
fn the_session_context_answers_for_the_clock_the_catalog_and_the_user() {
let db = database();
db.execute("SET TimeZone = 'UTC'").expect("a stable zone for written timestamp answers");
let text = |value: &str| Value::Varchar(value.to_string());
assert_eq!(
rows(
&db,
"SELECT typeof(now()), typeof(current_timestamp), typeof(get_current_timestamp()), \
typeof(transaction_timestamp()), typeof(current_localtimestamp()), \
typeof(localtimestamp)"
),
vec![vec![
text("TIMESTAMP WITH TIME ZONE"),
text("TIMESTAMP WITH TIME ZONE"),
text("TIMESTAMP WITH TIME ZONE"),
text("TIMESTAMP WITH TIME ZONE"),
text("TIMESTAMP"),
text("TIMESTAMP"),
]]
);
assert_eq!(
rows(
&db,
"SELECT typeof(current_date), typeof(today()), typeof(current_time), \
typeof(get_current_time()), typeof(localtime), typeof(current_localtime())"
),
vec![vec![
text("DATE"),
text("DATE"),
text("TIME WITH TIME ZONE"),
text("TIME WITH TIME ZONE"),
text("TIME"),
text("TIME"),
]]
);
assert_eq!(
rows(
&db,
"SELECT current_schema, current_schema(), current_catalog, current_database(), \
current_user, session_user, user"
),
vec![vec![
text("main"),
text("main"),
text("memory"),
text("memory"),
text("duckdb"),
text("duckdb"),
text("duckdb"),
]]
);
assert_eq!(
rows(
&db,
"SELECT now() = current_timestamp, now() = transaction_timestamp(), \
now() = get_current_timestamp(), today() = current_date"
),
vec![vec![
Value::Boolean(true),
Value::Boolean(true),
Value::Boolean(true),
Value::Boolean(true),
]]
);
assert_eq!(
rows(&db, "SELECT count(DISTINCT n) FROM (SELECT now() AS n FROM range(3))"),
vec![vec![Value::BigInt(1)]]
);
assert_eq!(
rows(&db, "SELECT now() > TIMESTAMP '2024-01-01', current_date > DATE '2024-01-01'"),
vec![vec![Value::Boolean(true), Value::Boolean(true)]]
);
db.execute(
"CREATE TABLE context(current_date VARCHAR, current_user VARCHAR, \"user\" VARCHAR)",
)
.expect("three columns named after keywords");
db.execute("INSERT INTO context VALUES ('a', 'b', 'c')").expect("one row");
assert_eq!(
rows(&db, "SELECT current_date, current_user, user FROM context"),
vec![vec![text("a"), text("b"), text("c")]]
);
assert_eq!(
failure(&db, "SELECT current_date FROM context, context AS again"),
"Ambiguous reference to column name \"current_date\" (use: 'context.current_date' or \
'again.current_date')"
);
assert_eq!(
failure(&db, "SELECT current_database"),
"Referenced column \"current_database\" not found in FROM clause!"
);
assert_eq!(
failure(&db, "SELECT current_timestamp()"),
"Scalar Function with name current_timestamp does not exist!"
);
assert!(
failure(&db, "SELECT now(1)")
.starts_with("No function matches the given name and argument types 'now(INTEGER)'"),
"an arity error rather than a missing function"
);
assert_eq!(
rows(
&db,
"SELECT typeof(now() + INTERVAL 1 DAY), typeof(INTERVAL 1 DAY + now()), \
typeof(now() - now()), typeof(now() - TIMESTAMP '2020-01-01'), \
typeof(now() - DATE '2020-01-01'), typeof(now() + NULL), \
typeof(current_time + INTERVAL 1 HOUR), typeof(current_date + current_time)"
),
vec![vec![
text("TIMESTAMP WITH TIME ZONE"),
text("TIMESTAMP WITH TIME ZONE"),
text("INTERVAL"),
text("INTERVAL"),
text("INTERVAL"),
text("TIMESTAMP WITH TIME ZONE"),
text("TIME WITH TIME ZONE"),
text("TIMESTAMP WITH TIME ZONE"),
]]
);
assert_eq!(
rows(
&db,
"SELECT typeof(date_part('year', now())), typeof(date_trunc('day', now())), \
typeof(age(now(), now())), \
CAST(date_trunc('day', TIMESTAMPTZ '2020-01-02 03:04:05') AS VARCHAR), \
CAST(TIMESTAMPTZ '2020-01-31 10:00:00' + INTERVAL 1 MONTH AS VARCHAR), \
CAST(DATE '2020-01-02' + TIMETZ '03:04:05' AS VARCHAR)"
),
vec![vec![
text("BIGINT"),
text("TIMESTAMP WITH TIME ZONE"),
text("INTERVAL"),
text("2020-01-02 00:00:00+00"),
text("2020-02-29 10:00:00+00"),
text("2020-01-02 03:04:05+00"),
]]
);
assert_eq!(
rows(
&db,
"SELECT count(DISTINCT function_name) FROM duckdb_functions() WHERE function_name IN \
('now', 'today', \
'get_current_timestamp', 'get_current_time', 'transaction_timestamp', \
'current_localtime', 'current_localtimestamp', 'current_date', 'current_schema', \
'current_database', 'current_catalog', 'current_user', 'session_user', 'user')"
),
vec![vec![Value::BigInt(14)]]
);
}
#[test]
fn the_session_time_zone_moves_local_context_and_one_argument_age() {
let db = database();
let default_zone = db.setting("TimeZone").expect("the operating-system zone");
db.execute("SET TimeZone = 'America/New_York'").expect("an IANA zone");
assert_eq!(
rows(&db, "SELECT current_setting('timezone')"),
vec![vec![Value::Varchar("America/New_York".to_string())]]
);
let answer = rows(&db, "SELECT now(), localtimestamp, current_time, localtime");
let [
Value::TimestampTz(utc),
Value::Timestamp(local),
Value::TimeTz(zoned_time),
Value::Time(local_time),
] = &answer[0][..]
else {
panic!("the four context values had the wrong types: {:?}", answer[0]);
};
assert_eq!(local - utc, -4 * 60 * 60 * 1_000_000, "New York is EDT in September");
assert_eq!(zoned_time, local_time);
assert_eq!(
rows(
&db,
"SELECT CAST(stamp AS VARCHAR) FROM (VALUES \
(TIMESTAMPTZ '2020-01-01 12:00:00+00'), \
(TIMESTAMPTZ '2020-07-01 12:00:00+00')) AS t(stamp)"
),
vec![
vec![Value::Varchar("2020-01-01 07:00:00-05".to_string())],
vec![Value::Varchar("2020-07-01 08:00:00-04".to_string())],
]
);
assert_eq!(
rows(&db, "VALUES (CAST(TIMESTAMPTZ '2020-07-01 12:00:00+00' AS VARCHAR))"),
vec![vec![Value::Varchar("2020-07-01 08:00:00-04".to_string())]]
);
assert_eq!(
rows(
&db,
"SELECT age(DATE '2020-02-28') = age(current_date, DATE '2020-02-28'), \
age(TIMESTAMP '2020-02-28 12:00:00') = \
age(current_date, TIMESTAMP '2020-02-28 12:00:00')"
),
vec![vec![Value::Boolean(true), Value::Boolean(true)]]
);
db.execute("SET TIME ZONE 'Asia/Kathmandu'").expect("the standard spelling");
assert_eq!(db.setting("TimeZone").expect("the zone"), "Asia/Kathmandu");
db.execute("RESET TimeZone").expect("the default zone");
assert_eq!(db.setting("timezone").expect("case is ignored"), default_zone);
db.execute("SET TimeZone = 'UTC'").expect("a different zone");
db.execute("SET TIME ZONE LOCAL").expect("the local zone");
assert_eq!(db.setting("TimeZone").expect("the local zone"), default_zone);
let error = db.execute("SET TimeZone = 'not/a_zone'").expect_err("an unknown zone");
assert_eq!(error.code().duckdb_name(), "Not implemented Error");
assert!(error.message().starts_with("Unknown TimeZone 'not/a_zone'!"), "{error}");
}
#[test]
fn the_session_sort_defaults_are_resolved_into_each_sort_key() {
let db = database();
db.execute("CREATE TABLE sort_defaults(x INTEGER)").expect("a nullable column");
db.execute("INSERT INTO sort_defaults VALUES (1), (NULL), (2)").expect("three rows");
let one = |value| vec![Value::Integer(value)];
let null = vec![Value::Null];
assert_eq!(
rows(&db, "SELECT x FROM sort_defaults ORDER BY x DESC"),
vec![one(2), one(1), null.clone()]
);
db.execute("SET default_order = 'descending'").expect("the descending default");
assert_eq!(db.setting("default_order").expect("the direction"), "DESC");
assert_eq!(
rows(&db, "SELECT x FROM sort_defaults ORDER BY x"),
vec![one(2), one(1), null.clone()]
);
db.execute("SET default_null_order = 'first'").expect("nulls first");
assert_eq!(
rows(&db, "SELECT x FROM sort_defaults ORDER BY x"),
vec![null.clone(), one(2), one(1)]
);
db.execute("SET default_null_order = 'sqlite'").expect("the SQLite convention");
assert_eq!(
rows(&db, "SELECT x FROM sort_defaults ORDER BY x ASC"),
vec![null.clone(), one(1), one(2)]
);
assert_eq!(
rows(&db, "SELECT x FROM sort_defaults ORDER BY x DESC"),
vec![one(2), one(1), null.clone()]
);
db.execute("SET default_null_order = 'postgres'").expect("the PostgreSQL convention");
assert_eq!(
rows(&db, "SELECT x FROM sort_defaults ORDER BY x DESC"),
vec![null, one(2), one(1)]
);
db.execute("RESET default_order").expect("the direction default");
db.execute("RESET default_null_order").expect("the null default");
assert_eq!(db.setting("default_order").expect("the direction"), "ASCENDING");
assert_eq!(db.setting("default_null_order").expect("the null order"), "NULLS_LAST");
}
#[test]
fn integer_division_is_resolved_while_the_expression_is_bound() {
let db = database();
assert_eq!(
rows(&db, "SELECT 7 / 2, typeof(7 / 2)"),
vec![vec![Value::Double(3.5), text("DOUBLE")]]
);
db.execute("SET integer_division = true").expect("integer division");
assert_eq!(db.setting("integer_division").expect("the setting"), "true");
assert_eq!(
rows(&db, "SELECT 7 / 2, typeof(7 / 2), 7.5 / 2.0, typeof(7.5 / 2.0)"),
vec![vec![Value::Integer(3), text("INTEGER"), Value::Double(3.75), text("DOUBLE")]]
);
assert_eq!(db.query("SELECT 7 / 2").expect("a division").names(), ["(7 // 2)".to_string()]);
db.execute("RESET integer_division").expect("floating point division");
assert_eq!(db.setting("integer_division").expect("the setting"), "false");
assert_eq!(rows(&db, "SELECT 7 / 2"), vec![vec![Value::Double(3.5)]]);
let error = db.execute("SET integer_division = 'off'").expect_err("not a boolean");
assert_eq!(error.message(), "Failed to cast value: Could not convert string 'off' to BOOL");
}
#[test]
fn zero_division_nulls_are_resolved_while_the_expression_is_bound() {
let db = database();
assert_eq!(db.setting("null_on_division_by_zero").expect("the setting"), "false");
assert!(db.query("SELECT 1 // 0").is_err());
db.execute("SET null_on_division_by_zero = true").expect("nulling division errors");
assert_eq!(db.setting("null_on_division_by_zero").expect("the setting"), "true");
assert_eq!(
rows(
&db,
"SELECT current_setting('null_on_division_by_zero'), typeof(current_setting('null_on_division_by_zero'))"
),
vec![vec![Value::Boolean(true), text("BOOLEAN")]]
);
assert_eq!(
rows(
&db,
"SELECT 1 / 0, 1 // 0, 1 % 0, 1.0 // 0.0, 1.0 % 0.0, CAST(1 AS FLOAT) / CAST(0 AS FLOAT)"
),
vec![vec![
Value::Double(f64::INFINITY),
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Float(f32::INFINITY),
]]
);
assert_eq!(
rows(&db, "SELECT a, 10 // a, 10 % a FROM range(-1, 2) t(a)"),
vec![
vec![Value::BigInt(-1), Value::BigInt(-10), Value::BigInt(0)],
vec![Value::BigInt(0), Value::Null, Value::Null],
vec![Value::BigInt(1), Value::BigInt(10), Value::BigInt(0)],
]
);
db.execute("RESET null_on_division_by_zero").expect("division errors");
assert_eq!(db.setting("null_on_division_by_zero").expect("the setting"), "false");
assert!(db.query("SELECT 1 // 0").is_err());
let error = db.execute("SET null_on_division_by_zero = 'off'").expect_err("not a boolean");
assert_eq!(error.message(), "Failed to cast value: Could not convert string 'off' to BOOL");
}
#[test]
fn ieee_floating_point_ops_are_resolved_while_the_expression_is_bound() {
let db = database();
assert_eq!(db.setting("ieee_floating_point_ops").expect("the setting"), "true");
assert!(
matches!(rows(&db, "SELECT 0.0::DOUBLE / 0.0::DOUBLE")[0][0], Value::Double(answer) if answer.is_nan())
);
assert!(
matches!(rows(&db, "SELECT 1.0::DOUBLE % 0.0::DOUBLE")[0][0], Value::Double(answer) if answer.is_nan())
);
db.execute("SET ieee_floating_point_ops = false").expect("checked floating operations");
assert_eq!(db.setting("ieee_floating_point_ops").expect("the setting"), "false");
assert_eq!(
rows(
&db,
"SELECT current_setting('ieee_floating_point_ops'), typeof(current_setting('ieee_floating_point_ops'))"
),
vec![vec![Value::Boolean(false), text("BOOLEAN")]]
);
let advice = "Use TRY(...) to return NULL for this expression, or SET null_on_division_by_zero=true to return NULL for all divisions by zero.";
assert_eq!(
failure(&db, "SELECT 1.0::DOUBLE / 0.0::DOUBLE"),
format!("Division by zero in expression (1.0 / 0.0). {advice}")
);
assert_eq!(
failure(&db, "SELECT 1.0::DOUBLE % 0.0::DOUBLE"),
format!("Division by zero in expression (1.0 % 0.0). {advice}")
);
db.create_table("ieee_values", vec![Field::new("a", LogicalType::Double)]).unwrap();
db.append(
"ieee_values",
&[vec![Value::Double(-1.0)], vec![Value::Double(0.0)], vec![Value::Double(1.0)]],
)
.unwrap();
assert_eq!(
failure(&db, "SELECT 1.0 / a FROM ieee_values"),
format!("Division by zero in expression (1.0 / a). {advice}")
);
assert_eq!(
failure(&db, "SELECT 1.0 % a FROM ieee_values"),
format!("Division by zero in expression (1.0 % a). {advice}")
);
db.execute("SET null_on_division_by_zero = true").expect("nulling division errors");
assert_eq!(
rows(&db, "SELECT 1.0::DOUBLE / 0.0::DOUBLE, 1.0::DOUBLE % 0.0::DOUBLE"),
vec![vec![Value::Null, Value::Null]]
);
db.execute("RESET null_on_division_by_zero").expect("division errors");
db.execute("RESET ieee_floating_point_ops").expect("IEEE floating operations");
assert_eq!(db.setting("ieee_floating_point_ops").expect("the setting"), "true");
let error = db.execute("SET ieee_floating_point_ops = 'off'").expect_err("not a boolean");
assert_eq!(error.message(), "Failed to cast value: Could not convert string 'off' to BOOL");
}
#[test]
fn timestamp_to_timestamptz_casts_can_be_disabled_while_binding() {
let db = database();
db.execute("SET TimeZone = 'UTC'").expect("a deterministic zone");
assert_eq!(db.setting("disable_timestamptz_casts").expect("the setting"), "false");
assert_eq!(
rows(&db, "SELECT TIMESTAMP '2024-01-02 03:04:05'::TIMESTAMPTZ"),
vec![vec![Value::TimestampTz(1_704_164_645_000_000)]]
);
db.execute("SET disable_timestamptz_casts = true").expect("disabled timestamp casts");
assert_eq!(
rows(
&db,
"SELECT current_setting('disable_timestamptz_casts'), typeof(current_setting('disable_timestamptz_casts'))"
),
vec![vec![Value::Boolean(true), text("BOOLEAN")]]
);
let expected = "Casting from TIMESTAMP to TIMESTAMP WITH TIME ZONE without an explicit time zone has been disabled - use \"AT TIME ZONE ...\"";
for query in [
"SELECT TIMESTAMP '2024-01-02 03:04:05'::TIMESTAMPTZ",
"SELECT TRY_CAST(TIMESTAMP '2024-01-02 03:04:05' AS TIMESTAMPTZ)",
"SELECT TIMESTAMP '2024-01-02 03:04:05' = TIMESTAMPTZ '2024-01-02 03:04:05+00'",
"SELECT CASE WHEN true THEN TIMESTAMP '2024-01-02 03:04:05' ELSE TIMESTAMPTZ '2024-01-02 03:04:05+00' END",
"SELECT DATE '2024-01-02'::TIMESTAMPTZ",
"VALUES (TIMESTAMP '2024-01-02 03:04:05'), (TIMESTAMPTZ '2024-01-02 03:04:05+00')",
"SELECT TIMESTAMP '2024-01-02 03:04:05' UNION ALL SELECT TIMESTAMPTZ '2024-01-02 03:04:05+00'",
] {
assert_eq!(failure(&db, query), expected, "{query}");
}
db.create_table("zoned", vec![Field::new("z", LogicalType::TimestampTz)]).unwrap();
assert_eq!(failure(&db, "INSERT INTO zoned SELECT TIMESTAMP '2024-01-02 03:04:05'"), expected);
assert_eq!(
rows(&db, "SELECT '2024-01-02 03:04:05'::TIMESTAMPTZ"),
vec![vec![Value::TimestampTz(1_704_164_645_000_000)]]
);
db.execute("RESET disable_timestamptz_casts").expect("timestamp casts restored");
assert_eq!(db.setting("disable_timestamptz_casts").expect("the setting"), "false");
let error = db.execute("SET disable_timestamptz_casts = 'off'").expect_err("not a boolean");
assert_eq!(error.message(), "Failed to cast value: Could not convert string 'off' to BOOL");
assert_eq!(
rows(
&db,
"SELECT description, input_type, scope FROM duckdb_settings() WHERE name = 'disable_timestamptz_casts'"
),
vec![vec![
text("Disable casting from timestamp to timestamptz "),
text("BOOLEAN"),
text("GLOBAL"),
]]
);
}
#[test]
fn a_non_integer_order_literal_needs_the_session_opt_in() {
let db = database();
let expected = "ORDER BY non-integer literal has no effect.\n* SET order_by_non_integer_literal=true to allow this behavior.";
assert_eq!(failure(&db, "SELECT 2 AS x ORDER BY 'a'"), expected);
assert_eq!(failure(&db, "SELECT 2 AS x ORDER BY 1.5"), expected);
assert_eq!(failure(&db, "SELECT 2 AS x ORDER BY NULL"), expected);
db.execute("SET order_by_non_integer_literal = true").expect("constant sort keys");
assert_eq!(db.setting("order_by_non_integer_literal").expect("the setting"), "true");
assert_eq!(rows(&db, "SELECT 2 AS x ORDER BY 'a'"), vec![vec![Value::Integer(2)]]);
db.execute("RESET order_by_non_integer_literal").expect("the default guard");
assert_eq!(db.setting("order_by_non_integer_literal").expect("the setting"), "false");
}
#[test]
fn regex_operator_semantics_are_resolved_while_the_expression_is_bound() {
let db = database();
assert_eq!(
rows(&db, "SELECT 'abc' ~ 'b', 'abc' !~ 'b', 'ABC' ~* 'b', 'ABC' !~* 'b'"),
vec![vec![
Value::Boolean(true),
Value::Boolean(false),
Value::Boolean(true),
Value::Boolean(false),
]]
);
db.execute("SET regex_match_operator_semantics = 'full'").expect("full matching");
assert_eq!(
rows(&db, "SELECT 'abc' ~ 'b', 'abc' !~ 'b', 'ABC' ~* 'b', 'ABC' !~* 'b'"),
vec![vec![
Value::Boolean(false),
Value::Boolean(true),
Value::Boolean(false),
Value::Boolean(true),
]]
);
assert_eq!(
db.query("SELECT 'abc' ~ 'b'").expect("a regex match").names(),
["regexp_full_match('abc', 'b')".to_string()]
);
assert_eq!(rows(&db, "SELECT 'abc' SIMILAR TO 'b'"), vec![vec![Value::Boolean(false)]]);
db.execute("RESET regex_match_operator_semantics").expect("partial matching");
assert_eq!(db.setting("regex_match_operator_semantics").expect("the setting"), "partial");
let error =
db.execute("SET regex_match_operator_semantics = 'nope'").expect_err("an unknown mode");
assert_eq!(error.code().duckdb_name(), "Not implemented Error");
assert_eq!(
error.message(),
"Enum value: unrecognized value \"nope\" for enum \"RegexMatchOperatorSemantics\"\n\nCandidates: \"FULL\""
);
}
#[test]
fn show_behavior_resolves_a_name_before_execution() {
let db = database();
assert_eq!(rows(&db, "SHOW show_behavior"), vec![vec![text("AUTO")]]);
let mixed = db.query("SHOW ShOw_BeHaViOr").expect("setting names ignore case");
assert_eq!(mixed.names(), ["ShOw_BeHaViOr"]);
assert_eq!(mixed.rows().collect::<Vec<_>>(), vec![vec![text("AUTO")]]);
assert_eq!(
rows(&db, "SHOW t"),
vec![
vec![text("x"), text("INTEGER"), text("YES"), Value::Null, Value::Null, Value::Null],
vec![text("s"), text("VARCHAR"), text("YES"), Value::Null, Value::Null, Value::Null],
]
);
db.execute("SET show_behavior = 'setting'").expect("settings only");
assert_eq!(rows(&db, "SHOW show_behavior"), vec![vec![text("setting")]]);
assert_eq!(failure(&db, "SHOW t"), "Setting with name \"t\" does not exist");
db.execute("SET show_behavior = 'table'").expect("tables only");
assert_eq!(failure(&db, "SHOW show_behavior"), "Table with name show_behavior does not exist!");
db.execute("RESET show_behavior").expect("automatic resolution");
assert_eq!(db.setting("show_behavior").expect("the setting"), "AUTO");
assert_eq!(
rows(
&db,
"SELECT description, input_type, scope FROM duckdb_settings() WHERE name = 'show_behavior'"
),
vec![vec![
text(
"How SHOW resolves a bare identifier: 'auto' (describe a table if one exists, else a setting; deprecated), 'table' (always a table), or 'setting' (always a setting)"
),
text("VARCHAR"),
text("GLOBAL"),
]]
);
}
#[test]
fn current_dialect_resolves_through_the_installed_parser_registry() {
let db = database();
assert_eq!(rows(&db, "SELECT current_setting('current_dialect')"), vec![vec![text("duckdb")]]);
db.execute("SET current_dialect = 'DUCKDB'").expect("the installed dialect");
assert_eq!(db.setting("current_dialect").expect("the dialect"), "DUCKDB");
assert_eq!(rows(&db, "SELECT 1"), vec![vec![Value::Integer(1)]]);
let error = db.execute("SET current_dialect = 'cypher'").expect_err("not installed");
assert_eq!(error.code().duckdb_name(), "Invalid Input Error");
assert_eq!(error.message(), "Dialect \"cypher\" is not installed");
db.execute("RESET current_dialect").expect("the default dialect");
assert_eq!(db.setting("current_dialect").expect("the dialect"), "duckdb");
assert_eq!(
rows(
&db,
"SELECT description, input_type, scope FROM duckdb_settings() WHERE name = 'current_dialect'"
),
vec![vec![text("The SQL dialect used by the parser"), text("VARCHAR"), text("GLOBAL")]]
);
}
#[test]
fn dialect_compatibility_mode_accepts_exactly_the_modes_the_pin_has() {
let db = database();
assert_eq!(
rows(&db, "SELECT current_setting('dialect_compatibility_mode')"),
vec![vec![text("NONE")]]
);
db.execute("SET dialect_compatibility_mode = 'spark'").expect("Spark mode");
assert_eq!(db.setting("dialect_compatibility_mode").expect("the mode"), "spark");
db.execute("SET GLOBAL dialect_compatibility_mode = 'SPARK'").expect("global Spark mode");
assert_eq!(db.setting("dialect_compatibility_mode").expect("the mode"), "SPARK");
db.execute("SET dialect_compatibility_mode = 'none'").expect("no compatibility mode");
assert_eq!(db.setting("dialect_compatibility_mode").expect("the mode"), "none");
db.execute("RESET GLOBAL dialect_compatibility_mode").expect("the default mode");
assert_eq!(db.setting("dialect_compatibility_mode").expect("the mode"), "NONE");
let error = db.execute("SET dialect_compatibility_mode = 'nope'").expect_err("an unknown mode");
assert_eq!(error.code().duckdb_name(), "Not implemented Error");
assert_eq!(
error.message(),
"Enum value: unrecognized value \"nope\" for enum \"DialectCompatibilityMode\"\n\nCandidates: \"NONE\""
);
assert_eq!(
rows(
&db,
"SELECT description, input_type, scope FROM duckdb_settings() WHERE name = 'dialect_compatibility_mode'"
),
vec![vec![
text(
"Enable SQL dialect compatibility for a certain engine (e.g. `SET dialect_compatibility_mode='spark'`)"
),
text("VARCHAR"),
text("GLOBAL"),
]]
);
}
#[test]
fn preserve_identifier_case_folds_only_unquoted_identifiers() {
let db = database();
assert_eq!(db.setting("preserve_identifier_case").expect("the mode"), "preserve_case");
db.execute("CREATE TABLE MixedTable (MixedColumn INTEGER)").expect("preserved names");
assert_eq!(
rows(
&db,
"SELECT table_name, column_name FROM duckdb_columns() WHERE table_name = 'MixedTable'"
),
vec![vec![text("MixedTable"), text("MixedColumn")]]
);
db.execute("SET preserve_identifier_case = 'lowercase'").expect("lowercase names");
db.execute("CREATE TABLE LowerTable (LowerColumn INTEGER)").expect("lowercase table");
assert_eq!(
rows(
&db,
"SELECT table_name, column_name FROM duckdb_columns() WHERE table_name = 'lowertable'"
),
vec![vec![text("lowertable"), text("lowercolumn")]]
);
db.execute("SET preserve_identifier_case = 'uppercase'").expect("uppercase names");
db.execute("CREATE TABLE UpperTable (UpperColumn INTEGER)").expect("uppercase table");
db.execute("CREATE TABLE \"QuotedTable\" (\"QuotedColumn\" INTEGER)").expect("quoted names");
assert_eq!(
rows(
&db,
"SELECT table_name, column_name FROM duckdb_columns() WHERE table_name IN ('UPPERTABLE', 'QuotedTable') ORDER BY table_name"
),
vec![
vec![text("QuotedTable"), text("QuotedColumn")],
vec![text("UPPERTABLE"), text("UPPERCOLUMN")],
]
);
assert_eq!(rows(&db, "SELECT 'MixedString'"), vec![vec![text("MixedString")]]);
db.execute("RESET preserve_identifier_case").expect("the default mode");
assert_eq!(db.setting("preserve_identifier_case").expect("the mode"), "preserve_case");
}
#[test]
fn preserve_identifier_case_keeps_legacy_boolean_aliases_and_metadata() {
let db = database();
for truthy in ["true", "1", "'t'", "'y'", "'yes'"] {
db.execute(&format!("SET preserve_identifier_case = {truthy}")).expect("truthy alias");
assert_eq!(db.setting("preserve_identifier_case").expect("the mode"), "preserve_case");
}
for falsy in ["false", "0", "'f'", "'n'", "'no'"] {
db.execute(&format!("SET preserve_identifier_case = {falsy}")).expect("falsy alias");
assert_eq!(db.setting("preserve_identifier_case").expect("the mode"), "lowercase");
}
let invalid = db.execute("SET preserve_identifier_case = 'bogus'").expect_err("invalid mode");
assert_eq!(invalid.code().duckdb_name(), "Invalid Input Error");
assert_eq!(
invalid.message(),
"Unrecognized parameter for option preserve_identifier_case \"bogus\", expected one of: preserve_case, lowercase, uppercase"
);
let null = db.execute("SET preserve_identifier_case = NULL").expect_err("null mode");
assert_eq!(null.message(), "preserve_identifier_case setting cannot be NULL");
assert_eq!(
rows(
&db,
"SELECT description, input_type, scope FROM duckdb_settings() WHERE name = 'preserve_identifier_case'"
),
vec![vec![
text(
"How to fold non-quoted identifiers: 'preserve_case' keeps the case as written, 'lowercase' lowercases them, 'uppercase' uppercases them"
),
text("VARCHAR"),
text("GLOBAL"),
]]
);
}
#[test]
fn allow_parser_override_extension_matches_the_only_installed_mode() {
let db = database();
assert_eq!(
rows(&db, "SELECT current_setting('allow_parser_override_extension')"),
vec![vec![text("DEFAULT")]]
);
db.execute("SET allow_parser_override_extension = DEFAULT").expect("the default mode");
db.execute("SET allow_parser_override_extension = 'default'").expect("case insensitive mode");
let invalid = db
.execute("SET allow_parser_override_extension = true")
.expect_err("the pin has no enabled mode");
assert_eq!(invalid.code().duckdb_name(), "Not implemented Error");
assert_eq!(
invalid.message(),
"Enum value: unrecognized value \"true\" for enum \"AllowParserOverride\"\n\nCandidates: \"DEFAULT\""
);
db.execute("RESET allow_parser_override_extension").expect("reset the mode");
assert_eq!(db.setting("allow_parser_override_extension").expect("the mode"), "DEFAULT");
assert_eq!(
rows(
&db,
"SELECT description, input_type, scope FROM duckdb_settings() WHERE name = 'allow_parser_override_extension'"
),
vec![vec![
text("Allow extensions to override the current parser"),
text("VARCHAR"),
text("GLOBAL"),
]]
);
}
#[test]
fn warnings_as_errors_matches_the_pin_without_a_logger() {
let db = database();
assert_eq!(
rows(&db, "SELECT current_setting('warnings_as_errors')"),
vec![vec![Value::Boolean(false)]]
);
db.execute("SET warnings_as_errors = false").expect("warnings stay warnings");
db.execute("SET warnings_as_errors = 'no'").expect("the boolean alias");
let error = db.execute("SET warnings_as_errors = true").expect_err("there is no logger");
assert_eq!(error.code().duckdb_name(), "Settings Error");
assert_eq!(
error.message(),
"Can not set 'warnings_as_errors=true'; no logger is available. To solve, run: 'SET enable_logging=true;'"
);
assert_eq!(db.setting("warnings_as_errors").expect("the setting"), "false");
db.execute("RESET warnings_as_errors").expect("the default");
assert_eq!(
rows(
&db,
"SELECT description, input_type, scope FROM duckdb_settings() WHERE name = 'warnings_as_errors'"
),
vec![vec![text("Escalate all warnings to errors."), text("BOOLEAN"), text("GLOBAL")]]
);
}
#[test]
fn errors_as_json_structures_errors_at_every_sql_api_boundary() {
let db = database();
assert_eq!(
rows(&db, "SELECT current_setting('errors_as_json')"),
vec![vec![Value::Boolean(false)]]
);
db.execute("SET errors_as_json = true").expect("JSON errors");
let missing = db.query("SELECT * FROM nonexistent_table").expect_err("missing table");
assert_eq!(missing.code().duckdb_name(), "Catalog Error");
assert!(missing.message().contains("\"exception_type\":\"Catalog\""));
assert!(missing.message().contains("\"error_subtype\":\"MISSING_ENTRY\""));
assert!(!missing.to_string().starts_with("Catalog Error:"));
let column = db.query("SELECT cbl FROM (VALUES (42)) t(col)").expect_err("missing column");
assert!(column.message().contains("\"exception_type\":\"Binder\""));
assert!(column.message().contains("\"error_subtype\":\"COLUMN_NOT_FOUND\""));
let syntax = db.prepare("SECT 1").expect_err("syntax error");
assert!(syntax.message().contains("\"exception_type\":\"Parser\""));
assert!(syntax.message().contains("\"error_subtype\":\"SYNTAX_ERROR\""));
assert!(syntax.message().contains("\"position\":"));
assert!(db.plan("SELECT missing").expect_err("plan error").message().starts_with('{'));
db.execute("RESET errors_as_json").expect("plain errors");
assert!(
db.query("SELECT * FROM nonexistent_table")
.expect_err("plain error")
.to_string()
.starts_with("Catalog Error:")
);
assert_eq!(
rows(
&db,
"SELECT description, input_type, scope FROM duckdb_settings() WHERE name = 'errors_as_json'"
),
vec![vec![
text("Output error messages as structured JSON instead of as a raw string"),
text("BOOLEAN"),
text("GLOBAL"),
]]
);
}
#[test]
fn uncorrelated_scalar_subqueries_are_single_joins() {
let db = database();
assert_eq!(rows(&db, "SELECT (SELECT 42)"), vec![vec![Value::Integer(42)]]);
assert_eq!(rows(&db, "SELECT 1 + (SELECT 2)"), vec![vec![Value::Integer(3)]]);
assert_eq!(
rows(&db, "SELECT x, (SELECT 7) FROM (VALUES (1), (2)) t(x) ORDER BY x"),
vec![
vec![Value::Integer(1), Value::Integer(7)],
vec![Value::Integer(2), Value::Integer(7)]
]
);
assert_eq!(rows(&db, "SELECT (SELECT 1 WHERE false)"), vec![vec![Value::Null]]);
let several = db
.query("SELECT (SELECT x FROM (VALUES (1), (2)) t(x))")
.expect_err("a scalar query has one row at most");
assert_eq!(several.code().duckdb_name(), "Invalid Input Error");
assert_eq!(
several.message(),
"More than one row returned by a subquery used as an expression - scalar subqueries can only return a single row.\n\nUse \"SET scalar_subquery_error_on_multiple_rows=false\" to revert to previous behavior of returning a random row."
);
let plan = db.plan("SELECT (SELECT 42)").expect("the scalar query plans");
assert!(plan.contains("Join SINGLE"), "{plan}");
}
#[test]
fn scalar_subquery_multiple_row_behavior_is_a_bound_semantic() {
let db = database();
assert_eq!(
rows(&db, "SELECT current_setting('scalar_subquery_error_on_multiple_rows')"),
vec![vec![Value::Boolean(true)]]
);
db.execute("SET scalar_subquery_error_on_multiple_rows = false").expect("choose one row");
assert_eq!(
rows(&db, "SELECT (SELECT x FROM (VALUES (1), (2)) t(x))"),
vec![vec![Value::Integer(1)]]
);
let plan = db
.plan("SELECT (SELECT x FROM (VALUES (1), (2)) t(x))")
.expect("the relaxed scalar query plans");
assert!(plan.contains("Limit 1 offset 0"), "{plan}");
db.execute("RESET scalar_subquery_error_on_multiple_rows").expect("restore strict mode");
assert!(db.query("SELECT (SELECT x FROM (VALUES (1), (2)) t(x))").is_err());
assert_eq!(
rows(
&db,
"SELECT description, input_type, scope FROM duckdb_settings() WHERE name = 'scalar_subquery_error_on_multiple_rows'"
),
vec![vec![
text(
"Throw an error when a scalar subquery returns more than one row. When disabled, an arbitrary row is returned instead."
),
text("BOOLEAN"),
text("GLOBAL"),
]]
);
}
#[test]
fn a_correlated_scalar_filter_unnests_to_one_single_join() {
let db = database();
let sql = "SELECT k, (SELECT value FROM (VALUES (1, 10), (2, 20)) i(k, value) WHERE i.k = o.k) FROM (VALUES (1), (2), (3)) o(k) ORDER BY k";
assert_eq!(
rows(&db, sql),
vec![
vec![Value::Integer(1), Value::Integer(10)],
vec![Value::Integer(2), Value::Integer(20)],
vec![Value::Integer(3), Value::Null],
]
);
let plan = db.plan(sql).expect("the correlated scalar query plans");
assert!(plan.contains("Join SINGLE"), "{plan}");
assert!(!plan.contains("DependentJoin"), "{plan}");
assert_eq!(
rows(
&db,
"SELECT (SELECT value FROM (VALUES (1, 10), (1, 20)) i(k, value) WHERE i.k = o.k AND value > 10) FROM (VALUES (1), (2)) o(k) ORDER BY k"
),
vec![vec![Value::Integer(20)], vec![Value::Null]]
);
db.execute("SET disabled_optimizers = 'unnest_rewriter'")
.expect("the upstream optimizer name is accepted");
assert_eq!(rows(&db, sql)[2], vec![Value::Integer(3), Value::Null]);
}
#[test]
fn a_correlated_scalar_projection_replays_over_distinct_outer_values() {
let db = database();
let sql =
"SELECT k, (SELECT o.k + 1) FROM (VALUES (1), (1), (2), (NULL)) o(k) ORDER BY k NULLS LAST";
assert_eq!(
rows(&db, sql),
vec![
vec![Value::Integer(1), Value::Integer(2)],
vec![Value::Integer(1), Value::Integer(2)],
vec![Value::Integer(2), Value::Integer(3)],
vec![Value::Null, Value::Null],
]
);
let plan = db.plan(sql).expect("the correlated scalar projection plans");
assert!(plan.contains("Join SINGLE"), "{plan}");
assert!(plan.contains("IS NOT DISTINCT FROM"), "{plan}");
assert!(plan.contains("__correlated_1"), "{plan}");
assert!(!plan.contains("DependentJoin"), "{plan}");
assert_eq!(
rows(
&db,
"SELECT k, (SELECT o.k + x FROM (VALUES (10)) i(x)) FROM (VALUES (1), (2)) o(k) ORDER BY k"
),
vec![
vec![Value::Integer(1), Value::Integer(11)],
vec![Value::Integer(2), Value::Integer(12)],
]
);
db.execute("CREATE TABLE empty_scalar_source(x INTEGER)")
.expect("the empty scalar source is created");
assert_eq!(
rows(
&db,
"SELECT k, (SELECT o.k + x FROM empty_scalar_source) FROM (VALUES (1), (2)) o(k) ORDER BY k"
),
vec![vec![Value::Integer(1), Value::Null], vec![Value::Integer(2), Value::Null],]
);
assert!(
db.query("SELECT (SELECT o.k + x FROM (VALUES (10), (20)) i(x)) FROM (VALUES (1)) o(k)")
.is_err()
);
}
#[test]
fn correlated_scalar_aggregates_group_by_hidden_correlation_keys() {
let db = database();
let sql = "SELECT k, (SELECT sum(value) FROM (VALUES (1, 10), (1, 20), (2, 5), (NULL, 40)) i(k, value) WHERE i.k = o.k AND value > 5) FROM (VALUES (1), (2), (3), (NULL)) o(k) ORDER BY k NULLS LAST";
assert_eq!(
rows(&db, sql),
vec![
vec![Value::Integer(1), Value::HugeInt(30)],
vec![Value::Integer(2), Value::Null],
vec![Value::Integer(3), Value::Null],
vec![Value::Null, Value::Null],
]
);
let plan = db.plan(sql).expect("the correlated scalar aggregate plans");
assert!(plan.contains("Join SINGLE"), "{plan}");
assert!(plan.contains("groups=[#1.0::INTEGER]"), "{plan}");
assert!(!plan.contains("DependentJoin"), "{plan}");
}
#[test]
fn correlated_scalar_counts_keep_zero_for_missing_groups() {
let db = database();
let sql = "SELECT k, (SELECT count(*) FROM (VALUES (1, 10), (1, NULL), (2, 5), (NULL, 40)) i(k, value) WHERE i.k = o.k AND value > 5) FROM (VALUES (1), (2), (3), (NULL)) o(k) ORDER BY k NULLS LAST";
assert_eq!(
rows(&db, sql),
vec![
vec![Value::Integer(1), Value::BigInt(1)],
vec![Value::Integer(2), Value::BigInt(0)],
vec![Value::Integer(3), Value::BigInt(0)],
vec![Value::Null, Value::BigInt(0)],
]
);
assert_eq!(
rows(
&db,
"SELECT k, (SELECT count(1) FROM (VALUES (1), (1), (2)) i(k) WHERE i.k = o.k) FROM (VALUES (1), (3)) o(k) ORDER BY k"
),
vec![vec![Value::Integer(1), Value::BigInt(2)], vec![Value::Integer(3), Value::BigInt(0)],]
);
let plan = db.plan(sql).expect("the correlated scalar count plans");
assert!(plan.contains("Join LEFT"), "{plan}");
assert!(plan.contains("count(#1.0::INTEGER FILTER"), "{plan}");
assert!(plan.contains("IS NOT DISTINCT FROM #0.0"), "{plan}");
assert!(!plan.contains("DependentJoin"), "{plan}");
}
#[test]
fn correlated_scalar_aggregates_use_an_outer_domain_for_arbitrary_predicates() {
let db = database();
assert_eq!(
rows(
&db,
"SELECT k, (SELECT count(*) FROM (VALUES (1), (2), (3)) i(x) WHERE i.x < o.k) FROM (VALUES (1), (2), (4)) o(k) ORDER BY k"
),
vec![
vec![Value::Integer(1), Value::BigInt(0)],
vec![Value::Integer(2), Value::BigInt(1)],
vec![Value::Integer(4), Value::BigInt(3)],
]
);
assert_eq!(
rows(
&db,
"SELECT k, (SELECT sum(value) FROM (VALUES (1, 10), (2, 20), (3, 30)) i(x, value) WHERE i.x < o.k) FROM (VALUES (1), (2), (4)) o(k) ORDER BY k"
),
vec![
vec![Value::Integer(1), Value::Null],
vec![Value::Integer(2), Value::HugeInt(10)],
vec![Value::Integer(4), Value::HugeInt(60)],
]
);
assert_eq!(
rows(
&db,
"SELECT k, (SELECT count(*) FROM (VALUES (10), (20)) i(x) WHERE o.k > 1) FROM (VALUES (1), (2)) o(k) ORDER BY k"
),
vec![vec![Value::Integer(1), Value::BigInt(0)], vec![Value::Integer(2), Value::BigInt(2)],]
);
let sql = "SELECT (SELECT count(*) FROM (VALUES (NULL), (1)) i(x) WHERE i.x IS DISTINCT FROM o.k) FROM (VALUES (NULL)) o(k)";
assert_eq!(rows(&db, sql), vec![vec![Value::BigInt(1)]]);
let plan = db.plan(sql).expect("the arbitrary correlated aggregate plans");
assert!(plan.contains("Join LEFT"), "{plan}");
assert!(plan.contains("__inner_"), "{plan}");
assert!(!plan.contains("DependentJoin"), "{plan}");
}
#[test]
fn correlated_aggregate_arguments_replay_over_the_outer_domain() {
let db = database();
let sql = "SELECT k, (SELECT count(o.k) FROM (VALUES (10), (20)) i(x)) FROM (VALUES (1), (NULL)) o(k) ORDER BY k NULLS LAST";
assert_eq!(
rows(&db, sql),
vec![vec![Value::Integer(1), Value::BigInt(2)], vec![Value::Null, Value::BigInt(0)],]
);
assert_eq!(
rows(
&db,
"SELECT k, (SELECT sum(o.k) FROM (VALUES (10), (20)) i(x)) FROM (VALUES (1), (NULL)) o(k) ORDER BY k NULLS LAST"
),
vec![vec![Value::Integer(1), Value::HugeInt(2)], vec![Value::Null, Value::Null],]
);
db.execute("CREATE TABLE empty_aggregate_source(x INTEGER)")
.expect("the empty aggregate source is created");
assert_eq!(
rows(&db, "SELECT (SELECT count(o.k) FROM empty_aggregate_source) FROM (VALUES (1)) o(k)"),
vec![vec![Value::BigInt(0)]]
);
let plan = db.plan(sql).expect("the correlated aggregate argument plans");
assert!(plan.contains("Join LEFT"), "{plan}");
assert!(plan.contains("IS NOT DISTINCT FROM"), "{plan}");
assert!(!plan.contains("DependentJoin"), "{plan}");
}
#[test]
fn uncorrelated_exists_is_a_single_joined_marker() {
let db = database();
let answer = db.query("SELECT EXISTS (SELECT 1)").expect("EXISTS answers");
assert_eq!(answer.column_name(0), "EXISTS(SELECT 1)");
assert_eq!(answer.rows().collect::<Vec<_>>(), vec![vec![Value::Boolean(true)]]);
let negated = db.query("SELECT NOT EXISTS (SELECT 1 WHERE false)").expect("NOT EXISTS answers");
assert_eq!(negated.column_name(0), "(NOT EXISTS(SELECT 1 WHERE false))");
assert_eq!(negated.rows().collect::<Vec<_>>(), vec![vec![Value::Boolean(true)]]);
assert_eq!(
rows(&db, "SELECT EXISTS (SELECT 1 WHERE false)"),
vec![vec![Value::Boolean(false)]]
);
assert_eq!(
rows(&db, "SELECT EXISTS (SELECT NULL FROM range(3))"),
vec![vec![Value::Boolean(true)]]
);
assert_eq!(
rows(&db, "SELECT x FROM (VALUES (1), (2)) t(x) WHERE EXISTS (SELECT 1) ORDER BY x"),
vec![vec![Value::Integer(1)], vec![Value::Integer(2)]]
);
assert!(
rows(&db, "SELECT x FROM (VALUES (1), (2)) t(x) WHERE EXISTS (SELECT 1 WHERE false)")
.is_empty()
);
let plan = db.plan("SELECT EXISTS (SELECT * FROM range(1000))").expect("EXISTS plans");
assert!(plan.contains("Join SINGLE"), "{plan}");
assert!(plan.contains("Limit 1 offset 0"), "{plan}");
}
#[test]
fn correlated_exists_ends_up_a_semi_join_against_the_relation() {
let db = database();
let exists = "SELECT k FROM (VALUES (1), (2), (3), (NULL)) o(k) WHERE EXISTS (SELECT 1 FROM (VALUES (1), (1), (3), (NULL)) i(k) WHERE i.k = o.k) ORDER BY k";
assert_eq!(rows(&db, exists), vec![vec![Value::Integer(1)], vec![Value::Integer(3)]]);
assert_eq!(
rows(
&db,
"SELECT k FROM (VALUES (1), (2), (3)) o(k) WHERE NOT EXISTS (SELECT 1 FROM (VALUES (1, 5), (2, 20)) i(k, value) WHERE i.k = o.k AND value > 10) ORDER BY k"
),
vec![vec![Value::Integer(1)], vec![Value::Integer(3)]]
);
let plan = db.plan(exists).expect("the correlated existence query plans");
assert!(plan.contains("Join SEMI"), "{plan}");
assert!(!plan.contains("Join SINGLE"), "{plan}");
assert!(!plan.contains("Aggregate"), "{plan}");
assert!(!plan.contains("DependentJoin"), "{plan}");
assert!(!plan.contains("Limit 1"), "{plan}");
}
#[test]
fn correlated_exists_with_an_inequality_ends_up_a_semi_join() {
let db = database();
let sql = "SELECT k FROM (VALUES (1), (2), (4)) o(k) WHERE EXISTS (SELECT 1 FROM (VALUES (1), (3)) i(x) WHERE i.x < o.k) ORDER BY k";
assert_eq!(rows(&db, sql), vec![vec![Value::Integer(2)], vec![Value::Integer(4)]]);
assert_eq!(
rows(
&db,
"SELECT k FROM (VALUES (1), (2), (4)) o(k) WHERE NOT EXISTS (SELECT 1 FROM (VALUES (1), (3)) i(x) WHERE i.x < o.k AND i.x > 1) ORDER BY k"
),
vec![vec![Value::Integer(1)], vec![Value::Integer(2)]]
);
assert_eq!(
rows(
&db,
"SELECT EXISTS (SELECT 1 FROM (VALUES (1)) i(x) WHERE i.x IS DISTINCT FROM o.k) FROM (VALUES (NULL)) o(k)"
),
vec![vec![Value::Boolean(true)]]
);
let plan = db.plan(sql).expect("the correlated inequality existence query plans");
assert!(plan.contains("Join SEMI on=[(#1.0::INTEGER < #0.0::INTEGER)::BOOLEAN]"), "{plan}");
assert!(!plan.contains("Join SINGLE"), "{plan}");
assert!(!plan.contains("Aggregate"), "{plan}");
assert!(!plan.contains("DependentJoin"), "{plan}");
}
#[test]
fn uncorrelated_in_subqueries_are_mark_joins() {
let db = database();
let query = |subject: &str, values: &str| {
format!("SELECT {subject} IN (SELECT x FROM (VALUES {values}) t(x))")
};
assert_eq!(rows(&db, &query("1", "(1), (2)")), vec![vec![Value::Boolean(true)]]);
assert_eq!(rows(&db, &query("3", "(1), (2)")), vec![vec![Value::Boolean(false)]]);
assert_eq!(rows(&db, &query("3", "(1), (NULL)")), vec![vec![Value::Null]]);
assert_eq!(rows(&db, &query("NULL", "(1), (2)")), vec![vec![Value::Null]]);
assert_eq!(
rows(&db, "SELECT NULL IN (SELECT x FROM (VALUES (1)) t(x) WHERE false)"),
vec![vec![Value::Boolean(false)]]
);
assert_eq!(
rows(&db, "SELECT 3 NOT IN (SELECT x FROM (VALUES (1), (NULL)) t(x))"),
vec![vec![Value::Null]]
);
let answer = db
.query("SELECT 1 IN (SELECT x FROM (VALUES (1), (2)) t(x))")
.expect("the membership query answers");
assert_eq!(
answer.column_name(0),
"(1 = ANY(SELECT x FROM (SELECT * FROM (VALUES (1), (2)) AS valueslist) AS t(x)))"
);
let plan = db.plan(&query("1", "(1), (2)")).expect("the membership query plans");
assert!(plan.contains("Join MARK"), "{plan}");
}
#[test]
fn correlated_membership_filters_unnest_to_mark_joins() {
let db = database();
let sql = "SELECT k, k IN (SELECT x FROM (VALUES (1), (2), (NULL)) i(x) WHERE i.x = o.k) FROM (VALUES (1), (3), (NULL)) o(k) ORDER BY k NULLS LAST";
assert_eq!(
rows(&db, sql),
vec![
vec![Value::Integer(1), Value::Boolean(true)],
vec![Value::Integer(3), Value::Boolean(false)],
vec![Value::Null, Value::Boolean(false)],
]
);
assert_eq!(
rows(
&db,
"SELECT k FROM (VALUES (1), (2), (3)) o(k) WHERE k NOT IN (SELECT x FROM (VALUES (1), (3)) i(x) WHERE i.x = o.k) ORDER BY k"
),
vec![vec![Value::Integer(2)]]
);
assert_eq!(
rows(
&db,
"SELECT k, k = ALL (SELECT x FROM (VALUES (1), (2)) i(x) WHERE i.x = o.k) FROM (VALUES (1), (3)) o(k) ORDER BY k"
),
vec![
vec![Value::Integer(1), Value::Boolean(true)],
vec![Value::Integer(3), Value::Boolean(true)],
]
);
let plan = db.plan(sql).expect("the correlated membership query plans");
assert!(plan.contains("Join MARK"), "{plan}");
assert!(plan.contains("IS NOT DISTINCT FROM TRUE"), "{plan}");
assert!(!plan.contains("DependentJoin"), "{plan}");
}
#[test]
fn correlated_mark_joins_carry_hidden_filter_columns_after_the_marker() {
let db = database();
let sql = "SELECT g, 15 IN (SELECT value FROM (VALUES (1, 10), (1, NULL), (2, 20)) i(g, value) WHERE i.g = o.g) FROM (VALUES (1), (2), (3)) o(g) ORDER BY g";
assert_eq!(
rows(&db, sql),
vec![
vec![Value::Integer(1), Value::Null],
vec![Value::Integer(2), Value::Boolean(false)],
vec![Value::Integer(3), Value::Boolean(false)],
]
);
let plan = db.plan(sql).expect("the hidden correlation key plans");
assert!(plan.contains("Join MARK"), "{plan}");
assert!(plan.contains("__correlated_2"), "{plan}");
assert!(!plan.contains("DependentJoin"), "{plan}");
}
#[test]
fn uncorrelated_any_and_all_subqueries_are_mark_joins() {
let db = database();
assert_eq!(
rows(&db, "SELECT 2 = ANY (SELECT x FROM (VALUES (1), (2)) t(x))"),
vec![vec![Value::Boolean(true)]]
);
assert_eq!(
rows(&db, "SELECT 2 <> ANY (SELECT x FROM (VALUES (2), (3)) t(x))"),
vec![vec![Value::Boolean(true)]]
);
assert_eq!(
rows(&db, "SELECT 2 > ALL (SELECT x FROM (VALUES (0), (1)) t(x))"),
vec![vec![Value::Boolean(true)]]
);
assert_eq!(
rows(&db, "SELECT 2 = ALL (SELECT x FROM (VALUES (2), (NULL)) t(x))"),
vec![vec![Value::Null]]
);
assert_eq!(
rows(&db, "SELECT NULL = ANY (SELECT x FROM (VALUES (1)) t(x) WHERE false)"),
vec![vec![Value::Boolean(false)]]
);
assert_eq!(
rows(&db, "SELECT NULL = ALL (SELECT x FROM (VALUES (1)) t(x) WHERE false)"),
vec![vec![Value::Boolean(true)]]
);
let answer = db
.query("SELECT 2 > ALL (SELECT x FROM (VALUES (0), (1)) t(x))")
.expect("the universal comparison answers");
assert_eq!(
answer.column_name(0),
"(NOT (2 <= ANY(SELECT x FROM (SELECT * FROM (VALUES (0), (1)) AS valueslist) AS t(x))))"
);
let plan = db
.plan("SELECT 2 = ANY (SELECT x FROM (VALUES (1), (2)) t(x))")
.expect("the quantified comparison plans");
assert!(plan.contains("Join MARK"), "{plan}");
}
#[test]
fn the_settings_table_reads_back_what_set_left_behind() {
let db = database();
let text = |value: &str| Value::Varchar(value.to_string());
assert!(
rows(
&db,
"SELECT value FROM duckdb_settings() WHERE name = 'memory_limit' AND value IS NOT NULL"
)
.len()
== 1
);
db.execute("SET memory_limit = '1GiB'").expect("a size");
assert_eq!(
rows(&db, "SELECT value, typed_value FROM duckdb_settings() WHERE name = 'memory_limit'"),
vec![vec![text("1.0 GiB"), text("1.0 GiB")]]
);
assert_eq!(
rows(&db, "SELECT value FROM duckdb_settings() WHERE name = 'max_memory'"),
vec![vec![text("1.0 GiB")]]
);
db.execute("SET max_memory = '2GiB'").expect("a size");
assert_eq!(
rows(&db, "SELECT value FROM duckdb_settings() WHERE name = 'memory_limit'"),
vec![vec![text("2.0 GiB")]]
);
db.execute("SET worker_threads = 3").expect("a thread count");
assert_eq!(
rows(&db, "SELECT value FROM duckdb_settings() WHERE name = 'threads'"),
vec![vec![text("3")]]
);
}
#[test]
fn a_setting_read_as_a_value_has_the_type_the_setting_holds() {
let db = database();
db.execute("SET threads = 3").expect("a thread count");
db.execute("SET memory_limit = '1GiB'").expect("a size");
assert_eq!(rows(&db, "SELECT current_setting('threads')"), vec![vec![Value::BigInt(3)]]);
assert_eq!(rows(&db, "SELECT typeof(current_setting('threads'))"), vec![vec![text("BIGINT")]]);
assert_eq!(rows(&db, "SELECT current_setting('memory_limit')"), vec![vec![text("1.0 GiB")]]);
assert_eq!(
rows(&db, "SELECT typeof(current_setting('memory_limit'))"),
vec![vec![text("VARCHAR")]]
);
assert_eq!(rows(&db, "SELECT current_setting('worker_threads')"), vec![vec![Value::BigInt(3)]]);
assert_eq!(rows(&db, "SELECT current_setting('max_memory')"), vec![vec![text("1.0 GiB")]]);
assert_eq!(rows(&db, "SELECT current_setting('THREADS')"), vec![vec![Value::BigInt(3)]]);
assert_eq!(
db.query("SELECT current_setting('threads')").expect("a setting").names(),
["current_setting('threads')".to_string()]
);
db.execute("RESET threads").expect("a reset");
let before = db.opened_with().threads();
assert_eq!(
rows(&db, "SELECT current_setting('threads')"),
vec![vec![Value::BigInt(i64::try_from(before).expect("a thread count fits"))]]
);
}
#[test]
fn a_setting_read_as_a_value_is_folded_before_the_plan_exists() {
let db = database();
db.execute("SET threads = 7").expect("a thread count");
let plan = db.plan("SELECT current_setting('threads')").expect("a plan");
assert!(plan.contains("[7::BIGINT AS \"current_setting('threads')\"]"), "{plan}");
}
#[test]
fn a_setting_that_is_not_a_constant_or_not_a_setting_is_refused_the_pins_way() {
let db = database();
assert_eq!(
failure(&db, "SELECT current_setting(s) FROM t"),
"The \"setting_name\" argument in function \"current_setting\" must be a constant expression"
);
let unknown = failure(&db, "SELECT current_setting('nope')");
assert!(unknown.starts_with("unrecognized configuration parameter \"nope\""), "{unknown}");
assert_eq!(unknown, db.execute("SET nope = 1").unwrap_err().message());
assert_eq!(
failure(&db, "SELECT current_setting()"),
"No function matches the given name and argument types 'current_setting()'. You might \
need to add explicit type casts.\n\tCandidate functions:\n\tcurrent_setting(setting_name \
VARCHAR) -> ANY\n"
);
}
#[test]
fn the_settings_table_answers_the_question_a_client_asks_it() {
let db = database();
let text = |value: &str| Value::Varchar(value.to_string());
assert_eq!(rows(&db, "SELECT count(*) FROM duckdb_settings()"), vec![vec![Value::BigInt(192)]]);
assert_eq!(
rows(
&db,
"SELECT description, input_type, scope FROM duckdb_settings() WHERE name = 'threads'"
),
vec![vec![
text("The number of total threads used by the system."),
text("BIGINT"),
text("GLOBAL"),
]]
);
assert!(rows(&db, "SELECT name FROM duckdb_settings() WHERE name LIKE 'seam%'").is_empty());
assert_eq!(
rows(&db, "SELECT name FROM duckdb_settings() WHERE value IS NULL ORDER BY name"),
vec![
vec![text("enable_profiling")],
vec![text("operator_memory_limit")],
vec![text("parquet_prefetch_column_gap")],
]
);
}
#[test]
fn a_setting_the_engine_does_not_read_still_answers_every_way_of_asking() {
let db = database();
let text = |value: &str| Value::Varchar(value.to_string());
let value = "SELECT value FROM duckdb_settings() WHERE name = 'enable_http_metadata_cache'";
assert_eq!(rows(&db, value), vec![vec![text("false")]]);
db.execute("SET enable_http_metadata_cache = true").expect("a knob takes a value");
assert_eq!(rows(&db, value), vec![vec![text("true")]]);
assert_eq!(
rows(&db, "SELECT current_setting('enable_http_metadata_cache')"),
vec![vec![Value::Boolean(true)]]
);
db.execute("RESET enable_http_metadata_cache").expect("a knob resets");
assert_eq!(rows(&db, value), vec![vec![text("false")]]);
db.execute("SET partitioned_write_max_open_files = 42").expect("a number knob");
assert_eq!(
rows(&db, "SELECT current_setting('partitioned_write_max_open_files')"),
vec![vec![Value::UBigInt(42)]]
);
let error = db.execute("SET preserve_insertion_order = false").unwrap_err();
assert_eq!(error.code().duckdb_name(), "Not implemented Error");
db.execute("SET preserve_insertion_order = true").expect("the value it already behaves as");
}
#[test]
fn a_pragma_that_is_a_statement_writes_the_setting_it_stands_for() {
let db = database();
let text = |value: &str| Value::Varchar(value.to_string());
let optimizer = "SELECT value FROM duckdb_settings() WHERE name = 'enable_optimizer'";
assert_eq!(rows(&db, optimizer), vec![vec![text("true")]]);
db.execute("PRAGMA disable_optimizer").expect("a pragma that is a statement");
assert_eq!(rows(&db, optimizer), vec![vec![text("false")]]);
db.execute("PRAGMA enable_optimizer").expect("and back");
assert_eq!(rows(&db, optimizer), vec![vec![text("true")]]);
db.execute("PRAGMA disable_print_progress_bar").expect("the one whose name is not the setting");
assert_eq!(
rows(&db, "SELECT current_setting('enable_progress_bar_print')"),
vec![vec![Value::Boolean(false)]]
);
db.execute("PRAGMA enable_profiling").expect("a word rather than a boolean");
assert_eq!(
rows(&db, "SELECT current_setting('enable_profiling')"),
vec![vec![text("query_tree")]]
);
db.execute("PRAGMA disable_profile").expect("the other spelling of the same statement");
assert_eq!(rows(&db, "SELECT current_setting('enable_profiling')"), vec![vec![Value::Null]]);
}
#[test]
fn a_pragma_that_changes_nothing_a_query_can_see_still_succeeds() {
let db = database();
for statement in [
"PRAGMA disable_checkpoint_on_shutdown",
"PRAGMA enable_checkpoint_on_shutdown",
"PRAGMA disable_object_cache",
"PRAGMA enable_object_cache",
"PRAGMA disable_verification",
"PRAGMA enable_verification",
"PRAGMA disable_verify_parallelism",
"PRAGMA verify_parallelism",
"PRAGMA force_checkpoint",
] {
db.execute(statement).unwrap_or_else(|error| panic!("{statement}: {error}"));
}
let error = db.execute("PRAGMA enable_nothing_at_all").unwrap_err();
assert_eq!(error.code().duckdb_name(), "Catalog Error");
assert_eq!(
error.to_string(),
"Catalog Error: Pragma Function with name enable_nothing_at_all does not exist!"
);
}
#[test]
fn a_setting_the_pin_leaves_unset_reads_as_null_rather_than_as_the_empty_string() {
let db = database();
let unset = "SELECT name FROM duckdb_settings() WHERE value IS NULL ORDER BY name";
let text = |value: &str| Value::Varchar(value.to_string());
assert_eq!(
rows(&db, unset),
vec![
vec![text("enable_profiling")],
vec![text("operator_memory_limit")],
vec![text("parquet_prefetch_column_gap")],
]
);
assert_eq!(
rows(&db, "SELECT current_setting('parquet_prefetch_column_gap')"),
vec![vec![Value::Null]]
);
db.execute("SET parquet_prefetch_column_gap = 64").expect("and it still takes a number");
assert_eq!(
rows(&db, "SELECT current_setting('parquet_prefetch_column_gap')"),
vec![vec![Value::UBigInt(64)]]
);
}
#[test]
fn every_pragma_the_catalog_has_is_one_the_parser_sends_to_it() {
let db = database();
for entry in rudb_functions::PRAGMAS {
let statement = format!("PRAGMA {}", entry.name);
db.execute(&statement).unwrap_or_else(|error| panic!("{statement}: {error}"));
}
}
#[test]
fn the_databases_and_schemas_tables_describe_the_catalogs_there_are() {
let db = database();
let text = |value: &str| Value::Varchar(value.to_string());
let yes = Value::Boolean(true);
let no = Value::Boolean(false);
assert_eq!(
rows(
&db,
"SELECT database_name, internal, type, readonly FROM duckdb_databases() ORDER BY 1"
),
vec![
vec![text("memory"), no.clone(), text("duckdb"), no.clone()],
vec![text("system"), yes.clone(), text("duckdb"), no.clone()],
vec![text("temp"), yes.clone(), text("duckdb"), no.clone()],
]
);
assert_eq!(
rows(
&db,
"SELECT database_name, schema_name, internal FROM duckdb_schemas() ORDER BY 1, 2"
),
vec![
vec![text("memory"), text("main"), yes.clone()],
vec![text("system"), text("information_schema"), yes.clone()],
vec![text("system"), text("main"), yes.clone()],
vec![text("system"), text("pg_catalog"), yes.clone()],
vec![text("temp"), text("main"), yes],
]
);
assert_eq!(
rows(
&db,
"SELECT count(*) FROM duckdb_schemas() s, duckdb_databases() d \
WHERE s.database_oid = d.database_oid"
),
vec![vec![Value::BigInt(5)]]
);
}
#[test]
fn the_engine_ships_with_the_views_upstream_ships_with() {
let db = Database::new();
let text = |value: &str| Value::Varchar(value.to_string());
db.execute("CREATE TABLE t(a INTEGER NOT NULL, b VARCHAR)").expect("a table to describe");
db.execute("CREATE VIEW v AS SELECT a FROM t").expect("a view to describe");
assert_eq!(
rows(
&db,
"SELECT table_catalog, table_schema, table_name, table_type, is_insertable_into \
FROM information_schema.tables ORDER BY table_name"
),
vec![
vec![text("memory"), text("main"), text("t"), text("BASE TABLE"), text("YES")],
vec![text("memory"), text("main"), text("v"), text("VIEW"), text("NO")],
]
);
assert_eq!(
rows(
&db,
"SELECT table_name, column_name, ordinal_position, is_nullable, data_type \
FROM information_schema.columns ORDER BY table_name, ordinal_position"
),
vec![
vec![text("t"), text("a"), Value::Integer(1), text("NO"), text("INTEGER")],
vec![text("t"), text("b"), Value::Integer(2), text("YES"), text("VARCHAR")],
vec![text("v"), text("a"), Value::Integer(1), text("YES"), text("INTEGER")],
]
);
assert_eq!(
rows(
&db,
"SELECT character_set_name, default_collate_name FROM information_schema.character_sets"
),
vec![vec![text("UTF8"), text("ucs_basic")]]
);
assert_eq!(rows(&db, "SELECT count(*) FROM duckdb_views"), vec![vec![Value::BigInt(1)]]);
assert_eq!(rows(&db, "SELECT count(*) FROM duckdb_views()"), vec![vec![Value::BigInt(13)]]);
assert_eq!(
failure(&db, "CREATE TABLE information_schema.x(a INTEGER)"),
"Cannot create entry in system catalog"
);
assert_eq!(
db.execute("DROP VIEW duckdb_views").expect_err("an internal entry").message(),
"Cannot drop internal catalog entry \"duckdb_views\"!"
);
}
#[test]
fn the_two_pragmas_describe_a_table_and_a_view_the_way_upstream_does() {
let db = Database::new();
db.execute("CREATE TABLE t(a INTEGER NOT NULL, b VARCHAR)").expect("a table to describe");
db.execute("CREATE VIEW v AS SELECT a FROM t").expect("a view to describe");
let no = Value::Boolean(false);
let table = vec![
vec![integer(0), text("a"), text("INTEGER"), Value::Boolean(true), Value::Null, no.clone()],
vec![integer(1), text("b"), text("VARCHAR"), no.clone(), Value::Null, no.clone()],
];
assert_eq!(rows(&db, "SELECT * FROM pragma_table_info('t')"), table);
assert_eq!(rows(&db, "SELECT * FROM pragma_table_info('main.t')"), table);
assert_eq!(rows(&db, "SELECT * FROM pragma_table_info('MEMORY.MAIN.T')"), table);
assert_eq!(
rows(&db, "SELECT * FROM pragma_table_info('v')"),
vec![vec![integer(0), text("a"), text("INTEGER"), no.clone(), Value::Null, no]]
);
assert_eq!(
rows(&db, "SELECT * FROM pragma_show('t')"),
vec![
vec![text("a"), text("INTEGER"), text("NO"), Value::Null, Value::Null, Value::Null],
vec![text("b"), text("VARCHAR"), text("YES"), Value::Null, Value::Null, Value::Null],
]
);
assert_eq!(
rows(
&db,
"SELECT info.name, info.type FROM pragma_table_info('t') AS info WHERE info.cid = 1"
),
vec![vec![text("b"), text("VARCHAR")]]
);
assert_eq!(
rows(&db, "SELECT n FROM pragma_table_info('t') AS info(c, n, ty, nn, d, k) WHERE c = 0"),
vec![vec![text("a")]]
);
}
#[test]
fn a_pragma_pointed_at_an_internal_view_binds_it_and_reports_its_columns() {
let db = Database::new();
let bound = "SELECT column_count FROM duckdb_views() WHERE view_name = 'duckdb_views'";
assert_eq!(rows(&db, bound), vec![vec![Value::Null]]);
assert_eq!(
rows(&db, "SELECT count(*) FROM pragma_table_info('duckdb_views')"),
vec![vec![Value::BigInt(13)]]
);
assert_eq!(rows(&db, bound), vec![vec![Value::BigInt(13)]]);
}
#[test]
fn a_pragma_given_a_name_that_is_not_there_says_what_the_catalog_says() {
let db = database();
assert_eq!(
failure(&db, "SELECT * FROM pragma_table_info('nope')"),
"Table with name nope does not exist!"
);
assert_eq!(
failure(&db, "SELECT * FROM pragma_show(NULL)"),
"Table with name NULL does not exist!"
);
for wrong in ["pragma_table_info()", "pragma_table_info('a', 'b')", "pragma_show(3)"] {
let message = failure(&db, &format!("SELECT * FROM {wrong}"));
assert!(message.starts_with("No function matches the given name"), "{message}");
assert!(message.contains("(VARCHAR)"), "{message}");
}
}
#[test]
fn the_four_pragmas_about_the_build_answer_in_one_row_of_their_own_columns() {
let db = database();
let shapes = [
("pragma_version", vec!["library_version", "source_id", "codename"]),
("pragma_platform", vec!["platform"]),
("pragma_user_agent", vec!["user_agent"]),
(
"pragma_database_size",
vec![
"database_name",
"database_size",
"block_size",
"total_blocks",
"used_blocks",
"free_blocks",
"wal_size",
"memory_usage",
"memory_limit",
],
),
];
for (name, columns) in shapes {
let answer = rows(&db, &format!("SELECT * FROM {name}()"));
assert_eq!(answer.len(), 1, "{name} answers about one build and one process");
assert_eq!(answer[0].len(), columns.len(), "{name}");
let named: Vec<Value> =
rows(&db, &format!("SELECT column_name FROM (DESCRIBE SELECT * FROM {name}())"))
.into_iter()
.map(|row| row[0].clone())
.collect();
assert_eq!(named, columns.iter().map(|column| text(column)).collect::<Vec<Value>>());
let message = failure(&db, &format!("SELECT * FROM {name}('t')"));
assert!(message.starts_with("No function matches the given name"), "{message}");
assert!(message.contains(&format!("\"{name}\"()")), "{message}");
}
}
#[test]
fn the_version_pragma_says_what_this_engine_is_and_the_others_agree_with_it() {
let db = database();
let version = rows(&db, "SELECT * FROM pragma_version()").remove(0);
assert_eq!(version[0], text(&format!("v{}", env!("CARGO_PKG_VERSION"))));
assert_eq!(version[1], text(""));
assert_eq!(version[2], text("Development Version"));
let platform = rows(&db, "SELECT * FROM pragma_platform()").remove(0);
let Value::Varchar(written) = &platform[0] else { panic!("the platform is text") };
assert!(written.contains('_'), "{written}");
assert!(!written.contains("macos") && !written.contains("x86_64"), "{written}");
let agent = rows(&db, "SELECT * FROM pragma_user_agent()").remove(0);
assert_eq!(agent[0], text(&format!("rudb/v{}({written})", env!("CARGO_PKG_VERSION"))));
}
#[test]
fn the_size_pragma_reports_the_attached_database_and_the_live_memory_budget() {
let db = database();
let size = rows(&db, "SELECT * FROM pragma_database_size()");
assert_eq!(size.len(), 1);
let row = &size[0];
assert_eq!(row[0], text("memory"));
for column in [1, 6] {
assert_eq!(row[column], text("0 bytes"));
}
assert_eq!(
row[2..=5],
[Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), Value::BigInt(0)]
);
db.execute("SET memory_limit = '1GiB'").expect("a size");
let after = rows(&db, "SELECT memory_limit FROM pragma_database_size()").remove(0);
assert_eq!(after[0], text("1.0 GiB"));
assert_eq!(after[0], rows(&db, "SELECT current_setting('memory_limit')").remove(0)[0]);
}
#[test]
fn the_storage_pragma_answers_in_sixteen_columns_and_leaves_unwritten_rows_out() {
let db = database();
let result = db.query("SELECT * FROM pragma_storage_info('t')").expect("the pragma ran");
assert_eq!(
result.names(),
[
"row_group_id",
"column_name",
"column_id",
"column_path",
"segment_id",
"segment_type",
"start",
"count",
"compression",
"stats",
"has_updates",
"persistent",
"block_id",
"block_offset",
"segment_info",
"additional_block_ids",
]
);
assert_eq!(
result.types(),
[
LogicalType::BigInt,
LogicalType::Varchar,
LogicalType::BigInt,
LogicalType::Varchar,
LogicalType::BigInt,
LogicalType::Varchar,
LogicalType::BigInt,
LogicalType::BigInt,
LogicalType::Varchar,
LogicalType::Varchar,
LogicalType::Boolean,
LogicalType::Boolean,
LogicalType::BigInt,
LogicalType::BigInt,
LogicalType::Varchar,
LogicalType::list(LogicalType::BigInt),
]
);
assert_eq!(result.len(), 0, "nothing is on disk here, so nothing is stored in any form");
assert_eq!(
failure(&db, "SELECT * FROM pragma_storage_info('nope')"),
"Table with name nope does not exist!"
);
}
#[test]
fn the_pragma_statement_answers_what_the_function_of_that_name_answers() {
let db = database();
for (statement, call) in [
("PRAGMA version", "SELECT * FROM pragma_version()"),
("PRAGMA platform", "SELECT * FROM pragma_platform()"),
("PRAGMA user_agent", "SELECT * FROM pragma_user_agent()"),
("PRAGMA database_size", "SELECT * FROM pragma_database_size()"),
("PRAGMA table_info('t')", "SELECT * FROM pragma_table_info('t')"),
("PRAGMA table_info(t)", "SELECT * FROM pragma_table_info('t')"),
("PRAGMA table_info(main.t)", "SELECT * FROM pragma_table_info('main.t')"),
("PRAGMA VERSION", "SELECT * FROM pragma_version()"),
("PRAGMA database_list", "SELECT * FROM pragma_database_list"),
] {
assert_eq!(rows(&db, statement), rows(&db, call), "{statement}");
}
}
#[test]
fn a_call_runs_the_table_function_the_same_query_over_it_would() {
let db = database();
for (call, query) in [
("CALL pragma_version()", "SELECT * FROM pragma_version()"),
("CALL range(3)", "SELECT * FROM range(3)"),
("CALL range(1, 7, 2)", "SELECT * FROM range(1, 7, 2)"),
("CALL pragma_table_info('t')", "SELECT * FROM pragma_table_info('t')"),
("CALL main.range(3)", "SELECT * FROM main.range(3)"),
("call RANGE(3)", "SELECT * FROM range(3)"),
] {
assert_eq!(rows(&db, call), rows(&db, query), "{call}");
}
assert_eq!(rows(&db, "EXPLAIN CALL range(3)"), rows(&db, "EXPLAIN SELECT * FROM range(3)"));
assert_eq!(
failure(&db, "CALL nope()"),
"Table Function with name nope does not exist!",
"the catalog answers for the name"
);
assert_eq!(failure(&db, "CALL abs(1)"), failure(&db, "SELECT * FROM abs(1)"));
assert!(
failure(&db, "CALL range(3) AS r").starts_with("syntax error at or near \"AS\""),
"{}",
failure(&db, "CALL range(3) AS r")
);
}
#[test]
fn a_pragma_with_an_equals_sign_sets_the_setting_a_plain_set_would() {
let db = database();
db.execute("PRAGMA memory_limit = '1GiB'").expect("a size");
assert_eq!(rows(&db, "SELECT current_setting('memory_limit')"), vec![vec![text("1.0 GiB")]]);
db.execute("PRAGMA threads = 4").expect("a count");
assert_eq!(rows(&db, "SELECT current_setting('threads')"), vec![vec![Value::BigInt(4)]]);
}
#[test]
fn the_show_pragmas_list_the_tables_the_databases_and_both_with_the_columns() {
let db = database();
db.execute("CREATE VIEW v AS SELECT 1 AS a").expect("a view");
assert_eq!(
rows(&db, "PRAGMA show_tables"),
vec![vec![text("empty")], vec![text("t")], vec![text("v")]]
);
assert_eq!(rows(&db, "PRAGMA show_databases"), vec![vec![text("memory")]]);
let names = |values: Vec<&str>| Value::List {
element: LogicalType::Varchar,
values: values.into_iter().map(text).collect(),
};
assert_eq!(
rows(&db, "PRAGMA show_tables_expanded"),
vec![
vec![
text("memory"),
text("main"),
text("empty"),
names(vec!["x"]),
names(vec!["INTEGER"]),
Value::Boolean(false),
],
vec![
text("memory"),
text("main"),
text("t"),
names(vec!["x", "s"]),
names(vec!["INTEGER", "VARCHAR"]),
Value::Boolean(false),
],
vec![
text("memory"),
text("main"),
text("v"),
names(vec!["a"]),
names(vec!["INTEGER"]),
Value::Boolean(false),
],
]
);
}
#[test]
fn the_show_statement_answers_the_pragma_of_the_same_name() {
let db = database();
for (statement, pragma) in [
("SHOW TABLES", "PRAGMA show_tables"),
("SHOW tables", "PRAGMA show_tables"),
("DESCRIBE TABLES", "PRAGMA show_tables"),
("SHOW DATABASES", "PRAGMA show_databases"),
("DESCRIBE DATABASES", "PRAGMA show_databases"),
("SHOW ALL", "PRAGMA show_tables_expanded"),
("SHOW ALL TABLES", "PRAGMA show_tables_expanded"),
("DESCRIBE ALL", "PRAGMA show_tables_expanded"),
] {
assert_eq!(rows(&db, statement), rows(&db, pragma), "{statement}");
}
db.execute("CREATE TABLE tables(a INTEGER)").expect("a table named tables");
assert_eq!(rows(&db, "SHOW TABLES"), rows(&db, "PRAGMA show_tables"));
assert_eq!(rows(&db, "DESCRIBE TABLES"), rows(&db, "PRAGMA show_tables"));
assert_eq!(rows(&db, "DESCRIBE main.tables"), rows(&db, "DESCRIBE SELECT * FROM tables"));
}
#[test]
fn a_name_that_exists_only_after_the_word_pragma_is_not_a_table_function() {
let db = database();
for name in ["pragma_show_tables", "pragma_show_databases", "pragma_show_tables_expanded"] {
assert_eq!(
failure(&db, &format!("SELECT * FROM {name}()")),
format!("Table Function with name {name} does not exist!")
);
}
assert!(!rows(&db, "PRAGMA show_tables").is_empty());
let message = failure(&db, "PRAGMA show_tables(1)");
assert!(message.contains("'show_tables(INTEGER)'"), "{message}");
assert!(message.ends_with("\tPRAGMA \"show_tables\"\n"), "{message}");
}
#[test]
fn a_pragma_that_is_wrong_is_complained_about_in_the_spelling_it_was_written_in() {
let db = database();
assert_eq!(failure(&db, "PRAGMA nope"), "Pragma Function with name nope does not exist!");
assert_eq!(failure(&db, "PRAGMA NOPE"), "Pragma Function with name NOPE does not exist!");
let message = failure(&db, "PRAGMA table_info");
assert!(message.contains("'table_info()'"), "{message}");
assert!(message.ends_with("\tPRAGMA \"table_info\"(VARCHAR)\n"), "{message}");
let message = failure(&db, "PRAGMA table_info(1)");
assert!(message.contains("'table_info(INTEGER)'"), "{message}");
let message = failure(&db, "PRAGMA version(1)");
assert!(message.ends_with("\tPRAGMA \"version\"\n"), "{message}");
}
#[test]
fn a_view_the_engine_ships_with_is_bound_when_it_is_first_read() {
let db = database();
let unbound = "SELECT column_count, is_bound FROM duckdb_views() WHERE view_name = 'schemata'";
assert_eq!(rows(&db, unbound), vec![vec![Value::Null, Value::Boolean(false)]]);
assert_eq!(
rows(&db, "SELECT count(*) FROM information_schema.schemata"),
vec![vec![Value::BigInt(5)]]
);
assert_eq!(rows(&db, unbound), vec![vec![Value::BigInt(7), Value::Boolean(true)]]);
}
#[test]
fn a_schema_carries_an_oid_of_its_own_and_not_its_databases() {
let db = database();
let rows = rows(&db, "SELECT oid, database_oid FROM duckdb_schemas()");
assert_eq!(rows.len(), 5);
for row in &rows {
assert_ne!(row[0], row[1]);
assert_ne!(row[0], Value::BigInt(0));
assert_ne!(row[1], Value::BigInt(0));
}
}
#[test]
fn the_tables_and_columns_tables_describe_what_was_created() {
let db = database();
let text = |value: &str| Value::Varchar(value.to_string());
db.execute("CREATE TABLE shapes(x INTEGER, s VARCHAR, d DECIMAL(9,2), b BOOLEAN)")
.expect("a fresh table");
db.execute("INSERT INTO shapes VALUES (1, 'a', 1.5, true)").expect("a row");
assert_eq!(
rows(
&db,
"SELECT column_count, estimated_size, index_count, check_constraint_count, sql \
FROM duckdb_tables() WHERE table_name = 'shapes'"
),
vec![vec![
Value::BigInt(4),
Value::BigInt(1),
Value::BigInt(0),
Value::BigInt(0),
text("CREATE TABLE shapes(x INTEGER, s VARCHAR, d DECIMAL(9,2), b BOOLEAN);"),
]]
);
assert_eq!(
rows(
&db,
"SELECT column_name, column_index, data_type, data_type_id, numeric_precision, \
numeric_precision_radix, numeric_scale FROM duckdb_columns() \
WHERE table_name = 'shapes' ORDER BY column_index"
),
vec![
vec![
text("x"),
Value::Integer(1),
text("INTEGER"),
Value::BigInt(13),
Value::Integer(32),
Value::Integer(2),
Value::Integer(0),
],
vec![
text("s"),
Value::Integer(2),
text("VARCHAR"),
Value::BigInt(25),
Value::Null,
Value::Null,
Value::Null,
],
vec![
text("d"),
Value::Integer(3),
text("DECIMAL(9,2)"),
Value::BigInt(21),
Value::Integer(9),
Value::Integer(10),
Value::Integer(2),
],
vec![
text("b"),
Value::Integer(4),
text("BOOLEAN"),
Value::BigInt(10),
Value::Null,
Value::Null,
Value::Null,
],
]
);
}
#[test]
fn a_table_and_its_columns_agree_on_the_oid_they_join_on() {
let db = database();
let text = |value: &str| Value::Varchar(value.to_string());
db.execute("CREATE TABLE joined(x INTEGER, s VARCHAR)").expect("a fresh table");
assert_eq!(
rows(
&db,
"SELECT c.column_name FROM duckdb_columns() c, duckdb_tables() t \
WHERE c.table_oid = t.table_oid AND t.table_name = 'joined' ORDER BY c.column_index"
),
vec![vec![text("x")], vec![text("s")]]
);
}
#[test]
fn a_not_null_column_says_so_in_both_tables() {
let db = database();
let text = |value: &str| Value::Varchar(value.to_string());
db.execute("CREATE TABLE n(a INTEGER NOT NULL, b INTEGER)").expect("a fresh table");
assert_eq!(
rows(&db, "SELECT sql FROM duckdb_tables() WHERE table_name = 'n'"),
vec![vec![text("CREATE TABLE n(a INTEGER NOT NULL, b INTEGER);")]]
);
assert_eq!(
rows(
&db,
"SELECT is_nullable FROM duckdb_columns() WHERE table_name = 'n' ORDER BY column_index"
),
vec![vec![Value::Boolean(false)], vec![Value::Boolean(true)]]
);
}
#[test]
fn a_view_lists_its_columns_the_way_a_table_does() {
let db = database();
let text = |value: &str| Value::Varchar(value.to_string());
db.execute("CREATE TABLE base(x INTEGER NOT NULL, s VARCHAR, d DECIMAL(9,2))")
.expect("a fresh table");
db.execute("CREATE VIEW v AS SELECT x, s, d, x + 1 AS e FROM base").expect("a fresh view");
db.execute("CREATE VIEW w(p, q) AS SELECT x, s FROM base").expect("a view with an alias list");
assert_eq!(
rows(
&db,
"SELECT table_name, column_name, column_index, is_nullable, data_type \
FROM duckdb_columns() WHERE table_name IN ('v', 'w') \
ORDER BY table_name, column_index"
),
vec![
vec![text("v"), text("x"), Value::Integer(1), Value::Boolean(true), text("INTEGER")],
vec![text("v"), text("s"), Value::Integer(2), Value::Boolean(true), text("VARCHAR")],
vec![
text("v"),
text("d"),
Value::Integer(3),
Value::Boolean(true),
text("DECIMAL(9,2)")
],
vec![text("v"), text("e"), Value::Integer(4), Value::Boolean(true), text("INTEGER")],
vec![text("w"), text("p"), Value::Integer(1), Value::Boolean(true), text("INTEGER")],
vec![text("w"), text("q"), Value::Integer(2), Value::Boolean(true), text("VARCHAR")],
]
);
assert_eq!(
rows(
&db,
"SELECT count(*) FROM duckdb_columns() c, duckdb_tables() t \
WHERE c.table_oid = t.table_oid AND c.table_name IN ('v', 'w')"
),
vec![vec![Value::BigInt(0)]]
);
}
#[test]
fn a_view_reports_itself_and_the_statement_it_was_written_as() {
let db = database();
let text = |value: &str| Value::Varchar(value.to_string());
db.execute("CREATE TABLE base(x INTEGER, s VARCHAR)").expect("a fresh table");
db.execute("CREATE VIEW v AS SELECT x, s FROM base WHERE x > 0").expect("a fresh view");
db.execute("CREATE VIEW w(p) AS -- a comment\n select X as Y from base")
.expect("a view with an alias list");
assert_eq!(
rows(
&db,
"SELECT view_name, column_count, internal, temporary, is_bound, sql \
FROM duckdb_views() WHERE view_name IN ('v', 'w') ORDER BY view_name"
),
vec![
vec![
text("v"),
Value::BigInt(2),
Value::Boolean(false),
Value::Boolean(false),
Value::Boolean(true),
text("CREATE VIEW v AS SELECT x, s FROM base WHERE (x > 0);"),
],
vec![
text("w"),
Value::BigInt(1),
Value::Boolean(false),
Value::Boolean(false),
Value::Boolean(true),
text("CREATE VIEW w (p) AS SELECT X AS Y FROM base;"),
],
]
);
assert_eq!(
rows(
&db,
"SELECT count(*) FROM duckdb_views() v, duckdb_columns() c \
WHERE v.view_oid = c.table_oid"
),
vec![vec![Value::BigInt(3)]]
);
assert!(rows(&db, "SELECT view_name FROM duckdb_views() WHERE view_name = 'base'").is_empty());
assert!(rows(&db, "SELECT table_name FROM duckdb_tables() WHERE table_name = 'v'").is_empty());
}
#[test]
fn the_engine_answers_for_its_optimizer_passes_and_its_extensions() {
let db = database();
let text = |value: &str| Value::Varchar(value.to_string());
assert_eq!(
rows(&db, "SELECT count(*) FROM duckdb_optimizers()"),
vec![vec![Value::BigInt(44)]]
);
assert_eq!(
rows(
&db,
"SELECT count(*) FROM duckdb_optimizers() WHERE name IN ('expression_rewriter', \
'distinct_aggregate_rewrite', 'filter_pushdown', 'empty_result_pullup', \
'unused_columns', 'limit_pushdown', 'top_n', 'late_materialization')"
),
vec![vec![Value::BigInt(8)]]
);
db.execute("SET disabled_optimizers = 'join_order,filter_pushdown'").expect("both names take");
assert_eq!(
rows(&db, "SELECT count(*) FROM duckdb_extensions()"),
vec![vec![Value::BigInt(31)]]
);
assert_eq!(
rows(&db, "SELECT extension_name FROM duckdb_extensions() WHERE loaded ORDER BY 1"),
vec![vec![text("core_functions")], vec![text("parquet")]]
);
assert_eq!(
rows(
&db,
"SELECT loaded, installed, install_path, install_mode, signature_key_fingerprint \
FROM duckdb_extensions() WHERE extension_name = 'parquet'"
),
vec![vec![
Value::Boolean(true),
Value::Boolean(true),
text("(BUILT-IN)"),
text("STATICALLY_LINKED"),
Value::Null,
]]
);
assert_eq!(
rows(
&db,
"SELECT loaded, installed, install_path, extension_version, install_mode, \
installed_from FROM duckdb_extensions() WHERE extension_name = 'spatial'"
),
vec![vec![
Value::Boolean(false),
Value::Boolean(false),
text(""),
text(""),
text("NOT_INSTALLED"),
text(""),
]]
);
let aliases = |values: Vec<&str>| Value::List {
element: LogicalType::Varchar,
values: values.into_iter().map(text).collect(),
};
assert_eq!(
rows(
&db,
"SELECT aliases FROM duckdb_extensions() \
WHERE extension_name IN ('httpfs', 'parquet') ORDER BY extension_name"
),
vec![vec![aliases(vec!["http", "https", "s3"])], vec![aliases(Vec::new())]]
);
assert_eq!(
rows(
&db,
"SELECT count(*) FROM duckdb_functions() \
WHERE function_name IN ('duckdb_extensions', 'duckdb_optimizers')"
),
vec![vec![Value::BigInt(2)]]
);
}
#[test]
fn the_engine_lists_its_parser_dialect_and_no_grammar_extensions() {
let db = database();
assert_eq!(rows(&db, "SELECT * FROM duckdb_dialects()"), vec![vec![text("duckdb")]]);
assert!(rows(&db, "SELECT * FROM duckdb_grammar_extensions()").is_empty());
assert_eq!(
rows(
&db,
"SELECT column_name, column_type FROM (DESCRIBE SELECT * FROM duckdb_grammar_extensions())"
),
vec![vec![text("name"), text("VARCHAR")], vec![text("description"), text("VARCHAR")]]
);
assert_eq!(
rows(
&db,
"SELECT count(*) FROM duckdb_functions() WHERE function_name IN ('duckdb_dialects', 'duckdb_grammar_extensions')"
),
vec![vec![Value::BigInt(2)]]
);
}
#[test]
fn a_star_in_a_view_is_expanded_again_every_time_the_view_is_read() {
let db = database();
let text = |value: &str| Value::Varchar(value.to_string());
db.execute("CREATE TABLE base(x INTEGER)").expect("a fresh table");
db.execute("CREATE VIEW star AS SELECT * FROM base").expect("a fresh view");
assert_eq!(
rows(&db, "SELECT column_name FROM duckdb_columns() WHERE table_name = 'star'"),
vec![vec![text("x")]]
);
db.query("SELECT * FROM star").expect("the view reads");
assert_eq!(
rows(&db, "SELECT column_name FROM duckdb_columns() WHERE table_name = 'star'"),
vec![vec![text("x")]]
);
}
#[test]
fn the_subscripts_that_are_refused_say_what_duckdb_says() {
let db = database();
let error = db.query("SELECT 'abcdef'[]").unwrap_err();
assert_eq!(error.code().duckdb_name(), "Parser Error");
assert_eq!(error.message(), "Empty subscript '[]' is not allowed");
let error = db.query("SELECT 'abcdef'[1:6:2]").unwrap_err();
assert_eq!(error.code().duckdb_name(), "Not implemented Error");
assert!(error.message().starts_with("Slice with steps has not been implemented"), "{error}");
let error = db.query("SELECT array_slice(1, 2, 3)").unwrap_err();
assert_eq!(error.code().duckdb_name(), "Binder Error");
assert_eq!(error.message(), "ARRAY_SLICE can only operate on LISTs and VARCHARs");
assert!(failure(&db, "SELECT 'abcdef'[1.5]").starts_with("No function matches"));
}
#[test]
fn a_dollar_quoted_string_is_the_text_between_the_tags() {
let db = Database::new();
assert_eq!(rows(&db, "SELECT $$dollar quoted$$"), vec![vec![text("dollar quoted")]]);
assert_eq!(rows(&db, "SELECT $tag$body$tag$"), vec![vec![text("body")]]);
assert_eq!(rows(&db, "SELECT $$a$$ = 'a'"), vec![vec![Value::Boolean(true)]]);
assert_eq!(db.query("SELECT $$a$$").unwrap().names(), &["'a'".to_string()]);
}
#[test]
fn a_string_that_is_not_the_other_type_is_a_conversion_error() {
let db = database();
assert!(failure(&db, "SELECT 1 = 'abc'").contains("abc"));
assert!(failure(&db, "SELECT CAST('2013-07-15' AS DATE) = 'nope'").contains("nope"));
}
#[test]
fn a_cast_that_cannot_hold_the_value_is_an_error_and_try_cast_is_null() {
let db = database();
assert!(failure(&db, "SELECT CAST('oops' AS INTEGER)").contains("oops"));
assert_eq!(rows(&db, "SELECT TRY_CAST('oops' AS INTEGER)"), vec![vec![Value::Null]]);
}
#[test]
fn a_failed_cast_says_what_duckdb_says() {
let db = database();
assert_eq!(failure(&db, "SELECT 'abc'::TINYINT"), "Could not convert string 'abc' to INT8");
assert_eq!(failure(&db, "SELECT '300'::TINYINT"), "Could not convert string '300' to INT8");
assert_eq!(
failure(&db, "SELECT 'abc'::DECIMAL(4,1)"),
"Could not convert string \"abc\" to DECIMAL(4,1)"
);
assert_eq!(
failure(&db, "SELECT 300::INTEGER::TINYINT"),
"Type INT32 with value 300 can't be cast because the value is out of range for the \
destination type INT8"
);
assert_eq!(
failure(&db, "SELECT 999.9::DECIMAL(4,1)::TINYINT"),
"Failed to cast decimal value 1000 to type INT8"
);
assert_eq!(
failure(&db, "SELECT 200000::DECIMAL(4,1)"),
"Could not cast value 200000 to DECIMAL(4,1)"
);
assert_eq!(
failure(&db, "SELECT 200000.5::DECIMAL(7,1)::DECIMAL(4,1)"),
"Casting value \"200000.5\" to type DECIMAL(4,1) failed: value is out of range!"
);
assert_eq!(
failure(&db, "SELECT DATE '1970-01-01'::INTEGER"),
"Unimplemented type for cast (DATE -> INTEGER)"
);
assert_eq!(rows(&db, "SELECT TRY_CAST(DATE '1970-01-01' AS INTEGER)"), vec![vec![Value::Null]]);
}
#[test]
fn a_written_day_that_does_not_exist_is_refused() {
let db = database();
assert_eq!(
failure(&db, "SELECT '2021-04-31'::DATE"),
"date field value out of range: \"2021-04-31\""
);
assert_eq!(
failure(&db, "SELECT '2021-02-29 10:00:00'::TIMESTAMP"),
"timestamp field value out of range: \"2021-02-29 10:00:00\""
);
assert_eq!(
failure(&db, "SELECT 'yesterday'::DATE"),
"invalid date field format: \"yesterday\", expected format is (YYYY-MM-DD)"
);
assert_eq!(
rows(&db, "SELECT '2020-02-29 10:30:00'::DATE"),
vec![vec![Value::Date(days_from_civil(2020, 2, 29))]]
);
}
#[test]
fn a_time_can_be_written_and_read_back() {
let db = database();
let at = |hours: i64, minutes: i64, seconds: i64, micros: i64| {
vec![vec![Value::Time(((hours * 60 + minutes) * 60 + seconds) * 1_000_000 + micros)]]
};
assert_eq!(rows(&db, "SELECT TIME '12:34:56'"), at(12, 34, 56, 0));
assert_eq!(rows(&db, "SELECT CAST('12:34:56' AS TIME)"), at(12, 34, 56, 0));
assert_eq!(rows(&db, "SELECT '12:34'::TIME"), at(12, 34, 0, 0));
assert_eq!(rows(&db, "SELECT '12:34:56.1234567'::TIME"), at(12, 34, 56, 123_456));
assert_eq!(rows(&db, "SELECT '2024-01-02 03:04:05'::TIME"), at(3, 4, 5, 0));
assert_eq!(rows(&db, "SELECT '12:34:56 UTC'::TIME"), at(12, 34, 56, 0));
assert_eq!(rows(&db, "SELECT TIMESTAMP '2024-01-02 03:04:05'::TIME"), at(3, 4, 5, 0));
assert_eq!(rows(&db, "SELECT '12:34:56.100'::TIME::VARCHAR"), vec![vec![text("12:34:56.1")]]);
assert_eq!(rows(&db, "SELECT typeof(TIME '12:34:56')"), vec![vec![text("TIME")]]);
assert_eq!(
failure(&db, "SELECT '25:00:00'::TIME"),
"time field value out of range: \"25:00:00\", expected format is ([YYYY-MM-DD ]HH:MM:SS[.MS])"
);
assert_eq!(rows(&db, "SELECT TRY_CAST('25:00:00' AS TIME)"), vec![vec![Value::Null]]);
assert_eq!(
failure(&db, "SELECT DATE '2024-01-02'::TIME"),
"Unimplemented type for cast (DATE -> TIME)"
);
}
#[test]
fn a_type_in_front_of_a_string_is_a_cast_of_that_string() {
let db = database();
assert_eq!(
rows(&db, "SELECT DATE '2013-07-15'"),
vec![vec![Value::Date(days_from_civil(2013, 7, 15))]]
);
assert_eq!(
rows(&db, "SELECT date '2013-07-15'"),
vec![vec![Value::Date(days_from_civil(2013, 7, 15))]],
"the type is a name and names are not case sensitive"
);
assert_eq!(rows(&db, "SELECT INTEGER '42' + 1"), vec![vec![Value::Integer(43)]]);
assert_eq!(rows(&db, "SELECT VARCHAR 'hi'"), vec![vec![text("hi")]]);
let result = db.query("SELECT DATE '1995-09-01'").unwrap();
assert_eq!(result.names(), &["CAST('1995-09-01' AS DATE)"]);
assert!(failure(&db, "SELECT DATE 'nope'").contains("nope"));
}
#[test]
fn a_prefix_in_front_of_a_string_decides_how_the_string_is_read() {
let db = database();
assert_eq!(rows(&db, "SELECT E'a\\tb'"), vec![vec![text("a\tb")]]);
assert_eq!(rows(&db, "SELECT e'a\\u00e9b'"), vec![vec![text("aéb")]]);
assert_eq!(rows(&db, "SELECT N'abc'"), vec![vec![text("abc")]]);
assert_eq!(rows(&db, "SELECT B'101'"), vec![vec![text("b101")]], "not a bit string upstream");
assert_eq!(rows(&db, "SELECT length(E'a\\nb')"), vec![vec![Value::BigInt(3)]]);
let result = db.query("SELECT E'ab', N'ab'").unwrap();
assert_eq!(result.names(), &["'ab'", "CAST('ab' AS VARCHAR)"]);
}
#[test]
fn a_hex_string_answers_with_the_bytes_it_names() {
let db = database();
assert_eq!(rows(&db, "SELECT x'ff'"), vec![vec![Value::Blob(vec![0xff])]]);
assert_eq!(rows(&db, "SELECT X'4142'"), vec![vec![Value::Blob(b"AB".to_vec())]]);
assert_eq!(rows(&db, "SELECT x''"), vec![vec![Value::Blob(Vec::new())]]);
assert_eq!(rows(&db, "SELECT x'ff' = '\\xFF'::BLOB"), vec![vec![Value::Boolean(true)]]);
let result = db.query("SELECT x'ff41'").unwrap();
assert_eq!(result.names(), &["'\\xFFA'::BLOB"]);
assert_eq!(result.types(), &[LogicalType::Blob]);
assert!(failure(&db, "SELECT x'41zz'").contains("string -> blob conversion of string"));
}
#[test]
fn a_missing_table_names_the_table() {
let db = database();
assert!(failure(&db, "SELECT * FROM nope").contains("nope"));
}
#[test]
fn a_missing_column_names_the_column() {
let db = database();
assert!(failure(&db, "SELECT nope FROM t").contains("nope"));
}
#[test]
fn a_syntax_error_is_a_parser_error_rather_than_a_panic() {
let db = database();
let error = db.query("SELECT FROM WHERE").unwrap_err();
assert_eq!(error.code(), rudb_common::ErrorCode::Parser);
}
#[test]
fn value_returns_one_cell_and_refuses_anything_else() {
let db = database();
assert_eq!(db.value("SELECT count(*) FROM t").unwrap(), Value::BigInt(4));
assert!(db.value("SELECT * FROM t").is_err());
}
#[test]
fn a_plan_prints_parent_before_child() {
let db = database();
let text = db.plan("SELECT x FROM t WHERE x > 1").unwrap();
let lines: Vec<&str> = text.lines().collect();
assert!(lines[0].starts_with("Project"), "{text}");
assert!(lines[1].trim_start().starts_with("Filter"), "{text}");
assert!(lines[2].trim_start().starts_with("Get memory.main.t"), "{text}");
}
#[test]
fn a_query_that_cannot_match_answers_nothing_without_reading_the_table() {
let db = database();
let text = db.plan("SELECT x FROM t WHERE false").unwrap();
assert!(!text.contains("Get memory.main.t"), "{text}");
assert_eq!(rows(&db, "SELECT x FROM t WHERE false"), Vec::<Vec<Value>>::new());
assert_eq!(rows(&db, "SELECT x FROM t LIMIT 0"), Vec::<Vec<Value>>::new());
db.execute("SET disabled_optimizers = 'empty_result_pullup'").unwrap();
let text = db.plan("SELECT x FROM t WHERE false").unwrap();
assert!(text.contains("Get memory.main.t"), "{text}");
assert_eq!(rows(&db, "SELECT x FROM t WHERE false"), Vec::<Vec<Value>>::new());
assert_eq!(rows(&db, "SELECT x FROM t LIMIT 0"), Vec::<Vec<Value>>::new());
}
#[test]
fn an_aggregate_over_a_query_that_cannot_match_still_answers_its_row() {
let db = database();
assert_eq!(
rows(&db, "SELECT count(*), min(x) FROM t WHERE false"),
vec![vec![Value::BigInt(0), Value::Null]]
);
assert_eq!(
rows(&db, "SELECT x, count(*) FROM t WHERE false GROUP BY x"),
Vec::<Vec<Value>>::new()
);
db.execute("SET disabled_optimizers = 'empty_result_pullup'").unwrap();
assert_eq!(
rows(&db, "SELECT count(*), min(x) FROM t WHERE false"),
vec![vec![Value::BigInt(0), Value::Null]]
);
}
#[test]
fn a_predicate_that_is_always_true_leaves_no_filter_behind() {
let db = database();
let text = db.plan("SELECT x FROM t WHERE true").unwrap();
assert!(!text.contains("Filter"), "{text}");
let text = db.plan("SELECT x FROM t WHERE true AND x > 1").unwrap();
assert!(text.contains("Filter (#0.0::INTEGER > 1::INTEGER)::BOOLEAN"), "{text}");
assert_eq!(
rows(&db, "SELECT x FROM t WHERE true AND x > 1"),
vec![vec![integer(3)], vec![integer(2)]]
);
}
#[test]
fn a_limit_ends_up_under_the_projection_and_answers_the_same_rows() {
let db = database();
let text = db.plan("SELECT x + 1 AS y FROM t LIMIT 2").unwrap();
let lines: Vec<&str> = text.lines().collect();
assert!(lines[0].starts_with("Project"), "{text}");
assert!(lines[1].trim_start().starts_with("Limit 2 offset 0"), "{text}");
let wanted = vec![vec![integer(4)], vec![integer(2)]];
assert_eq!(rows(&db, "SELECT x + 1 AS y FROM t LIMIT 2"), wanted);
db.execute("SET disabled_optimizers = 'limit_pushdown'").unwrap();
let text = db.plan("SELECT x + 1 AS y FROM t LIMIT 2").unwrap();
assert!(text.lines().next().unwrap().starts_with("Limit 2 offset 0"), "{text}");
assert_eq!(rows(&db, "SELECT x + 1 AS y FROM t LIMIT 2"), wanted);
}
#[test]
fn an_order_by_with_a_limit_is_still_one_operator_after_the_limit_has_moved() {
let db = database();
let printed = db.plan("SELECT s FROM t ORDER BY x DESC LIMIT 2").unwrap();
assert!(printed.contains("TopN 2 offset 0"), "{printed}");
assert_eq!(
rows(&db, "SELECT s FROM t ORDER BY x DESC LIMIT 2"),
vec![vec![text("a")], vec![text("c")]]
);
}
#[test]
fn creating_a_table_twice_is_an_error_and_dropping_it_makes_room_again() {
let db = Database::new();
db.create_table("t", vec![Field::new("x", LogicalType::Integer)]).unwrap();
assert!(db.create_table("t", vec![Field::new("x", LogicalType::Integer)]).is_err());
db.drop_table("t").unwrap();
db.create_table("t", vec![Field::new("x", LogicalType::Integer)]).unwrap();
}
#[test]
fn a_result_wider_than_one_vector_reads_across_chunks() {
let db = Database::new();
db.create_table("big", vec![Field::new("x", LogicalType::Integer)]).unwrap();
let total = VECTOR_SIZE + VECTOR_SIZE / 2;
let mut rows = Vec::new();
for x in 0..i32::try_from(total).unwrap() {
rows.push(vec![Value::Integer(x)]);
}
db.append("big", &rows).unwrap();
let first = i32::try_from(VECTOR_SIZE).unwrap();
let last = i32::try_from(total).unwrap() - 1;
let result = db.query("SELECT x FROM big").unwrap();
assert_eq!(result.len(), total);
assert!(result.chunks().len() > 1);
assert_eq!(result.value_at(0, 0), integer(0));
assert_eq!(result.value_at(VECTOR_SIZE, 0), integer(first));
assert_eq!(result.value_at(total - 1, 0), integer(last));
assert_eq!(result.row(total), None);
assert_eq!(result.value_at(total, 0), Value::Null);
assert_eq!(db.value("SELECT count(*) FROM big").unwrap(), Value::BigInt(total as i64));
}
#[test]
fn a_table_can_be_named_with_its_schema_and_its_catalog() {
let db = database();
assert_eq!(db.value("SELECT count(*) FROM main.t").unwrap(), Value::BigInt(4));
assert_eq!(db.value("SELECT count(*) FROM memory.main.t").unwrap(), Value::BigInt(4));
assert_eq!(db.table_len("main.t").unwrap(), 4);
}
fn scripted(statements: &[&str]) -> Database {
let db = Database::new();
for statement in statements {
db.execute(statement).unwrap_or_else(|error| panic!("{statement}: {error}"));
}
db
}
fn refusal(db: &Database, sql: &str) -> String {
db.execute(sql).unwrap_err().message().to_string()
}
#[test]
fn a_table_can_be_created_filled_and_read_without_leaving_sql() {
let db = scripted(&[
"CREATE TABLE t (a INTEGER, b VARCHAR)",
"INSERT INTO t VALUES (1, 'one'), (2, 'two')",
]);
assert_eq!(
rows(&db, "SELECT a, b FROM t"),
vec![vec![integer(1), text("one")], vec![integer(2), text("two")],]
);
}
#[test]
fn a_value_is_cast_to_the_column_it_lands_in() {
let db = scripted(&["CREATE TABLE t (a BIGINT, b DOUBLE)", "INSERT INTO t VALUES (1, 2)"]);
assert_eq!(rows(&db, "SELECT a, b FROM t"), vec![vec![Value::BigInt(1), Value::Double(2.0)]]);
}
#[test]
fn a_column_the_insert_did_not_name_is_null() {
let db = scripted(&[
"CREATE TABLE t (a INTEGER, b VARCHAR, c INTEGER)",
"INSERT INTO t (c, a) VALUES (30, 10)",
]);
assert_eq!(
rows(&db, "SELECT a, b, c FROM t"),
vec![vec![integer(10), Value::Null, integer(30)]]
);
}
#[test]
fn an_insert_that_reads_its_own_target_sees_the_rows_that_were_there_when_it_started() {
let db = scripted(&[
"CREATE TABLE t (a INTEGER)",
"INSERT INTO t VALUES (1), (2)",
"INSERT INTO t SELECT a + 10 FROM t",
]);
assert_eq!(
rows(&db, "SELECT a FROM t"),
vec![vec![integer(1)], vec![integer(2)], vec![integer(11)], vec![integer(12)],]
);
}
#[test]
fn create_table_as_takes_its_types_from_the_query() {
let db = scripted(&[
"CREATE TABLE t (a INTEGER)",
"INSERT INTO t VALUES (1), (2), (3)",
"CREATE TABLE counted AS SELECT count(*) AS n, sum(a) AS total FROM t",
]);
assert_eq!(
db.query("SELECT n, total FROM counted").unwrap().types(),
&[LogicalType::BigInt, LogicalType::HugeInt]
);
assert_eq!(
rows(&db, "SELECT n, total FROM counted"),
vec![vec![Value::BigInt(3), Value::HugeInt(6)]]
);
}
#[test]
fn a_create_table_as_can_rename_the_query_s_columns() {
let db = scripted(&["CREATE TABLE t (x, y) AS SELECT 1, 'a'"]);
assert_eq!(db.query("SELECT * FROM t").unwrap().names(), &["x", "y"]);
}
#[test]
fn a_short_column_list_renames_the_front_and_leaves_the_rest_to_the_query() {
let db = scripted(&["CREATE TABLE t (a) AS SELECT 1, 2"]);
assert_eq!(db.query("SELECT * FROM t").unwrap().names(), &["a", "2"]);
}
#[test]
fn a_column_list_longer_than_the_query_is_the_error_duckdb_writes_for_it() {
let db = Database::new();
assert_eq!(
refusal(&db, "CREATE TABLE t (a, b, c) AS SELECT 1, 2"),
"Target table has more colum names than query result."
);
}
#[test]
fn a_query_that_names_two_columns_the_same_gets_them_renamed_apart() {
let db = scripted(&["CREATE TABLE t AS SELECT 1 AS a, 2 AS a, 3 AS a"]);
assert_eq!(db.query("SELECT * FROM t").unwrap().names(), &["a", "a_1", "a_2"]);
}
#[test]
fn a_renamed_column_steps_past_a_name_the_query_already_used() {
let db = scripted(&["CREATE TABLE t AS SELECT 1 AS a, 2 AS a, 3 AS a_1"]);
assert_eq!(db.query("SELECT * FROM t").unwrap().names(), &["a", "a_1", "a_1_1"]);
let db = scripted(&["CREATE TABLE t AS SELECT 1 AS a_1, 2 AS a, 3 AS a"]);
assert_eq!(db.query("SELECT * FROM t").unwrap().names(), &["a_1", "a", "a_2"]);
}
#[test]
fn the_renaming_is_case_insensitive_and_keeps_the_case_it_was_written_in() {
let db = scripted(&["CREATE TABLE t AS SELECT 1 AS a, 2 AS A"]);
assert_eq!(db.query("SELECT * FROM t").unwrap().names(), &["a", "A_1"]);
}
#[test]
fn a_column_list_turns_the_renaming_off_and_a_repeat_becomes_an_error() {
let db = scripted(&["CREATE TABLE t (z) AS SELECT 1 AS a, 2 AS a"]);
assert_eq!(db.query("SELECT * FROM t").unwrap().names(), &["z", "a"]);
let db = Database::new();
assert_eq!(
refusal(&db, "CREATE TABLE t (z) AS SELECT 1 AS a, 2 AS a, 3 AS a"),
"Column with name a already exists!"
);
let db = Database::new();
assert_eq!(
refusal(&db, "CREATE TABLE t (a, a) AS SELECT 1, 2"),
"Column with name a already exists!"
);
}
#[test]
fn two_columns_of_one_name_in_a_plain_create_is_the_same_error() {
let db = Database::new();
assert_eq!(
refusal(&db, "CREATE TABLE t (Abc INTEGER, aBC VARCHAR)"),
"Column with name aBC already exists!"
);
}
#[test]
fn if_not_exists_leaves_the_table_and_its_rows_alone() {
let db = scripted(&[
"CREATE TABLE t (a INTEGER)",
"INSERT INTO t VALUES (1)",
"CREATE TABLE IF NOT EXISTS t (b VARCHAR, c VARCHAR)",
]);
assert_eq!(db.query("SELECT * FROM t").unwrap().names(), &["a"]);
assert_eq!(db.table_len("t").unwrap(), 1);
assert!(refusal(&db, "CREATE TABLE t (b VARCHAR)").contains("already exists"));
assert_eq!(db.query("SELECT * FROM t").unwrap().names(), &["a"]);
}
#[test]
fn or_replace_runs_the_query_against_the_table_it_is_about_to_replace() {
let db = scripted(&[
"CREATE TABLE t (a INTEGER)",
"INSERT INTO t VALUES (1), (2), (3)",
"CREATE OR REPLACE TABLE t AS SELECT a * 2 AS a FROM t",
]);
assert_eq!(
rows(&db, "SELECT a FROM t"),
vec![vec![integer(2)], vec![integer(4)], vec![integer(6)],]
);
}
#[test]
fn dropping_takes_a_list_and_if_exists_forgives_a_name_that_is_not_there() {
let db = scripted(&[
"CREATE TABLE a (x INTEGER)",
"CREATE TABLE b (x INTEGER)",
"DROP TABLE a, b",
"DROP TABLE IF EXISTS a",
]);
assert!(db.with_catalog(|catalog| catalog.tables().next().is_none()));
assert!(refusal(&db, "DROP TABLE a").contains("does not exist"));
}
#[test]
fn values_is_a_query_and_the_columns_take_the_type_every_row_agrees_on() {
let db = Database::new();
let result = db.query("VALUES (1, 'a'), (2.5, 'b')").unwrap();
assert_eq!(result.names(), &["col0", "col1"]);
assert_eq!(
result.types(),
&[LogicalType::Decimal { width: 11, scale: 1 }, LogicalType::Varchar]
);
assert_eq!(result.len(), 2);
assert_eq!(
rows(&db, "SELECT col0 FROM (VALUES (3), (1), (2)) ORDER BY col0"),
vec![vec![integer(1)], vec![integer(2)], vec![integer(3)]]
);
}
#[test]
fn multiplying_two_decimals_keeps_the_digits_of_both_of_them() {
let db = Database::new();
let sql = "SELECT 1.5 * 1.5 AS p";
assert_eq!(db.query(sql).unwrap().types(), &[LogicalType::Decimal { width: 4, scale: 2 }]);
assert_eq!(rows(&db, sql), vec![vec![Value::Decimal { unscaled: 225, width: 4, scale: 2 }]]);
let run = "SELECT a * 1.5 AS p FROM range(3) t(a)";
assert_eq!(db.query(run).unwrap().types(), &[LogicalType::Decimal { width: 21, scale: 1 }]);
let decimal = |unscaled| vec![Value::Decimal { unscaled, width: 21, scale: 1 }];
assert_eq!(rows(&db, run), vec![decimal(0), decimal(15), decimal(30)]);
}
#[test]
fn integer_division_with_a_decimal_in_it_is_a_double() {
let db = Database::new();
let sql = "SELECT 7.5 // 2.5 AS q";
assert_eq!(db.query(sql).unwrap().types(), &[LogicalType::Double]);
assert_eq!(rows(&db, sql), vec![vec![Value::Double(3.0)]]);
let sql = "SELECT 7.5 // 2 AS q";
assert_eq!(db.query(sql).unwrap().types(), &[LogicalType::Double]);
assert_eq!(rows(&db, sql), vec![vec![Value::Double(3.75)]]);
let sql = "SELECT 7.9 // 1.0 AS q";
assert_eq!(rows(&db, sql), vec![vec![Value::Double(7.9)]]);
let sql = "SELECT 7 // 2 AS q";
assert_eq!(db.query(sql).unwrap().types(), &[LogicalType::Integer]);
assert_eq!(rows(&db, sql), vec![vec![Value::Integer(3)]]);
let run = "SELECT a // 2.0 AS q FROM range(4) t(a)";
assert_eq!(db.query(run).unwrap().types(), &[LogicalType::Double]);
let double = |value| vec![Value::Double(value)];
assert_eq!(rows(&db, run), vec![double(0.0), double(0.5), double(1.0), double(1.5)]);
}
#[test]
fn an_overflow_says_what_duckdb_says() {
let db = Database::new();
let cases = [
("127::TINYINT + 1::TINYINT", "Overflow in addition of INT8 (127 + 1)!"),
("(-128)::TINYINT - 1::TINYINT", "Overflow in subtraction of INT8 (-128 - 1)!"),
("127::TINYINT * 2::TINYINT", "Overflow in multiplication of INT8 (127 * 2)!"),
("32767::SMALLINT + 1::SMALLINT", "Overflow in addition of INT16 (32767 + 1)!"),
("2147483647::INTEGER + 1::INTEGER", "Overflow in addition of INT32 (2147483647 + 1)!"),
(
"9223372036854775807::BIGINT + 1::BIGINT",
"Overflow in addition of INT64 (9223372036854775807 + 1)!",
),
("255::UTINYINT + 1::UTINYINT", "Overflow in addition of UINT8 (255 + 1)!"),
("65535::USMALLINT + 1::USMALLINT", "Overflow in addition of UINT16 (65535 + 1)!"),
("4294967295::UINTEGER + 1::UINTEGER", "Overflow in addition of UINT32 (4294967295 + 1)!"),
(
"18446744073709551615::UBIGINT + 1::UBIGINT",
"Overflow in addition of UINT64 (18446744073709551615 + 1)!",
),
(
"170141183460469231731687303715884105727::HUGEINT + 1::HUGEINT",
"Overflow in addition of INT128 (170141183460469231731687303715884105727 + 1)!",
),
(
"99999999999999999999999999999999999999::DECIMAL(38,0) + 1::DECIMAL(38,0)",
"Overflow in addition of DECIMAL(38) (99999999999999999999999999999999999999 + 1);",
),
(
"(-99999999999999999999999999999999999999)::DECIMAL(38,0) - 1::DECIMAL(38,0)",
"Overflow in subtract of DECIMAL(38) (-99999999999999999999999999999999999999 - 1);",
),
(
"9999999999.99::DECIMAL(38,2) * 9999999999999999999999999999.99::DECIMAL(38,2)",
concat!(
"Overflow in multiplication of DECIMAL(38) ",
"(999999999999 * 999999999999999999999999999999). ",
"You might want to add an explicit cast to a decimal with a smaller scale.",
),
),
(
"9999999999.9999::DECIMAL(18,4) * 99999999.9999::DECIMAL(18,4)",
concat!(
"Overflow in multiplication of DECIMAL(18) (99999999999999 * 999999999999). ",
"You might want to add an explicit cast to a bigger decimal.",
),
),
("abs((-2147483648)::INTEGER)", "Overflow on abs(-2147483648)"),
("abs((-32768)::SMALLINT)", "Overflow on abs(-32768)"),
("abs((-9223372036854775808)::BIGINT)", "Overflow on abs(-9223372036854775808)"),
(
"-((-170141183460469231731687303715884105728)::HUGEINT)",
"Overflow in negation of numeric value!",
),
];
for (expression, expected) in cases {
assert_eq!(failure(&db, &format!("SELECT {expression}")), expected, "{expression}");
}
let one = "FROM range(1, 2) t(a)";
let sql = format!("SELECT 127::TINYINT + a::TINYINT {one}");
assert_eq!(failure(&db, &sql), "Overflow in addition of INT8 (127 + 1)!");
let big = "99999999999999999999999999999999999999::DECIMAL(38,0)";
let sql = format!("SELECT {big} + a::DECIMAL(38,0) {one}");
let expected =
"Overflow in addition of DECIMAL(38) (99999999999999999999999999999999999999 + 1);";
assert_eq!(failure(&db, &sql), expected);
let sql = "SELECT abs(a::INTEGER) FROM range(-2147483648, -2147483647) t(a)";
assert_eq!(failure(&db, sql), "Overflow on abs(-2147483648)");
let sql = "SELECT -(a::INTEGER) FROM range(-2147483648, -2147483647) t(a)";
assert_eq!(failure(&db, sql), "Overflow in negation of numeric value!");
}
#[test]
fn negating_the_smallest_value_of_a_type_widens_by_one_step() {
let db = Database::new();
let widened = [
("(-128)::TINYINT", LogicalType::SmallInt, Value::SmallInt(128)),
("(-32768)::SMALLINT", LogicalType::Integer, Value::Integer(32768)),
("(-2147483648)::INTEGER", LogicalType::BigInt, Value::BigInt(2147483648)),
(
"(-9223372036854775808)::BIGINT",
LogicalType::HugeInt,
Value::HugeInt(9223372036854775808),
),
];
for (argument, ty, answer) in widened {
let sql = format!("SELECT -({argument}) AS n");
assert_eq!(db.query(&sql).unwrap().types(), &[ty], "{argument}");
assert_eq!(rows(&db, &sql), vec![vec![answer]], "{argument}");
}
let sql = "SELECT -((-127)::TINYINT) AS n";
assert_eq!(db.query(sql).unwrap().types(), &[LogicalType::TinyInt]);
assert_eq!(rows(&db, sql), vec![vec![Value::TinyInt(127)]]);
let sql = "SELECT -(a::INTEGER) AS n FROM range(-2147483647, -2147483646) t(a)";
assert_eq!(db.query(sql).unwrap().types(), &[LogicalType::Integer]);
assert_eq!(rows(&db, sql), vec![vec![Value::Integer(2147483647)]]);
}
#[test]
fn a_number_that_is_not_a_number_says_what_duckdb_says() {
let db = Database::new();
let cases = [
("SELECT 1e", "Invalid Input Error", "Could not convert string '1e' to DOUBLE"),
("SELECT 1e-", "Invalid Input Error", "Could not convert string '1e-' to DOUBLE"),
("SELECT 1e2e", "Parser Error", "Already found scientific notation"),
(
"SELECT 1.2.3",
"Invalid Input Error",
"Failed to cast value: Could not convert string \"1.2.3\" to DECIMAL(4,1)",
),
];
for (sql, class, message) in cases {
let error = db.query(sql).unwrap_err();
assert_eq!(error.code().duckdb_name(), class, "{sql}");
assert_eq!(error.message(), message, "{sql}");
}
}
#[test]
fn an_underscore_in_a_number_is_a_separator() {
let db = Database::new();
assert_eq!(rows(&db, "SELECT 1_000"), vec![vec![integer(1000)]]);
assert_eq!(rows(&db, "SELECT 1_000_000"), vec![vec![integer(1_000_000)]]);
assert_eq!(rows(&db, "SELECT 1e1_0"), vec![vec![Value::Double(1e10)]]);
let sql = "SELECT 1_0.5_0";
assert_eq!(db.query(sql).unwrap().types(), &[LogicalType::Decimal { width: 4, scale: 2 }]);
assert_eq!(rows(&db, sql), vec![vec![Value::Decimal { unscaled: 1050, width: 4, scale: 2 }]]);
}
#[test]
fn dividing_by_zero_raises_on_two_of_the_three_operators() {
let db = Database::new();
let double = |value| vec![vec![Value::Double(value)]];
assert_eq!(rows(&db, "SELECT 7 / 0"), double(f64::INFINITY));
assert_eq!(rows(&db, "SELECT (-7) / 0"), double(f64::NEG_INFINITY));
assert!(matches!(rows(&db, "SELECT 0 / 0")[0][0], Value::Double(answer) if answer.is_nan()));
assert!(matches!(rows(&db, "SELECT 7.5::DOUBLE % 0.0::DOUBLE")[0][0],
Value::Double(answer) if answer.is_nan()));
let run = rows(&db, "SELECT 10 / a FROM range(-1, 2) t(a)");
assert_eq!(run[0], vec![Value::Double(-10.0)]);
assert!(matches!(run[1][0], Value::Double(answer) if answer.is_infinite()));
assert_eq!(run[2], vec![Value::Double(10.0)]);
}
#[test]
fn dividing_by_zero_says_what_duckdb_says() {
let db = Database::new();
db.create_table("z", vec![Field::new("a", LogicalType::Integer)]).unwrap();
db.append("z", &[vec![Value::Integer(1)], vec![Value::Integer(-2)]]).unwrap();
let advice = "Use TRY(...) to return NULL for this expression, or SET \
null_on_division_by_zero=true to return NULL for all divisions by zero.";
let cases = [
("SELECT 7 // 0", "(7 // 0)"),
("SELECT 7 % 0", "(7 % 0)"),
("SELECT 7.50 % 0.00", "(7.50 % 0.00)"),
("SELECT 7.0::DOUBLE // 0.0::DOUBLE", "(7.0 // 0.0)"),
("SELECT a // 0 FROM z", "(a // 0)"),
("SELECT a % 0 FROM z", "(a % 0)"),
("SELECT a::DOUBLE // 0.0 FROM z", "(CAST(a AS DOUBLE) // 0.0)"),
("SELECT (a + 1) // 0 FROM z", "((a + 1) // 0)"),
("SELECT abs(a) % 0 FROM z", "(abs(a) % 0)"),
("SELECT -a // 0 FROM z", "(-(a) // 0)"),
("SELECT 10 // (a - a) FROM z", "(10 // (a - a))"),
("SELECT 10.5 % (a - a) FROM z", "(10.5 % CAST((a - a) AS DECIMAL(11,1)))"),
];
for (sql, quoted) in cases {
let expected = format!("Division by zero in expression {quoted}. {advice}");
assert_eq!(failure(&db, sql), expected, "{sql}");
}
let error = db.query("SELECT a // 0 FROM z").expect_err("integer division by zero raises");
assert_eq!(error.span(), Some(Span::new(7, 13)));
}
#[test]
fn adding_two_decimals_widens_the_answer_by_the_digit_the_carry_needs() {
let db = Database::new();
let sql = "SELECT 999999999999999999::DECIMAL(18,0) + 999999999999999999::DECIMAL(18,0) AS s";
let result = db.query(sql).unwrap();
assert_eq!(result.types(), &[LogicalType::Decimal { width: 19, scale: 0 }]);
assert_eq!(
rows(&db, sql),
vec![vec![Value::Decimal { unscaled: 1_999_999_999_999_999_998, width: 19, scale: 0 }]]
);
assert_eq!(
db.query("SELECT 2.0 + 1::INTEGER").unwrap().types(),
&[LogicalType::Decimal { width: 12, scale: 1 }]
);
}
#[test]
fn a_statement_that_writes_something_the_answer_would_depend_on_is_refused() {
let db = scripted(&["CREATE TABLE t (a INTEGER)"]);
for statement in [
"CREATE TABLE u (a INTEGER PRIMARY KEY)",
"INSERT INTO t VALUES (1) RETURNING a",
"INSERT INTO t (a, a) VALUES (1, 2)",
"CREATE TABLE u (a INTEGER, a VARCHAR)",
] {
let message = refusal(&db, statement);
assert!(!message.is_empty(), "{statement} was accepted");
}
assert!(db.with_catalog(|catalog| catalog.tables().all(|table| table.name().table != "u")));
}
#[test]
fn a_temporary_table_shadows_a_stored_one_of_the_same_name() {
let db = scripted(&[
"CREATE TABLE t (a INTEGER)",
"INSERT INTO t VALUES (99)",
"CREATE TEMPORARY TABLE t (a INTEGER)",
"INSERT INTO t VALUES (1)",
]);
assert_eq!(rows(&db, "SELECT a FROM t"), vec![vec![integer(1)]]);
assert_eq!(rows(&db, "SELECT a FROM temp.t"), vec![vec![integer(1)]]);
assert_eq!(rows(&db, "SELECT a FROM temp.main.t"), vec![vec![integer(1)]]);
assert_eq!(rows(&db, "SELECT a FROM memory.main.t"), vec![vec![integer(99)]]);
assert_eq!(rows(&db, "SELECT a FROM main.t"), vec![vec![integer(1)]]);
}
#[test]
fn a_drop_takes_the_temporary_table_first_and_the_stored_one_after() {
let db = scripted(&[
"CREATE TABLE t (a INTEGER)",
"CREATE TEMPORARY TABLE t (a INTEGER)",
"DROP TABLE t",
]);
assert_eq!(
rows(&db, "SELECT database_name FROM duckdb_tables() WHERE table_name = 't'"),
vec![vec![text("memory")]]
);
db.execute("DROP TABLE t").expect("the stored one is still there");
assert_eq!(
rows(&db, "SELECT database_name FROM duckdb_tables() WHERE table_name = 't'"),
Vec::<Vec<Value>>::new()
);
}
#[test]
fn a_temporary_name_can_only_say_the_temp_database() {
let db = scripted(&["CREATE TABLE stored (a INTEGER)"]);
for statement in [
"CREATE TEMPORARY TABLE one (a INTEGER)",
"CREATE TEMPORARY TABLE temp.two (a INTEGER)",
"CREATE TEMPORARY TABLE main.three (a INTEGER)",
"CREATE TEMPORARY TABLE temp.main.four (a INTEGER)",
"CREATE TEMPORARY VIEW five AS SELECT 1 AS a",
"CREATE TEMPORARY VIEW main.six AS SELECT 1 AS a",
] {
db.execute(statement).unwrap_or_else(|error| panic!("{statement}: {error}"));
}
assert_eq!(
rows(&db, "SELECT count(*) FROM duckdb_tables() WHERE database_name = 'temp'"),
vec![vec![Value::BigInt(4)]]
);
assert_eq!(
rows(&db, "SELECT count(*) FROM duckdb_views() WHERE database_name = 'temp'"),
vec![vec![Value::BigInt(2)]]
);
let outside = "TEMPORARY table names can *only* use the \"temp\" catalog";
for statement in [
"CREATE TEMPORARY TABLE memory.seven (a INTEGER)",
"CREATE TEMPORARY TABLE memory.main.seven (a INTEGER)",
"CREATE TEMPORARY TABLE system.main.seven (a INTEGER)",
"CREATE TEMPORARY VIEW memory.seven AS SELECT 1 AS a",
] {
assert_eq!(refusal(&db, statement), outside, "{statement}");
}
assert_eq!(
refusal(&db, "CREATE TEMPORARY TABLE nowhere.eight (a INTEGER)"),
"Schema with name nowhere does not exist!"
);
}
#[test]
fn a_create_that_is_not_temporary_cannot_name_the_temp_database() {
let db = scripted(&[]);
let inside = "Only TEMPORARY table names can use the \"temp\" catalog";
assert_eq!(refusal(&db, "CREATE TABLE temp.a (i INTEGER)"), inside);
assert_eq!(refusal(&db, "CREATE VIEW temp.b AS SELECT 1"), inside);
}
#[test]
fn a_temporary_entry_is_temporary_and_is_not_internal() {
let db = scripted(&[
"CREATE TABLE stored (a INTEGER)",
"CREATE TEMPORARY TABLE tt (b INTEGER)",
"CREATE TEMPORARY VIEW tv AS SELECT 1 AS c",
]);
assert_eq!(
rows(
&db,
"SELECT table_name, internal, temporary FROM duckdb_tables() ORDER BY table_name"
),
vec![
vec![text("stored"), Value::Boolean(false), Value::Boolean(false)],
vec![text("tt"), Value::Boolean(false), Value::Boolean(true)],
]
);
assert_eq!(
rows(&db, "SELECT internal, temporary FROM duckdb_views() WHERE view_name = 'tv'"),
vec![vec![Value::Boolean(false), Value::Boolean(true)]]
);
assert_eq!(
rows(
&db,
"SELECT table_name, internal FROM duckdb_columns() \
WHERE table_name IN ('tt', 'tv') ORDER BY table_name"
),
vec![vec![text("tt"), Value::Boolean(false)], vec![text("tv"), Value::Boolean(false)],]
);
assert_eq!(
rows(&db, "SELECT internal FROM duckdb_databases() WHERE database_name = 'temp'"),
vec![vec![Value::Boolean(true)]]
);
assert_eq!(
rows(&db, "SELECT internal FROM duckdb_schemas() WHERE database_name = 'temp'"),
vec![vec![Value::Boolean(true)]]
);
}
#[test]
fn information_schema_calls_a_temporary_table_a_local_temporary() {
let db = scripted(&[
"CREATE TABLE stored (a INTEGER)",
"CREATE TEMPORARY TABLE tt (b INTEGER)",
"CREATE TEMPORARY VIEW tv AS SELECT 1 AS c",
]);
assert_eq!(
rows(
&db,
"SELECT table_catalog, table_name, table_type FROM information_schema.tables \
ORDER BY table_name"
),
vec![
vec![text("memory"), text("stored"), text("BASE TABLE")],
vec![text("temp"), text("tt"), text("LOCAL TEMPORARY")],
vec![text("temp"), text("tv"), text("VIEW")],
]
);
}
#[test]
fn the_listings_show_a_temporary_entry_beside_a_stored_one() {
let db = scripted(&[
"CREATE TABLE stored (a INTEGER)",
"CREATE TEMPORARY TABLE tt (b INTEGER)",
"CREATE TEMPORARY VIEW tv AS SELECT 1 AS c",
]);
assert_eq!(
rows(&db, "PRAGMA show_tables"),
vec![vec![text("stored")], vec![text("tt")], vec![text("tv")],]
);
assert_eq!(
rows(&db, "PRAGMA show_tables_expanded")
.into_iter()
.map(|row| (row[0].clone(), row[2].clone(), row[5].clone()))
.collect::<Vec<_>>(),
vec![
(text("memory"), text("stored"), Value::Boolean(false)),
(text("temp"), text("tt"), Value::Boolean(true)),
(text("temp"), text("tv"), Value::Boolean(true)),
]
);
assert_eq!(rows(&db, "PRAGMA show_databases"), vec![vec![text("memory")]]);
}
#[test]
fn a_temporary_table_takes_the_clauses_a_stored_one_takes() {
let db = scripted(&[
"CREATE TABLE t (a INTEGER)",
"CREATE TEMPORARY TABLE t AS SELECT 7 AS a, 8 AS b",
]);
assert_eq!(rows(&db, "SELECT a, b FROM t"), vec![vec![integer(7), integer(8)]]);
db.execute("CREATE OR REPLACE TEMPORARY TABLE t (a INTEGER, b INTEGER, c INTEGER)")
.expect("replacing the temporary one");
assert_eq!(
rows(
&db,
"SELECT database_name, column_count FROM duckdb_tables() \
WHERE table_name = 't' ORDER BY database_name"
),
vec![vec![text("memory"), Value::BigInt(1)], vec![text("temp"), Value::BigInt(3)]]
);
db.execute("CREATE TEMPORARY TABLE IF NOT EXISTS t (a INTEGER)").expect("already there");
assert_eq!(
refusal(&db, "CREATE TEMPORARY TABLE t (a INTEGER)"),
"Table with name \"t\" already exists!"
);
}
#[test]
fn a_not_null_column_refuses_a_null_and_keeps_what_came_before_it() {
let db = scripted(&[
"CREATE TABLE t (a INTEGER NOT NULL, b VARCHAR)",
"INSERT INTO t VALUES (1, NULL)",
]);
assert_eq!(refusal(&db, "INSERT INTO t VALUES (NULL, 'x')"), "NOT NULL constraint failed: t.a");
assert_eq!(db.table_len("t").unwrap(), 1);
assert_eq!(rows(&db, "SELECT a, b FROM t"), vec![vec![integer(1), Value::Null]]);
}
#[test]
fn a_table_function_produces_rows_where_a_table_would() {
let db = Database::new();
assert_eq!(
rows(&db, "SELECT * FROM range(3)"),
vec![vec![Value::BigInt(0)], vec![Value::BigInt(1)], vec![Value::BigInt(2)]]
);
assert_eq!(
rows(&db, "SELECT * FROM generate_series(3)"),
vec![
vec![Value::BigInt(0)],
vec![Value::BigInt(1)],
vec![Value::BigInt(2)],
vec![Value::BigInt(3)],
]
);
}
#[test]
fn the_column_is_called_what_the_function_is_called_until_it_is_aliased() {
let db = Database::new();
let result = db.query("SELECT * FROM range(2)").unwrap();
assert_eq!(result.names()[0], "range");
let result = db.query("SELECT i FROM range(2) t(i)").unwrap();
assert_eq!(result.names()[0], "i");
let result = db.query("SELECT t.range FROM range(2) t").unwrap();
assert_eq!(result.names()[0], "range");
}
#[test]
fn a_table_function_joins_and_aggregates_like_anything_else_in_a_from_clause() {
let db = database();
assert_eq!(rows(&db, "SELECT count(*) FROM range(10)"), vec![vec![Value::BigInt(10)]]);
assert_eq!(rows(&db, "SELECT sum(range) FROM range(1, 5)"), vec![vec![Value::HugeInt(10)]]);
assert_eq!(
rows(&db, "SELECT count(*) FROM t, range(3)"),
vec![vec![Value::BigInt(12)]],
"four rows against three is twelve"
);
assert_eq!(
rows(&db, "SELECT x FROM t JOIN range(2) ON t.x = range ORDER BY x"),
vec![vec![integer(1)], vec![integer(1)]]
);
}
#[test]
fn the_arguments_are_expressions_and_a_column_in_one_is_lateral() {
let db = database();
assert_eq!(rows(&db, "SELECT count(*) FROM range(2 + 3)"), vec![vec![Value::BigInt(5)]]);
assert_eq!(rows(&db, "SELECT count(*) FROM t, range(t.x)"), vec![vec![Value::BigInt(7)]]);
let message = failure(&db, "SELECT count(*) FROM range(t.x), t");
assert!(message.contains("t"), "{message}");
}
#[test]
fn a_table_function_that_does_not_exist_says_so_rather_than_being_read_as_a_table() {
let db = Database::new();
let message = failure(&db, "SELECT * FROM read_json('x.json')");
assert!(message.contains("read_json"), "{message}");
let message = failure(&db, "SELECT * FROM nowhere.range(3)");
assert!(message.contains("nowhere"), "{message}");
let message = failure(&db, "SELECT * FROM range(1, 2, 3, 4)");
assert!(message.contains("range"), "{message}");
}
#[test]
fn a_step_of_zero_is_the_one_call_that_is_an_error_rather_than_an_empty_result() {
let db = Database::new();
let message = failure(&db, "SELECT * FROM range(1, 5, 0)");
assert!(message.contains("interval cannot be 0"), "{message}");
assert!(rows(&db, "SELECT * FROM range(5, 1)").is_empty());
assert!(rows(&db, "SELECT * FROM range(NULL)").is_empty());
}
#[test]
fn a_range_wider_than_one_chunk_comes_out_whole_and_in_order() {
let db = Database::new();
let result = db.query("SELECT count(*), min(range), max(range) FROM range(3000)").unwrap();
let row = result.rows().next().unwrap();
assert_eq!(row, vec![Value::BigInt(3000), Value::BigInt(0), Value::BigInt(2999)]);
}
#[test]
fn a_result_converts_to_arrow_columns_with_the_names_the_query_gave_them() {
let db = database();
let result = db.query("SELECT x, s FROM t ORDER BY x, s").unwrap();
let batches = result.to_arrow().unwrap();
assert_eq!(batches.len(), result.chunk_count());
let batch = &batches[0];
assert_eq!(batch.len(), 4);
assert_eq!(
batch.schema().fields,
vec![
arrow::Field::new("x", arrow::DataType::Int32),
arrow::Field::new("s", arrow::DataType::Utf8)
]
);
assert_eq!(
batch.column(0).unwrap().values(),
&[1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]
);
assert_eq!(batch.column(1).unwrap().values(), b"aca");
assert_eq!(batch.column(1).unwrap().null_count(), 1);
}
#[test]
fn a_query_that_produced_no_rows_still_says_what_its_columns_were() {
let db = database();
let result = db.query("SELECT x FROM empty").unwrap();
assert!(result.to_arrow().unwrap().is_empty());
let schema = result.arrow_schema().unwrap();
assert_eq!(schema.fields, vec![arrow::Field::new("x", arrow::DataType::Int32)]);
let empty = arrow::RecordBatch::empty(schema).unwrap();
assert_eq!(empty.width(), 1);
assert!(empty.is_empty());
}
#[test]
fn an_aggregate_over_a_wide_result_keeps_every_chunk_as_its_own_batch() {
let db = Database::new();
let total = VECTOR_SIZE + VECTOR_SIZE / 2;
let result = db.query(&format!("SELECT range FROM range({total})")).unwrap();
let batches = result.to_arrow().unwrap();
assert!(batches.len() > 1, "{total} rows is more than one chunk");
let rows: usize = batches.iter().map(rudb_arrow::RecordBatch::len).sum();
assert_eq!(rows, total);
let bytes: usize = batches.iter().map(|batch| batch.column(0).unwrap().values().len()).sum();
assert_eq!(bytes, total * 8);
}
#[test]
fn a_database_remembers_what_it_was_opened_with() {
let config = Config::new()
.with_memory_limit_text("2GB")
.unwrap()
.with_threads(3)
.unwrap()
.with_query_timeout(Duration::from_secs(10));
let db = Database::open_with(":memory:", config).unwrap();
assert_eq!(db.config().memory_limit(), Some(2_000_000_000));
assert_eq!(db.config().threads(), 3);
assert_eq!(db.config().query_timeout(), Some(Duration::from_secs(10)));
db.query("SELECT 1").unwrap();
assert_eq!(db.config().threads(), 3);
}
#[test]
fn a_database_opened_the_plain_way_has_the_defaults() {
let db = Database::new();
assert_eq!(db.config(), Config::default());
assert_eq!(db.config().memory_limit(), rudb_io::default_memory_limit());
assert_eq!(db.memory().limit(), db.config().memory_limit());
}
const FOREVER: &str = "SELECT count(*) FROM range(100000000000)";
#[test]
fn a_query_that_runs_too_long_is_stopped_by_the_limit_it_was_opened_with() {
let db = Database::with_config(Config::new().with_query_timeout(Duration::from_millis(50)));
let started = std::time::Instant::now();
let error = db.query(FOREVER).expect_err("that does not finish");
assert_eq!(error.code().duckdb_name(), "Interrupt Error");
assert!(error.message().contains("50 millisecond"), "{error}");
assert!(started.elapsed() < Duration::from_secs(10), "{:?}", started.elapsed());
}
#[test]
fn a_limit_does_not_stop_a_query_that_finishes_inside_it() {
let db = Database::with_config(Config::new().with_query_timeout(Duration::from_secs(60)));
assert_eq!(db.value("SELECT 42").unwrap(), Value::Integer(42));
assert_eq!(db.value("SELECT 43").unwrap(), Value::Integer(43));
}
#[test]
fn a_join_that_runs_too_long_is_stopped_partway_through_its_own_loop() {
let db = Database::with_config(Config::new().with_query_timeout(Duration::from_secs(1)));
db.execute("CREATE TABLE l AS SELECT i AS k FROM range(50000) t(i)").unwrap();
db.execute("CREATE TABLE r AS SELECT i * 2 AS k FROM range(20000) t(i)").unwrap();
let started = std::time::Instant::now();
let error =
db.query("SELECT count(*) FROM l JOIN r ON l.k < r.k").expect_err("that does not finish");
assert_eq!(error.code().duckdb_name(), "Interrupt Error");
assert!(started.elapsed() < Duration::from_secs(10), "{:?}", started.elapsed());
}
#[test]
fn another_thread_can_interrupt_a_running_query() {
let db = Database::new();
let connection = db.connect();
let stopper = connection.clone();
let watchdog = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(50));
stopper.interrupt();
});
let error = connection.query(FOREVER).expect_err("the other thread stopped it");
watchdog.join().expect("the watchdog ran");
assert_eq!(error.code().duckdb_name(), "Interrupt Error");
assert_eq!(error.message(), "Interrupted!");
}
#[test]
fn an_interrupt_stops_the_statement_it_was_meant_for_and_not_the_next_one() {
let db = Database::new();
let connection = db.connect();
connection.interrupt();
assert_eq!(connection.value("SELECT 1").unwrap(), Value::Integer(1));
}
#[test]
fn a_statement_that_writes_is_stoppable_too_and_leaves_nothing_behind() {
let db = Database::with_config(Config::new().with_query_timeout(Duration::from_millis(50)));
db.execute("CREATE TABLE big (x BIGINT)").unwrap();
let error = db
.execute("INSERT INTO big SELECT x FROM range(100000000000) t(x)")
.expect_err("that does not finish");
assert_eq!(error.code().duckdb_name(), "Interrupt Error");
assert_eq!(db.table_len("big").unwrap(), 0);
}
const SMALL: u64 = 1 << 20;
#[test]
fn a_query_that_buffers_too_much_is_stopped_by_the_memory_limit() {
let db = Database::with_config(Config::new().with_memory_limit(SMALL));
let error = db.query("SELECT * FROM range(10000000) ORDER BY range").expect_err("too large");
assert_eq!(error.code().duckdb_name(), "Out of Memory Error");
assert!(error.message().contains("could not allocate"), "{error}");
assert!(error.message().contains("1.0 MiB used"), "{error}");
}
#[test]
fn every_operator_that_buffers_is_held_to_the_limit() {
let queries = [
"SELECT * FROM range(10000000) ORDER BY range",
"SELECT range, count(*) FROM range(10000000) GROUP BY range",
"SELECT DISTINCT range FROM range(10000000)",
"SELECT * FROM range(10000000) UNION ALL SELECT * FROM range(10)",
"SELECT * FROM range(10000000) a, range(10000000) b WHERE a.range = b.range",
];
for query in queries {
let db = Database::with_config(Config::new().with_memory_limit(SMALL));
let error = db.query(query).err().unwrap_or_else(|| panic!("{query} should not have fit"));
assert_eq!(error.code().duckdb_name(), "Out of Memory Error", "{query}");
}
}
#[test]
fn one_long_group_key_does_not_charge_every_short_one_for_its_length() {
let db = Database::with_config(Config::new().with_memory_limit(4 << 20));
db.create_table("k", vec![Field::new("s", LogicalType::Varchar)]).unwrap();
let mut rows = vec![vec![Value::Varchar("x".repeat(100_000))]];
rows.extend((0..200).map(|n| vec![Value::Varchar(format!("group {n}"))]));
db.append("k", &rows).unwrap();
let result = db.query("SELECT s, count(*) FROM k GROUP BY s").expect("the table fits");
assert_eq!(result.len(), 201);
let loose = Database::new();
loose.create_table("k", vec![Field::new("s", LogicalType::Varchar)]).unwrap();
loose.append("k", &rows).unwrap();
assert_eq!(loose.query("SELECT s, count(*) FROM k GROUP BY s").unwrap().len(), 201);
}
#[test]
fn a_group_by_too_large_for_its_budget_spills_rather_than_stopping() {
let db = Database::with_config(Config::new().with_memory_limit(300 << 20));
let query = "SELECT range, count(*) FROM range(1000000) GROUP BY range";
assert_eq!(db.query(query).expect("it spills rather than stopping").len(), 1_000_000);
assert!(
db.memory().peak() <= 300 << 20,
"{} was held to get there, against a limit of {}",
db.memory().peak(),
300 << 20
);
let tight = Database::with_config(Config::new().with_memory_limit(SMALL));
let error = tight.query(query).err().unwrap_or_else(|| panic!("a megabyte holds none of this"));
assert_eq!(error.code().duckdb_name(), "Out of Memory Error");
}
#[test]
fn the_budget_is_given_back_when_the_query_stops() {
let db = Database::with_config(Config::new().with_memory_limit(SMALL));
db.query("SELECT * FROM range(10000000) ORDER BY range").expect_err("too large");
assert_eq!(db.memory().used(), 0, "an operator that failed still gave its rows back");
assert_eq!(db.value("SELECT 1").unwrap(), Value::Integer(1));
}
#[test]
fn a_result_holds_its_bytes_until_it_is_dropped() {
let db = Database::with_config(Config::new().with_memory_limit(SMALL));
let result = db.query("SELECT * FROM range(1000)").unwrap();
assert!(result.footprint() > 0, "a thousand rows are charged something");
assert_eq!(db.memory().used(), result.footprint());
drop(result);
assert_eq!(db.memory().used(), 0);
}
#[test]
fn query_metrics_report_the_memory_budget_high_water_mark() {
let db = Database::new();
let result = db.query("SELECT range, count(*) FROM range(10000) GROUP BY range").unwrap();
let measured = result.metrics().expect("a query carries execution metrics");
assert!(measured.resource.peak_bytes > 0, "a buffering operator reserved memory");
assert_eq!(measured.resource.peak_bytes, db.memory().peak());
}
#[test]
fn a_database_with_no_limit_counts_what_it_holds_anyway() {
let db = Database::with_config(Config::new().with_no_memory_limit());
let result = db.query("SELECT * FROM range(1000)").unwrap();
assert_eq!(db.memory().limit(), None);
assert_eq!(db.memory().used(), result.footprint());
}
#[test]
fn the_limit_is_on_the_database_rather_than_on_each_query() {
let db = Database::with_config(Config::new().with_memory_limit(SMALL));
let wide = "SELECT range FROM range(80000)";
let first = db.query(wide).expect("one fits");
let error = db.query(wide).expect_err("there is no room for a second copy");
assert_eq!(error.code().duckdb_name(), "Out of Memory Error");
drop(first);
db.query(wide).expect("the room came back");
}
#[test]
fn set_disabled_optimizers_turns_a_pass_off_for_the_statements_that_follow() {
let db = Database::new();
let folded = db.plan("SELECT 1 + 2").unwrap();
assert!(folded.contains('3'), "{folded}");
db.execute("SET disabled_optimizers = 'expression_rewriter'").unwrap();
let unfolded = db.plan("SELECT 1 + 2").unwrap();
assert!(unfolded.contains("\"+\""), "{unfolded}");
assert_eq!(db.setting("disabled_optimizers").unwrap(), "expression_rewriter");
db.execute("RESET disabled_optimizers").unwrap();
assert_eq!(db.plan("SELECT 1 + 2").unwrap(), folded);
}
#[test]
fn every_name_the_pass_list_publishes_is_a_name_the_statement_accepts() {
let names = crate::optimizers();
assert!(names.len() >= 6, "{names:?}");
let db = Database::new();
for name in &names {
db.execute(&format!("SET disabled_optimizers = '{name}'")).expect(name);
}
let all = names.join(",");
db.execute(&format!("SET disabled_optimizers = '{all}'")).expect("every pass off at once");
let mut sorted = names.clone();
sorted.sort_unstable();
assert_eq!(db.setting("disabled_optimizers").unwrap(), sorted.join(","));
assert_eq!(db.value("SELECT 1 + 2").unwrap(), Value::Integer(3));
let unoptimized = db.plan("SELECT 1 + 2").unwrap();
assert!(unoptimized.contains("\"+\""), "{unoptimized}");
}
#[test]
fn a_pass_that_nobody_has_is_refused_by_the_statement_that_named_it() {
let db = Database::new();
let error = db.execute("SET disabled_optimizers = 'no_such_pass'").unwrap_err();
assert_eq!(error.code().duckdb_name(), "Parser Error");
assert_eq!(db.setting("disabled_optimizers").unwrap(), "", "a refused set changed nothing");
}
#[test]
fn a_pass_duckdb_has_and_rudb_has_not_built_is_taken_and_does_nothing() {
let db = Database::new();
let folded = db.plan("SELECT 1 + 2").unwrap();
for name in ["compressed_materialization", "join_filter_pushdown", "common_subexpressions"] {
db.execute(&format!("SET disabled_optimizers = '{name}'")).expect(name);
assert_eq!(db.setting("disabled_optimizers").unwrap(), name);
assert_eq!(db.plan("SELECT 1 + 2").unwrap(), folded, "{name} turned something off");
}
}
#[test]
fn the_name_is_read_without_regard_to_case_because_two_corpus_files_shout_it() {
let db = Database::new();
db.execute("SET disabled_optimizers = 'LATE_MATERIALIZATION'").unwrap();
assert_eq!(db.setting("disabled_optimizers").unwrap(), "late_materialization");
db.execute("SET disabled_optimizers = 'Top_N'").unwrap();
assert_eq!(db.setting("disabled_optimizers").unwrap(), "top_n");
}
#[test]
fn the_setting_reads_back_as_what_was_understood_rather_than_as_what_was_written() {
let db = Database::new();
db.execute("SET disabled_optimizers = ' TOP_N , join_order , top_n ,'").unwrap();
assert_eq!(db.setting("disabled_optimizers").unwrap(), "join_order,top_n");
}
#[test]
fn every_name_the_binary_accepts_is_a_name_the_statement_accepts() {
let db = Database::new();
for name in rudb_opt::UPSTREAM {
db.execute(&format!("SET disabled_optimizers = '{name}'")).expect(name);
assert_eq!(db.setting("disabled_optimizers").unwrap(), name);
}
assert_eq!(rudb_opt::UPSTREAM.len(), 44, "the pinned binary lists forty four");
assert!(!rudb_opt::UPSTREAM.contains(&"dependent_group_keys"));
}
#[test]
fn set_memory_limit_moves_the_budget_the_queries_after_it_are_held_to() {
let db = Database::with_config(Config::new().with_no_memory_limit());
assert_eq!(db.memory().limit(), None);
db.execute("SET memory_limit = '1GiB'").unwrap();
assert_eq!(db.memory().limit(), Some(1 << 30));
assert_eq!(db.config().memory_limit(), Some(1 << 30));
assert_eq!(db.setting("memory_limit").unwrap(), "1.0 GiB");
db.execute("SET memory_limit = '1GB'").unwrap();
assert_eq!(db.memory().limit(), Some(1_000_000_000));
db.execute("RESET memory_limit").unwrap();
assert_eq!(db.memory().limit(), None, "back to what the database was opened with");
}
#[test]
fn a_memory_limit_set_in_the_middle_of_a_session_refuses_the_next_query_that_passes_it() {
let db = Database::new();
let wide = "SELECT range FROM range(80000)";
db.query(wide).expect("no limit yet");
db.execute("SET memory_limit = '100KB'").unwrap();
let error = db.query(wide).expect_err("the limit is on now");
assert_eq!(error.code().duckdb_name(), "Out of Memory Error");
}
#[test]
fn set_threads_is_recorded_even_though_nothing_runs_in_parallel_yet() {
let db = Database::with_config(Config::new().with_threads(2).unwrap());
db.execute("SET threads = 8").unwrap();
assert_eq!(db.config().threads(), 8);
assert_eq!(db.setting("threads").unwrap(), "8");
let error = db.execute("SET threads = 0").unwrap_err();
assert_eq!(error.to_string(), "Syntax Error: Must have at least 1 thread!");
db.execute("RESET threads").unwrap();
assert_eq!(db.config().threads(), 2, "reset is what the database was opened with");
}
#[test]
fn a_relationship_is_declared_by_a_setting_and_read_back_from_one() {
let db = Database::new();
assert_eq!(db.setting("graph_links").unwrap(), "", "a fresh database declares nothing");
db.execute(
"SET graph_links = 'lineitem(l_orderkey) -> orders(o_orderkey), \
orders(o_custkey) -> customer(c_custkey)'",
)
.unwrap();
let declared = rudb_graph::parse_links(&db.setting("graph_links").unwrap()).unwrap();
assert_eq!(declared.len(), 2);
assert_eq!(declared[0].name(), "lineitem(l_orderkey) -> orders(o_orderkey)");
assert_eq!(
declared[0].cardinality,
rudb_graph::Cardinality::Unverified,
"a declaration says what the author believes and the build says what is true"
);
let error = db.execute("SET graph_links = 'lineitem(l_orderkey)'").unwrap_err();
assert!(error.to_string().contains("child(column) -> parent(column)"), "{error}");
assert_eq!(
rudb_graph::parse_links(&db.setting("graph_links").unwrap()).unwrap().len(),
2,
"and the declaration that worked is still there"
);
db.execute("RESET graph_links").unwrap();
assert_eq!(db.setting("graph_links").unwrap(), "");
}
#[test]
fn rudb_links_says_what_is_declared_and_what_of_it_is_built() {
let db = Database::new();
assert!(rows(&db, "SELECT name FROM rudb_links()").is_empty(), "nothing declared, no rows");
db.create_table("customer", vec![Field::new("c_custkey", LogicalType::Integer)]).unwrap();
db.append("customer", &[vec![Value::Integer(1)], vec![Value::Integer(2)]]).unwrap();
db.execute(
"SET graph_links = 'orders(o_custkey) -> customer(c_custkey), \
lineitem(l_orderkey) -> orders(o_orderkey)'",
)
.unwrap();
let listed = rows(
&db,
"SELECT parent_table, cardinality, key_map, key_map_bytes, link, note FROM rudb_links() \
ORDER BY parent_table",
);
assert_eq!(
listed,
vec![
vec![
Value::Varchar("customer".into()),
Value::Varchar("unverified".into()),
Value::Null,
Value::Null,
Value::Null,
Value::Varchar("no key map is stored".into()),
],
vec![
Value::Varchar("orders".into()),
Value::Varchar("unverified".into()),
Value::Null,
Value::Null,
Value::Null,
Value::Varchar("no table of that name".into()),
],
],
"a memory table holds no sections and a table that is not here holds nothing at all"
);
assert_eq!(
rows(
&db,
"SELECT child_table, child_key, parent_key FROM rudb_links() WHERE \
parent_table = 'customer'"
),
vec![vec![
Value::Varchar("orders".into()),
Value::Varchar("o_custkey".into()),
Value::Varchar("c_custkey".into()),
]]
);
}
#[test]
fn a_forward_link_takes_the_monotone_form_only_when_every_child_row_has_a_parent_above_the_last() {
let path = std::env::temp_dir().join(format!(
"rudb-graph-forms-{}-{}.rdb",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("the clock advances")
.as_nanos()
));
let db = Database::open(path.to_str().expect("a UTF-8 temporary path")).unwrap();
db.execute("CREATE TABLE customer (c_custkey INTEGER)").unwrap();
db.execute("CREATE TABLE orders (o_custkey INTEGER)").unwrap();
db.execute("CREATE TABLE returns (r_custkey INTEGER)").unwrap();
db.execute("INSERT INTO customer SELECT i FROM range(1, 4001) AS r(i)").unwrap();
db.execute("INSERT INTO orders SELECT 1 + (i - 1) / 3 FROM range(1, 10001) AS r(i)").unwrap();
db.execute("INSERT INTO returns SELECT 1 + (i - 1) / 3 FROM range(1, 10001) AS r(i)").unwrap();
db.execute("INSERT INTO returns VALUES (9999)").unwrap();
db.execute(
"SET graph_links = 'orders(o_custkey) -> customer(c_custkey), \
returns(r_custkey) -> customer(c_custkey)'",
)
.unwrap();
db.execute("CHECKPOINT").unwrap();
assert_eq!(
rows(
&db,
"SELECT child_table, cardinality, link, note FROM rudb_links() ORDER BY child_table"
),
vec![
vec![
Value::Varchar("orders".into()),
Value::Varchar("exactly one".into()),
Value::Varchar("monotone".into()),
Value::Null,
],
vec![
Value::Varchar("returns".into()),
Value::Varchar("at most one".into()),
Value::Varchar("packed".into()),
Value::Varchar("some child rows have no parent, so this is not exactly one".into()),
],
],
"one unmatched child row costs the relationship both its form and its totality"
);
assert_eq!(
rows(&db, "SELECT count(*) FROM orders o JOIN customer c ON o.o_custkey = c.c_custkey"),
vec![vec![Value::BigInt(10000)]]
);
assert_eq!(
rows(&db, "SELECT count(*) FROM returns r JOIN customer c ON r.r_custkey = c.c_custkey"),
vec![vec![Value::BigInt(10000)]]
);
drop(db);
std::fs::remove_file(&path).ok();
}
#[test]
fn rudb_links_says_what_shape_the_relationship_turned_out_to_have() {
let path = std::env::temp_dir().join(format!(
"rudb-graph-degrees-{}-{}.rdb",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("the clock advances")
.as_nanos()
));
let db = Database::open(path.to_str().expect("a UTF-8 temporary path")).unwrap();
db.execute("CREATE TABLE customer (c_custkey INTEGER)").unwrap();
db.execute("CREATE TABLE orders (o_custkey INTEGER)").unwrap();
db.execute("CREATE TABLE skewed (s_custkey INTEGER)").unwrap();
db.execute("INSERT INTO customer SELECT i FROM range(1, 4001) AS r(i)").unwrap();
db.execute("INSERT INTO orders SELECT 1 + (i - 1) / 3 FROM range(1, 10001) AS r(i)").unwrap();
db.execute("INSERT INTO skewed SELECT 1 FROM range(1, 9001) AS r(i)").unwrap();
db.execute("INSERT INTO skewed SELECT 1 + i FROM range(1, 1001) AS r(i)").unwrap();
db.execute(
"SET graph_links = 'orders(o_custkey) -> customer(c_custkey), \
skewed(s_custkey) -> customer(c_custkey)'",
)
.unwrap();
db.execute("CHECKPOINT").unwrap();
let held = rows(
&db,
"SELECT child_table, degree_max, degree_p99, parent_unique, child_total FROM rudb_links() \
ORDER BY child_table",
);
assert_eq!(
held,
vec![
vec![
Value::Varchar("orders".into()),
Value::BigInt(3),
Value::BigInt(3),
Value::Boolean(true),
Value::Boolean(true),
],
vec![
Value::Varchar("skewed".into()),
Value::BigInt(9000),
Value::BigInt(1),
Value::Boolean(true),
Value::Boolean(true),
],
],
"the shape of the relationship, not the shape of the declaration"
);
db.execute("CREATE TABLE scattered (x_custkey INTEGER)").unwrap();
db.execute("INSERT INTO scattered SELECT 1 + (i * 1237) % 4000 FROM range(1, 10001) AS r(i)")
.unwrap();
db.execute(
"SET graph_links = 'orders(o_custkey) -> customer(c_custkey), \
scattered(x_custkey) -> customer(c_custkey)'",
)
.unwrap();
db.execute("CHECKPOINT").unwrap();
let locality = rows(
&db,
"SELECT child_table FROM rudb_links() WHERE gather_locality < 1 ORDER BY child_table",
);
assert_eq!(
locality,
vec![vec![Value::Varchar("orders".into())]],
"the clustered one gathers inside a cache line and the scattered one does not"
);
let far = rows(&db, "SELECT child_table FROM rudb_links() WHERE gather_locality > 1000");
assert_eq!(far, vec![vec![Value::Varchar("scattered".into())]]);
drop(db);
std::fs::remove_file(&path).ok();
}
#[test]
fn a_join_over_a_built_relationship_is_planned_as_a_link_join_and_answers_the_same() {
let path = std::env::temp_dir().join(format!(
"rudb-graph-linkjoin-{}-{}.rdb",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("the clock advances")
.as_nanos()
));
let db = Database::open(path.to_str().expect("a UTF-8 temporary path")).unwrap();
db.execute("CREATE TABLE customer (c_custkey INTEGER, c_name VARCHAR)").unwrap();
db.execute("CREATE TABLE orders (o_orderkey INTEGER, o_custkey INTEGER)").unwrap();
db.execute("INSERT INTO customer SELECT i, 'c' || i FROM range(1, 4001) AS r(i)").unwrap();
db.execute("INSERT INTO orders SELECT i, 1 + i % 4000 FROM range(1, 10001) AS r(i)").unwrap();
db.execute("SET graph_links = 'orders(o_custkey) -> customer(c_custkey)'").unwrap();
db.execute("CHECKPOINT").unwrap();
db.execute("SET graph_sections = 'on'").unwrap();
db.execute("SET stats_join_elimination = false").unwrap();
let sql = "SELECT count(*), sum(o_orderkey) FROM orders JOIN customer ON o_custkey = c_custkey";
let explained = |db: &Database, sql: &str| match db
.query(&format!("EXPLAIN {sql}"))
.expect("the explain ran")
.value_at(0, 1)
{
Value::Varchar(text) => text,
other => panic!("the plan came back as {other:?}"),
};
let plan = explained(&db, sql);
assert!(!plan.contains("LinkJoin"), "four thousand customers were worth a link");
assert!(
plan.contains(
"[builds a hash table, because the parent is 4000 rows and 16000 bytes \
projected, which fits in cache]"
),
"the plan does not say why it built a hash table:\n{plan}"
);
db.execute("SET graph_cache_bytes = 1").unwrap();
assert_eq!(db.setting("graph_cache_bytes").unwrap(), "1 bytes");
let plan = explained(&db, sql);
assert!(plan.contains("LinkJoin"), "the join was not planned as a link join:\n{plan}");
assert!(
plan.contains("[reads the link, because the parent does not fit in cache"),
"the plan does not say why it read the link:\n{plan}"
);
let linked = rows(&db, sql);
db.execute("SET disabled_optimizers = 'link_join'").unwrap();
assert!(!explained(&db, sql).contains("LinkJoin"), "the pass is still on");
assert_eq!(linked, rows(&db, sql), "the link join answered a different question");
db.execute("RESET disabled_optimizers").unwrap();
db.execute("SET graph_sections = 'off'").unwrap();
assert!(!explained(&db, sql).contains("LinkJoin"), "the sections are still being read");
assert_eq!(linked, rows(&db, sql), "the layer changed an answer rather than a time");
db.execute("SET graph_sections = 'on'").unwrap();
assert!(explained(&db, sql).contains("LinkJoin"), "turning the sections back on did nothing");
db.execute("RESET graph_cache_bytes").unwrap();
assert_eq!(db.setting("graph_cache_bytes").unwrap(), "8.0 MiB", "reset is the default");
drop(db);
std::fs::remove_file(&path).ok();
}
#[test]
fn a_link_join_gathers_a_parent_column_that_was_stored_in_a_form_that_is_not_flat() {
let path = std::env::temp_dir().join(format!(
"rudb-graph-gather-{}-{}.rdb",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("the clock advances")
.as_nanos()
));
let db = Database::open(path.to_str().expect("a UTF-8 temporary path")).unwrap();
db.execute("CREATE TABLE customer (c_custkey INTEGER, c_grade VARCHAR, c_balance INTEGER)")
.unwrap();
db.execute("CREATE TABLE orders (o_orderkey INTEGER, o_custkey INTEGER)").unwrap();
db.execute(
"INSERT INTO customer SELECT i, ['bronze', 'silver', 'gold', 'platinum'][1 + i % 4], \
100 + i % 50 FROM range(1, 4001) AS r(i)",
)
.unwrap();
db.execute("INSERT INTO orders SELECT i, 1 + i % 4000 FROM range(1, 10001) AS r(i)").unwrap();
db.execute("SET graph_links = 'orders(o_custkey) -> customer(c_custkey)'").unwrap();
db.execute("CHECKPOINT").unwrap();
db.execute("SET graph_sections = 'on'").unwrap();
drop(db);
let db = Database::open(path.to_str().expect("a UTF-8 temporary path")).unwrap();
db.execute("SET graph_links = 'orders(o_custkey) -> customer(c_custkey)'").unwrap();
db.execute("SET graph_sections = 'on'").unwrap();
let sql = "SELECT c_grade, count(*), sum(c_balance) FROM orders JOIN customer \
ON o_custkey = c_custkey GROUP BY c_grade ORDER BY c_grade";
let plan = |db: &Database| match db
.query(&format!("EXPLAIN {sql}"))
.expect("the explain ran")
.value_at(0, 1)
{
Value::Varchar(text) => text,
other => panic!("the plan came back as {other:?}"),
};
db.execute("SET graph_sections = 'off'").unwrap();
let hashed = rows(&db, sql);
assert!(!hashed.is_empty(), "the control answered nothing, so it is not a control");
db.execute("SET graph_sections = 'on'").unwrap();
db.execute("SET graph_cache_bytes = 1").unwrap();
assert!(
plan(&db).contains("LinkJoin"),
"the join was not planned as a link join:\n{}",
plan(&db)
);
assert_eq!(rows(&db, sql), hashed, "gathering a parent column changed the answer");
drop(db);
std::fs::remove_file(&path).ok();
}
#[test]
fn a_checkpoint_builds_a_key_map_over_the_parent_of_every_declared_relationship() {
let path = std::env::temp_dir().join(format!(
"rudb-graph-checkpoint-{}-{}.rdb",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("the clock advances")
.as_nanos()
));
let declaration = "SET graph_links = 'orders(o_custkey) -> customer(c_custkey)'";
let db = Database::open(path.to_str().expect("a UTF-8 temporary path")).unwrap();
db.execute("CREATE TABLE customer (c_custkey INTEGER, c_name VARCHAR)").unwrap();
db.execute("CREATE TABLE orders (o_orderkey INTEGER, o_custkey INTEGER)").unwrap();
db.execute("INSERT INTO customer SELECT i, 'c' || i FROM range(1, 4001) AS r(i)").unwrap();
db.execute("INSERT INTO orders SELECT i, 1 + i % 4000 FROM range(1, 10001) AS r(i)").unwrap();
db.execute(declaration).unwrap();
db.execute("CHECKPOINT").unwrap();
let built = rows(
&db,
"SELECT cardinality, key_map, key_map_bytes > 0, link, link_bytes > 0, note FROM \
rudb_links()",
);
assert_eq!(
built,
vec![vec![
Value::Varchar("exactly one".into()),
Value::Varchar("identity".into()),
Value::Boolean(true),
Value::Varchar("packed".into()),
Value::Boolean(true),
Value::Null,
]],
"a distinct ascending key column maps by subtraction, the second pass stored a forward \
link beside it, and every child row found a parent through it"
);
assert_eq!(
rows(&db, "SELECT count(*) FROM orders o JOIN customer c ON o.o_custkey = c.c_custkey"),
vec![vec![Value::BigInt(10000)]]
);
drop(db);
let reopened = Database::open(path.to_str().expect("a UTF-8 temporary path")).unwrap();
assert_eq!(
rows(&reopened, "SELECT count(*), max(c_custkey) FROM customer"),
vec![vec![Value::BigInt(4000), Value::Integer(4000)]]
);
assert!(
rows(&reopened, "SELECT name FROM rudb_links()").is_empty(),
"a declaration is a session's and not the file's"
);
reopened.execute(declaration).unwrap();
assert_eq!(
rows(&reopened, "SELECT key_map FROM rudb_links()"),
vec![vec![Value::Varchar("identity".into())]],
"and the map that was built is read back without being built again"
);
drop(reopened);
std::fs::remove_file(path).expect("the temporary native database is removed");
}
#[test]
fn rudb_links_says_what_a_structure_it_decided_against_would_have_cost() {
let path = std::env::temp_dir().join(format!(
"rudb-graph-refused-{}-{}.rdb",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("the clock advances")
.as_nanos()
));
let db = Database::open(path.to_str().expect("a UTF-8 temporary path")).unwrap();
db.execute("CREATE TABLE customer (c_custkey INTEGER)").unwrap();
db.execute("CREATE TABLE orders (o_custkey INTEGER)").unwrap();
db.execute("INSERT INTO customer SELECT 1 + i % 4000 FROM range(0, 8000) AS r(i)").unwrap();
db.execute("INSERT INTO orders SELECT 1 + i % 4000 FROM range(0, 10000) AS r(i)").unwrap();
db.execute("SET graph_links = 'orders(o_custkey) -> customer(c_custkey)'").unwrap();
db.execute("CHECKPOINT").unwrap();
let listed = rows(
&db,
"SELECT cardinality, key_map, key_map_bytes > 0, link, link_bytes, note FROM rudb_links()",
);
assert_eq!(
listed,
vec![vec![
Value::Varchar("unverified".into()),
Value::Null,
Value::Boolean(true),
Value::Null,
Value::Null,
Value::Varchar(
"the key map was measured and not kept, so key_map_bytes is what it would cost"
.into()
),
]],
"a structure that is not there has no form and still has a size"
);
let Value::BigInt(bytes) = rows(&db, "SELECT key_map_bytes FROM rudb_links()")[0][0] else {
panic!("a size");
};
assert!(bytes > 8000, "eight thousand keys do not encode in {bytes} bytes");
assert_eq!(
rows(&db, "SELECT count(*) FROM orders o JOIN customer c ON o.o_custkey = c.c_custkey"),
vec![vec![Value::BigInt(20000)]]
);
drop(db);
std::fs::remove_file(&path).ok();
}
#[test]
fn a_seam_is_set_and_read_back_through_the_statement_everything_else_goes_through() {
let db = Database::new();
assert_eq!(db.setting("seam.hash.table").unwrap(), "default");
db.execute("SET \"seam.hash.table\" = 'unchained'").unwrap();
assert_eq!(db.setting("seam.hash.table").unwrap(), "unchained");
assert_eq!(db.seams().pinned(crate::seam::SeamId::HashTable), Some("unchained"));
db.execute("SET seam_hash_table = 'linear-chained'").unwrap();
assert_eq!(db.setting("hash.table").unwrap(), "linear-chained");
db.execute("RESET seam_hash_table").unwrap();
assert_eq!(db.setting("seam.hash.table").unwrap(), "default");
assert_eq!(db.seams().pinned(crate::seam::SeamId::HashTable), None);
}
#[test]
fn the_policy_is_a_seam_like_the_rest_and_a_mistyped_one_names_the_seams() {
let db = Database::new();
db.execute("SET seam_policy = 'reference'").unwrap();
assert_eq!(db.seams().mode(), crate::seam::PolicyMode::Reference);
assert_eq!(db.setting("seam.policy").unwrap(), "reference");
let error = db.execute("SET \"seam.hash.tabel\" = 'unchained'").unwrap_err();
assert_eq!(error.code().duckdb_name(), "Catalog Error");
assert!(error.message().contains("rudb_strategies()"), "{error}");
let error = db.execute("SET seam_policy = 'clever'").unwrap_err();
assert!(error.message().contains("reference, default or adaptive-bandit"), "{error}");
}
#[test]
fn a_hint_pins_a_seam_for_one_query_and_leaves_the_session_alone() {
let db = Database::new();
let sql = "SELECT /*+ hash.table(unchained) */ 42";
assert_eq!(db.value(sql).unwrap(), Value::Integer(42));
let seams = db.seams_for(sql).unwrap();
assert_eq!(seams.pinned(crate::seam::SeamId::HashTable), Some("unchained"));
assert_eq!(db.seams().pinned(crate::seam::SeamId::HashTable), None, "the session is untouched");
}
#[test]
fn a_hint_naming_a_seam_nobody_has_fails_the_query_rather_than_being_ignored() {
let db = Database::new();
let error = db.query("SELECT /*+ hash.tabel(unchained) */ 42").unwrap_err();
assert_eq!(error.code().duckdb_name(), "Catalog Error");
assert!(error.message().contains("no seam called hash.tabel"), "{error}");
assert_eq!(db.value("SELECT /* hash.tabel(unchained) */ 42").unwrap(), Value::Integer(42));
}
#[test]
fn a_name_that_is_not_a_setting_says_so_and_offers_the_nearest_ones() {
let db = Database::new();
let error = db.execute("SET bogus = 1").unwrap_err();
assert_eq!(error.code().duckdb_name(), "Catalog Error");
assert_eq!(error.message(), "unrecognized configuration parameter \"bogus\"");
let error = db.execute("SET thread = 1").unwrap_err();
assert!(error.message().contains("\"threads\""), "{error}");
assert!(!error.message().contains("\"memory_limit\""), "{error}");
}
#[test]
fn the_two_scopes_this_database_does_not_have_are_two_different_sentences() {
let db = Database::new();
let error = db.execute("SET LOCAL threads = 2").unwrap_err();
assert_eq!(error.to_string(), "Not implemented Error: SET LOCAL is not implemented.");
let error = db.execute("SET SESSION threads = 2").unwrap_err();
assert_eq!(error.to_string(), "Catalog Error: option \"threads\" cannot be set locally");
db.execute("SET GLOBAL threads = 2").expect("global is the scope every setting here has");
assert_eq!(db.config().threads(), 2);
}
#[test]
fn the_settings_a_database_was_opened_with_are_what_reset_goes_back_to() {
let db = Database::with_config(Config::new().with_memory_limit(SMALL).with_threads(2).unwrap());
db.execute("SET memory_limit = '4GB'").unwrap();
db.execute("SET threads = 16").unwrap();
assert_eq!(db.opened_with().memory_limit(), Some(SMALL));
db.execute("RESET memory_limit").unwrap();
db.execute("RESET threads").unwrap();
assert_eq!(db.config(), db.opened_with());
assert_eq!(db.memory().limit(), Some(SMALL));
}
#[test]
fn rudb_strategies_lists_every_seam_with_the_milestone_that_owes_it() {
let db = database();
let listed = rows(&db, "SELECT DISTINCT seam, milestone FROM rudb_strategies() ORDER BY seam");
assert_eq!(listed.len(), 27, "the seam list is closed and this is its length");
for row in &listed {
let Value::Varchar(milestone) = &row[1] else { panic!("a milestone per seam") };
assert!(milestone.starts_with('F'), "{milestone} is not a milestone");
}
}
#[test]
fn a_seam_nobody_has_implemented_reads_back_as_planned_rather_than_missing() {
let db = database();
let listed = rows(
&db,
"SELECT count(*) FROM rudb_strategies() WHERE implementation IS NULL AND seam_description \
IS NOT NULL",
);
assert_eq!(
listed,
vec![vec![Value::BigInt(26)]],
"one seam has implementations and the rest say what milestone owes them"
);
}
#[test]
fn the_compaction_seam_lists_its_three_implementations() {
let db = database();
let listed = rows(
&db,
"SELECT implementation, is_reference FROM rudb_strategies() WHERE seam = \
'chunk.compaction'",
);
let names: Vec<&Value> = listed.iter().map(|row| &row[0]).collect();
assert_eq!(
names,
vec![
&Value::Varchar("never".to_owned()),
&Value::Varchar("fixed-threshold".to_owned()),
&Value::Varchar("learned-gain".to_owned()),
]
);
assert_eq!(listed[0][1], Value::Boolean(true), "the one that copies nothing is the reference");
}
#[test]
fn rudb_strategies_takes_no_arguments() {
let db = database();
assert!(
failure(&db, "SELECT * FROM rudb_strategies(1)").contains("\"rudb_strategies\"()"),
"a call with an argument is a binder error rather than an ignored argument"
);
}
fn operator<'a>(metrics: &'a rudb_metrics::Document, kind: &str) -> &'a rudb_metrics::Operator {
metrics
.operators
.iter()
.find(|operator| operator.kind == kind)
.unwrap_or_else(|| panic!("a {kind} in {:?}", metrics.operators))
}
#[test]
fn a_query_reports_what_every_operator_in_it_did() {
let db = database();
let sql = "SELECT x FROM t WHERE x > 1";
let result = db.query(sql).unwrap();
let metrics = result.metrics().expect("a query that ran has metrics");
assert_eq!(metrics.query.sql, sql);
let scan = operator(metrics, "Scan");
let project = operator(metrics, "Project");
assert_eq!(scan.detail.as_deref(), Some("t"), "a scan says what it read");
assert_eq!(scan.rows_out, 2, "two of the four rows are over one and the scan is the filter");
assert_eq!(project.rows_in, 2, "what the scan produced is what the one above it was handed");
assert_eq!(project.rows_out, 2, "a projection keeps every row it is given");
assert_eq!(project.pipeline, scan.pipeline, "nothing here breaks a pipeline");
assert!(metrics.timing.execute_ns > 0, "running it took longer than nothing");
assert!(
metrics.operators.iter().all(|operator| operator.reference_impl),
"everything at tier 0 is the reference implementation and the document says so"
);
assert_eq!(scan.implementations.len(), 1, "the filter it took in sits on one registered seam");
assert_eq!(scan.implementations[0].seam, "chunk.compaction");
assert_eq!(scan.implementations[0].name, "never");
}
#[test]
fn an_operator_that_was_pinned_off_the_reference_stops_being_marked_as_one() {
let db = database();
db.execute("SET seam_chunk_compaction = 'learned-gain'").unwrap();
let result = db.query("SELECT x FROM t WHERE x > 1").unwrap();
let metrics = result.metrics().expect("a query that ran has metrics");
let scan = operator(metrics, "Scan");
assert!(!scan.reference_impl, "{:?}", scan.implementations);
assert_eq!(scan.implementations[0].seam, "chunk.compaction");
assert_eq!(scan.implementations[0].name, "learned-gain");
assert!(
operator(metrics, "Project").reference_impl,
"a projection sits on no registered seam, and pinning one under the scan is not about it"
);
}
#[test]
fn every_operator_has_its_own_id_and_a_parent_is_numbered_before_its_children() {
let db = database();
let result = db.query("SELECT count(*) FROM t GROUP BY x").unwrap();
let metrics = result.metrics().expect("a query that ran has metrics");
let ids: Vec<u32> = metrics.operators.iter().map(|operator| operator.id).collect();
assert_eq!(ids, (0..u32::try_from(ids.len()).unwrap()).collect::<Vec<_>>());
let scan = operator(metrics, "Scan");
let aggregate = operator(metrics, "Aggregate");
assert!(aggregate.id < scan.id, "the aggregate is above the scan, so it is numbered first");
}
#[test]
fn a_filter_the_scan_applied_has_no_operator_and_leaves_its_number_unused() {
let db = database();
let result = db.query("SELECT count(*) FROM t WHERE x > 1").unwrap();
let metrics = result.metrics().expect("a query that ran has metrics");
assert!(
!metrics.operators.iter().any(|operator| operator.kind == "Filter"),
"{:?}",
metrics.operators
);
let ids: Vec<u32> = metrics.operators.iter().map(|operator| operator.id).collect();
assert!(ids.windows(2).all(|pair| pair[0] < pair[1]), "the ids still rise: {ids:?}");
assert_eq!(operator(metrics, "Scan").rows_out, 2, "the scan produced the rows that passed");
}
#[test]
fn a_sort_is_a_pipeline_that_the_one_above_it_waits_for() {
let db = database();
let result = db.query("SELECT x FROM t ORDER BY x").unwrap();
let metrics = result.metrics().expect("a query that ran has metrics");
let sort = operator(metrics, "Sort");
assert_eq!(metrics.pipelines.len(), 2, "a sort breaks the pipeline in two");
assert_eq!(metrics.pipelines[0].id, 0);
assert_eq!(metrics.pipelines[0].depends_on, vec![1], "the root waits for the sort");
assert_eq!(sort.pipeline, 1, "the sort ends the pipeline below");
assert_eq!(sort.rows_in, 4, "every row went into it");
assert!(metrics.pipelines[1].wall_ns > 0, "a pipeline's time is its operators' time");
}
#[test]
fn a_join_is_three_pipelines_in_the_order_they_have_to_run() {
let db = database();
let result = db.query("SELECT t.x FROM t JOIN t AS u ON t.x = u.x").unwrap();
let metrics = result.metrics().expect("a query that ran has metrics");
assert_eq!(metrics.pipelines.len(), 3, "one to gather the build side, one to probe, one above");
let gather = operator(metrics, "Gather");
let probe = operator(metrics, "Probe");
assert_eq!(
metrics.pipelines[0].depends_on,
vec![probe.pipeline],
"the root waits for the probe"
);
assert_eq!(
metrics.pipelines[usize::try_from(probe.pipeline).unwrap()].depends_on,
vec![gather.pipeline],
"the probe waits for the side that is gathered first"
);
assert_eq!(gather.rows_in, 4, "the whole right side was gathered");
let driving = metrics
.operators
.iter()
.filter(|operator| operator.kind == "Scan")
.find(|scan| scan.pipeline == probe.pipeline)
.expect("the driving scan is in the probe's own pipeline");
assert_eq!(driving.rows_out, 4, "every driving row went straight into the probe");
}
#[test]
fn a_join_no_lookup_answers_says_so_in_the_profile() {
let db = database();
let result = db.query("SELECT t.x FROM t JOIN t AS u ON t.x < u.x").unwrap();
let metrics = result.metrics().expect("a query that ran has metrics");
operator(metrics, "Join");
assert!(
!metrics.operators.iter().any(|operator| operator.kind == "Probe"),
"a range condition is not a lookup"
);
}
#[test]
fn every_operator_but_the_one_that_answers_names_what_consumed_its_rows() {
let db = database();
let result = db.query("SELECT sum(x) FROM (SELECT x FROM t WHERE x > 1) ORDER BY 1").unwrap();
let metrics = result.metrics().expect("a query that ran has metrics");
let roots: Vec<u32> = metrics
.operators
.iter()
.filter(|operator| operator.parent.is_none())
.map(|operator| operator.id)
.collect();
assert_eq!(roots, vec![0], "one operator produces the answer and it is the first one");
for operator in &metrics.operators {
let Some(parent) = operator.parent else { continue };
let parent = metrics
.operators
.iter()
.find(|other| other.id == parent)
.expect("a parent is an operator in the same document");
assert!(parent.id < operator.id, "a parent is numbered before everything under it");
}
}
#[test]
fn no_operator_hangs_under_a_row_that_is_not_there() {
let db = database();
for sql in [
"SELECT x FROM t WHERE x > 1",
"SELECT sum(x) FROM t WHERE x > 1 GROUP BY s ORDER BY 1",
"SELECT t.x FROM t JOIN t AS u ON t.x = u.x WHERE t.x > 1",
"SELECT t.x FROM t LEFT JOIN t AS u ON t.x = u.x",
"SELECT x FROM t WHERE x IN (SELECT x FROM t WHERE x > 1)",
"SELECT x FROM t UNION SELECT x FROM t",
"WITH c AS MATERIALIZED (SELECT x FROM t WHERE x > 1) SELECT x FROM c ORDER BY 1",
"SELECT x, count(*) OVER (PARTITION BY s) FROM t",
"SELECT x FROM t ORDER BY 1 LIMIT 2",
"SELECT a.x FROM t AS a, t AS b WHERE a.x > b.x",
] {
let result = db.query(sql).unwrap_or_else(|error| panic!("{sql} did not run: {error}"));
let metrics = result.metrics().expect("a query that ran has metrics");
let held = metrics.operators.iter().filter(|one| one.kind.contains("CTE")).count();
let roots = metrics.operators.iter().filter(|one| one.parent.is_none()).count();
assert_eq!(roots, 1 + held, "{sql} has one operator the answer is read from");
for one in &metrics.operators {
let Some(parent) = one.parent else { continue };
assert!(
metrics.operators.iter().any(|other| other.id == parent),
"{sql}: operator {} hangs under {parent}, which has no row",
one.id
);
}
}
}
#[test]
fn what_an_operator_was_handed_is_what_the_operators_under_it_produced() {
let db = database();
for sql in [
"SELECT x FROM t WHERE x > 1",
"SELECT sum(x) FROM t WHERE x > 1 GROUP BY s ORDER BY 1",
"SELECT t.x FROM t JOIN t AS u ON t.x = u.x WHERE t.x > 1",
"SELECT t.x FROM t LEFT JOIN t AS u ON t.x = u.x",
"SELECT t.x FROM t JOIN t AS u ON t.x > u.x AND t.x < u.x + 5",
"SELECT x FROM t WHERE x IN (SELECT x FROM t WHERE x > 1)",
"SELECT x FROM t UNION SELECT x FROM t",
"WITH c AS MATERIALIZED (SELECT x FROM t WHERE x > 1) SELECT x FROM c ORDER BY 1",
"SELECT x, count(*) OVER (PARTITION BY s) FROM t",
"SELECT a.x FROM t AS a, t AS b WHERE a.x > b.x",
] {
let result = db.query(sql).unwrap_or_else(|error| panic!("{sql} did not run: {error}"));
let metrics = result.metrics().expect("a query that ran has metrics");
for one in &metrics.operators {
let below: u64 = metrics
.operators
.iter()
.filter(|other| other.parent == Some(one.id))
.map(|other| other.rows_out)
.sum();
if !metrics.operators.iter().any(|other| other.parent == Some(one.id)) {
continue;
}
assert_eq!(
one.rows_in, below,
"{sql}: operator {} ({}) was handed {} rows and the operators under it made {below}",
one.id, one.kind, one.rows_in
);
}
}
}
#[test]
fn the_side_a_join_gathers_hangs_under_the_operator_that_holds_it() {
let db = database();
let result = db.query("SELECT t.x FROM t JOIN t AS u ON t.x = u.x").unwrap();
let metrics = result.metrics().expect("a query that ran has metrics");
let gather = operator(metrics, "Gather");
let probe = operator(metrics, "Probe");
assert_eq!(gather.parent, Some(probe.id), "the held side is handed to the join");
let held = metrics
.operators
.iter()
.find(|other| other.parent == Some(gather.id))
.expect("something fills the gather");
assert_eq!(held.rows_out, gather.rows_in, "and what it produced is what the gather took");
}
#[test]
fn a_join_says_what_it_built_and_what_it_chose() {
let db = database();
let result = db.query("SELECT t.x FROM t JOIN t AS u ON t.x = u.x").unwrap();
let metrics = result.metrics().expect("a query that ran has metrics");
let joined = operator(metrics, "Probe").joined.clone().expect("a join reports what it did");
assert_eq!(joined.algorithm, rudb_metrics::Algorithm::Hash);
assert_eq!(joined.build_rows, 4, "the whole gathered side went into the table");
assert!(joined.build_bytes > 0, "a table that holds four rows cost something to hold them");
let declined = &joined.declined;
assert_eq!(declined.len(), 1, "the nested loop was the only other answer");
assert_eq!(declined[0].algorithm, rudb_metrics::Algorithm::Loop);
assert!(declined[0].reason.contains("equality"), "{}", declined[0].reason);
}
#[test]
fn a_join_with_no_lookup_says_why_it_had_to_walk_every_pair() {
let db = database();
let result = db.query("SELECT t.x FROM t JOIN t AS u ON t.x < u.x").unwrap();
let metrics = result.metrics().expect("a query that ran has metrics");
let joined = operator(metrics, "Join").joined.clone().expect("a join reports what it did");
assert_eq!(joined.algorithm, rudb_metrics::Algorithm::Loop);
assert_eq!(joined.build_rows, 4, "the gathered side is walked once per driving row");
assert_eq!(joined.declined.len(), 1, "the table was the only other answer");
assert_eq!(joined.declined[0].algorithm, rudb_metrics::Algorithm::Hash);
assert!(joined.declined[0].reason.contains("no conjunct"), "{}", joined.declined[0].reason);
}
#[test]
fn an_operator_that_is_not_a_join_has_no_join_record() {
let db = database();
let result = db.query("SELECT sum(x) FROM t WHERE x > 1").unwrap();
let metrics = result.metrics().expect("a query that ran has metrics");
assert!(
metrics.operators.iter().all(|operator| operator.joined.is_none()),
"no operator in this query has two inputs"
);
}
#[test]
fn a_statement_that_runs_no_plan_has_nothing_to_report() {
let db = database();
assert!(db.query("EXPLAIN SELECT 1").unwrap().metrics().is_none(), "explain runs nothing");
assert!(db.execute("SET threads = 2").unwrap().metrics().is_none(), "a setting runs nothing");
}
#[test]
fn the_document_a_query_produces_is_the_json_a_harness_reads() {
let db = database();
let sql = "SELECT count(*) FROM t WHERE x > 1";
let result = db.query(sql).unwrap();
let written = result.metrics().expect("a query that ran has metrics").render();
assert!(written.starts_with("{\n \"schema\": 1,"), "{written}");
assert!(written.contains(&format!("\"sql\": \"{sql}\"")), "{written}");
assert!(written.contains("\"kind\": \"Scan\""), "{written}");
assert!(written.contains("\"reference_impl\": true"), "{written}");
}
#[test]
fn related_integer_sums_keep_null_and_empty_rules() {
let db = database();
let query = "SELECT sum(x), sum(x + 1) FROM \
(VALUES (1::SMALLINT), (NULL::SMALLINT), (3::SMALLINT)) AS v(x)";
assert_eq!(rows(&db, query), vec![vec![Value::HugeInt(4), Value::HugeInt(6)]]);
let empty = format!("{query} WHERE false");
assert_eq!(rows(&db, &empty), vec![vec![Value::Null, Value::Null]]);
}
fn threaded(threads: usize) -> Database {
Database::with_config(Config::new().with_threads(threads).unwrap())
}
#[test]
fn a_query_on_eight_threads_answers_what_it_answers_on_one() {
let sql = "SELECT count(*), sum(range), min(range), max(range) \
FROM range(1000000) WHERE range % 7 = 0";
assert_eq!(rows(&threaded(8), sql), rows(&threaded(1), sql));
}
#[test]
fn a_group_by_on_eight_threads_finds_every_group_exactly_once() {
let sql = "SELECT range % 1000 AS k, count(*), sum(range) FROM range(1000000) GROUP BY k";
let mut many = rows(&threaded(8), sql);
let mut one = rows(&threaded(1), sql);
many.sort_by_key(|row| format!("{:?}", row[0]));
one.sort_by_key(|row| format!("{:?}", row[0]));
assert_eq!(many.len(), 1000);
assert_eq!(many, one);
}
#[test]
fn a_count_distinct_on_eight_threads_counts_each_value_once() {
let sql = "SELECT count(DISTINCT range % 977) FROM range(200000)";
assert_eq!(rows(&threaded(8), sql), [vec![Value::BigInt(977)]]);
assert_eq!(rows(&threaded(8), sql), rows(&threaded(1), sql));
}
#[test]
fn every_distinct_aggregate_on_eight_threads_answers_what_it_answers_on_one() {
let sql = "SELECT sum(DISTINCT range % 1009), min(DISTINCT range % 1009), \
max(DISTINCT range % 1009), count(DISTINCT range % 1009) FROM range(300000)";
let total: i64 = (0..1009i64).sum();
assert_eq!(
rows(&threaded(8), sql),
[vec![
Value::HugeInt(i128::from(total)),
Value::BigInt(0),
Value::BigInt(1008),
Value::BigInt(1009)
]]
);
assert_eq!(rows(&threaded(8), sql), rows(&threaded(1), sql));
}
#[test]
fn a_grouped_count_distinct_on_eight_threads_finds_every_group_and_every_value() {
let sql = "SELECT range % 13 AS k, count(DISTINCT range % 91), count(*) \
FROM range(400000) GROUP BY k";
let mut many = rows(&threaded(8), sql);
let mut one = rows(&threaded(1), sql);
many.sort_by_key(|row| format!("{:?}", row[0]));
one.sort_by_key(|row| format!("{:?}", row[0]));
assert_eq!(many.len(), 13);
for row in &many {
assert_eq!(row[1], Value::BigInt(7), "91 over 13 is seven values in each group");
}
assert_eq!(many, one);
}
#[test]
fn a_count_distinct_over_strings_on_eight_threads_counts_each_string_once() {
let sql = "SELECT count(DISTINCT 'tag' || (range % 641)) FROM range(200000)";
assert_eq!(rows(&threaded(8), sql), [vec![Value::BigInt(641)]]);
assert_eq!(rows(&threaded(8), sql), rows(&threaded(1), sql));
}
#[test]
fn a_high_cardinality_group_by_on_eight_threads_merges_to_the_same_answer() {
let sql = "SELECT range % 50000 AS g, count(*), sum(range) FROM range(400000) GROUP BY g";
let many = rows(&threaded(8), sql);
assert_eq!(many.len(), 50_000);
let mut sorted = many.clone();
sorted.sort_by_key(|row| match row[0] {
Value::BigInt(key) => key,
_ => unreachable!("the key is a BIGINT"),
});
assert_eq!(sorted[0], vec![Value::BigInt(0), Value::BigInt(8), Value::HugeInt(1_400_000)]);
let mut one = rows(&threaded(1), sql);
one.sort_by_key(|row| match row[0] {
Value::BigInt(key) => key,
_ => unreachable!("the key is a BIGINT"),
});
assert_eq!(sorted, one);
}
#[test]
fn a_grouped_count_distinct_over_many_groups_on_eight_threads_agrees_with_one_thread() {
let sql = "SELECT range % 30000 AS k, count(DISTINCT range % 90000), count(*) \
FROM range(900000) GROUP BY k";
let many = rows(&threaded(8), sql);
assert_eq!(many.len(), 30_000);
for row in &many {
assert_eq!(row[1], Value::BigInt(3), "90000 over 30000 is three values in each group");
assert_eq!(row[2], Value::BigInt(30), "900000 over 30000 is thirty rows in each group");
}
let mut sorted = many;
let mut one = rows(&threaded(1), sql);
sorted.sort_by_key(|row| format!("{:?}", row[0]));
one.sort_by_key(|row| format!("{:?}", row[0]));
assert_eq!(sorted, one);
}
#[test]
fn a_group_by_that_spills_on_eight_threads_answers_what_it_answers_on_one() {
let sql = "SELECT range % 900000 AS k, count(*), sum(range) FROM range(1800000) GROUP BY k";
let db = Database::with_config(
Config::new().with_memory_limit(300 << 20).with_threads(8).expect("eight threads"),
);
let answer = db.query(sql).expect("it spills rather than stopping");
let mut many: Vec<Vec<Value>> = answer.rows().collect();
assert_eq!(many.len(), 900_000);
let mut one = rows(&threaded(1), sql);
many.sort_by_key(|row| first_key(row));
one.sort_by_key(|row| first_key(row));
assert_eq!(many, one, "every row landed once whether it went through a spill file or not");
}
#[test]
fn a_group_by_a_string_with_many_groups_on_eight_threads_agrees_with_one_thread() {
let sql = "SELECT 'k' || (range % 25000) AS k, count(*), min(range), max(range) \
FROM range(500000) GROUP BY k";
let many = rows(&threaded(8), sql);
assert_eq!(many.len(), 25_000);
let mut sorted = many;
let mut one = rows(&threaded(1), sql);
sorted.sort_by_key(|row| format!("{:?}", row[0]));
one.sort_by_key(|row| format!("{:?}", row[0]));
assert_eq!(sorted, one);
}
#[test]
fn a_query_with_no_order_by_keeps_the_source_order_on_eight_threads() {
let rows = rows(&threaded(8), "SELECT range FROM range(200000) WHERE range % 3 = 0");
let read: Vec<Value> = rows.into_iter().map(|row| row[0].clone()).collect();
let expected: Vec<Value> = (0..200_000i64).step_by(3).map(Value::BigInt).collect();
assert_eq!(read, expected, "the scan was cut into morsels and put back together in order");
}
#[test]
fn an_order_by_on_eight_threads_is_still_in_order() {
let rows = rows(
&threaded(8),
"SELECT range FROM range(100000) WHERE range % 1000 = 0 ORDER BY range DESC LIMIT 5",
);
assert_eq!(
rows,
vec![
vec![Value::BigInt(99000)],
vec![Value::BigInt(98000)],
vec![Value::BigInt(97000)],
vec![Value::BigInt(96000)],
vec![Value::BigInt(95000)],
]
);
}
#[test]
fn a_query_on_several_threads_says_so_in_its_metrics() {
let db = threaded(8);
let result =
db.query("SELECT count(*), sum(range) FROM range(2000000) WHERE range % 7 = 0").unwrap();
let metrics = result.metrics().unwrap();
assert_eq!(metrics.settings.threads, 8, "the document says what the query was allowed");
let widest = metrics.pipelines.iter().map(|pipeline| pipeline.instances).max().unwrap();
assert_eq!(widest, 8, "and the scan says it used all of them");
if rudb_metrics::thread_cpu_ns().is_some() {
assert!(metrics.resource.cpu_ns > 0);
}
}
#[test]
fn setting_threads_changes_what_the_next_query_may_use() {
let db = threaded(8);
db.execute("SET threads = 1").unwrap();
assert_eq!(db.config().threads(), 1);
let metrics = db.query("SELECT count(*) FROM range(1000000)").unwrap();
let metrics = metrics.metrics().unwrap();
assert_eq!(metrics.settings.threads, 1);
let widest = metrics.pipelines.iter().map(|pipeline| pipeline.instances).max().unwrap();
assert_eq!(widest, 1, "a setting nothing obeys is not a setting");
}
#[test]
fn a_group_by_that_spills_before_it_partitions_lands_the_file_and_the_table() {
let sql = "SELECT range % 10000 AS k, count(DISTINCT range), count(*), sum(range) \
FROM range(500000) GROUP BY k";
let db = Database::with_config(
Config::new().with_memory_limit(24 << 20).with_threads(8).expect("eight threads"),
);
let answer = db.query(sql).expect("it spills rather than stopping");
let mut many: Vec<Vec<Value>> = answer.rows().collect();
assert_eq!(many.len(), 10_000);
let mut one = rows(&threaded(1), sql);
many.sort_by_key(|row| first_key(row));
one.sort_by_key(|row| first_key(row));
assert_eq!(many, one, "every row landed once whether it came back from a file or not");
}
#[test]
fn a_table_built_from_a_query_keeps_the_dictionary_the_query_produced() {
let db = Database::new();
db.execute("CREATE TABLE src AS SELECT range % 4 AS k FROM range(4096)").expect("src builds");
db.execute("CREATE TABLE kept AS SELECT k FROM src WHERE k < 3").expect("kept builds");
let forms = db.with_catalog(|catalog| {
let name = rudb_catalog::QualifiedName::new("memory", "main", "kept");
let table = catalog.table(&name).expect("the table is there");
(0..table.rows().chunk_count())
.filter_map(|at| table.rows().chunk(at))
.map(|chunk| chunk.column(0).expect("one column").form())
.collect::<Vec<_>>()
});
assert!(!forms.is_empty(), "the table has chunks");
assert!(
forms.iter().any(|&form| form != rudb_vector::Form::Flat),
"every chunk was flattened on the way in, so the drain still pays for a copy nobody wants"
);
let answer = db.query("SELECT count(*), sum(k) FROM kept").expect("it reads back");
let rows: Vec<Vec<Value>> = answer.rows().collect();
assert_eq!(rows, vec![vec![Value::BigInt(3072), Value::HugeInt(3072)]]);
}
#[test]
fn a_file_backed_insert_streams_into_a_snapshot_that_a_new_process_can_read() {
let path = std::env::temp_dir().join(format!(
"rudb-native-checkpoint-{}-{}.rdb",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("the clock advances")
.as_nanos()
));
let database = Database::open(path.to_str().expect("a UTF-8 temporary path"))
.expect("the new native database opens");
database.execute("CREATE TABLE hits (id INTEGER, name VARCHAR)").expect("the table is made");
database
.execute("INSERT INTO hits VALUES (1, 'one'), (2, NULL), (3, 'three')")
.expect("the rows are inserted");
assert!(path.exists(), "the insert publishes its snapshot");
assert!(database.with_catalog(|catalog| {
let name = rudb_catalog::QualifiedName::new("memory", "main", "hits");
catalog.table(&name).expect("the table is there").rows().is_native()
}));
database.execute("CHECKPOINT").expect("checkpoint sees an already committed snapshot");
drop(database);
let reopened = Database::open(path.to_str().expect("a UTF-8 temporary path"))
.expect("the committed native database reopens");
assert_eq!(
rows(&reopened, "SELECT count(*), sum(id), min(name) FROM hits"),
vec![vec![Value::BigInt(3), Value::HugeInt(6), Value::Varchar("one".into())]]
);
std::fs::remove_file(path).expect("the temporary native database is removed");
}
#[test]
fn an_insert_into_a_committed_empty_table_streams_into_the_file() {
let path = std::env::temp_dir().join(format!(
"rudb-native-empty-reload-{}-{}.rdb",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("the clock advances")
.as_nanos()
));
let database = Database::open(path.to_str().expect("a UTF-8 temporary path"))
.expect("the new native database opens");
database.execute("CREATE TABLE hits (id INTEGER, name VARCHAR)").expect("the table is made");
database.execute("CHECKPOINT").expect("the empty table is committed on its own");
drop(database);
let reopened = Database::open(path.to_str().expect("a UTF-8 temporary path"))
.expect("the committed empty table reopens");
reopened
.execute("INSERT INTO hits VALUES (1, 'one'), (2, NULL), (3, 'three')")
.expect("the rows are inserted");
assert!(reopened.with_catalog(|catalog| {
let name = rudb_catalog::QualifiedName::new("memory", "main", "hits");
catalog.table(&name).expect("the table is there").rows().is_native()
}));
drop(reopened);
let again = Database::open(path.to_str().expect("a UTF-8 temporary path"))
.expect("the appended generation reopens");
assert_eq!(
rows(&again, "SELECT count(*), sum(id), min(name) FROM hits"),
vec![vec![Value::BigInt(3), Value::HugeInt(6), Value::Varchar("one".into())]]
);
std::fs::remove_file(path).expect("the temporary native database is removed");
}
#[test]
fn a_committed_empty_table_streams_beside_a_table_that_holds_rows() {
let path = std::env::temp_dir().join(format!(
"rudb-native-empty-neighbour-{}-{}.rdb",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("the clock advances")
.as_nanos()
));
let database = Database::open(path.to_str().expect("a UTF-8 temporary path"))
.expect("the new native database opens");
database.execute("CREATE TABLE kept (id INTEGER)").expect("the neighbour is made");
database.execute("INSERT INTO kept VALUES (7), (8)").expect("the neighbour gets rows");
database.execute("CREATE TABLE hits (id INTEGER, name VARCHAR)").expect("the target is made");
database.execute("CHECKPOINT").expect("both tables are committed");
drop(database);
let reopened = Database::open(path.to_str().expect("a UTF-8 temporary path"))
.expect("the committed pair reopens");
reopened.execute("INSERT INTO hits VALUES (1, 'one'), (2, 'two')").expect("the rows go in");
assert!(reopened.with_catalog(|catalog| {
let name = rudb_catalog::QualifiedName::new("memory", "main", "hits");
catalog.table(&name).expect("the table is there").rows().is_native()
}));
drop(reopened);
let again = Database::open(path.to_str().expect("a UTF-8 temporary path"))
.expect("the appended generation reopens");
assert_eq!(rows(&again, "SELECT count(*) FROM hits"), vec![vec![Value::BigInt(2)]]);
assert_eq!(
rows(&again, "SELECT count(*), sum(id) FROM kept"),
vec![vec![Value::BigInt(2), Value::HugeInt(15)]]
);
std::fs::remove_file(path).expect("the temporary native database is removed");
}
#[test]
fn a_temporary_table_is_not_written_into_a_file_backed_database() {
let path = std::env::temp_dir().join(format!(
"rudb-native-temporary-{}-{}.rdb",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("the clock advances")
.as_nanos()
));
let database = Database::open(path.to_str().expect("a UTF-8 temporary path"))
.expect("the new native database opens");
database.execute("CREATE TABLE kept (id INTEGER)").expect("the stored table is made");
database.execute("INSERT INTO kept VALUES (1), (2)").expect("the stored rows go in");
database
.execute("CREATE TEMPORARY TABLE gone (id INTEGER)")
.expect("the temporary table is made");
database.execute("INSERT INTO gone VALUES (3), (4), (5)").expect("the temporary rows go in");
assert_eq!(rows(&database, "SELECT count(*) FROM gone"), vec![vec![Value::BigInt(3)]]);
database.execute("CHECKPOINT").expect("the checkpoint writes the stored table only");
drop(database);
let reopened = Database::open(path.to_str().expect("a UTF-8 temporary path"))
.expect("the committed native database reopens");
assert_eq!(rows(&reopened, "SELECT count(*) FROM kept"), vec![vec![Value::BigInt(2)]]);
assert_eq!(
rows(&reopened, "SELECT table_name FROM duckdb_tables() ORDER BY table_name"),
vec![vec![text("kept")]]
);
std::fs::remove_file(path).expect("the temporary native database is removed");
}
#[test]
fn a_native_frequency_synopsis_answers_count_topn() {
let path = std::env::temp_dir().join(format!(
"rudb-native-frequency-{}-{}.rdb",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("the clock advances")
.as_nanos()
));
let database = Database::open(path.to_str().expect("a UTF-8 temporary path"))
.expect("the new native database opens");
database.execute("CREATE TABLE hits (id BIGINT)").expect("the table is made");
database
.execute(
"INSERT INTO hits SELECT CASE WHEN range < 1000 THEN 0 WHEN range < 1500 THEN 1 \
WHEN range < 1750 THEN 2 ELSE range END FROM range(5000)",
)
.expect("the rows are inserted");
assert_eq!(
rows(&database, "SELECT id, count(*) AS c FROM hits GROUP BY id ORDER BY c DESC LIMIT 3",),
vec![
vec![Value::BigInt(0), Value::BigInt(1000)],
vec![Value::BigInt(1), Value::BigInt(500)],
vec![Value::BigInt(2), Value::BigInt(250)],
]
);
assert_eq!(
rows(
&database,
"SELECT id, id - 1, id - 2, count(*) AS c FROM hits \
GROUP BY id, id - 1, id - 2 ORDER BY c DESC LIMIT 3",
),
vec![
vec![Value::BigInt(0), Value::BigInt(-1), Value::BigInt(-2), Value::BigInt(1000)],
vec![Value::BigInt(1), Value::BigInt(0), Value::BigInt(-1), Value::BigInt(500)],
vec![Value::BigInt(2), Value::BigInt(1), Value::BigInt(0), Value::BigInt(250)],
]
);
drop(database);
std::fs::remove_file(path).expect("the temporary native database is removed");
}
#[test]
fn a_join_a_certificate_says_changes_nothing_is_deleted_and_the_row_counts_agree() {
let path = std::env::temp_dir().join(format!(
"rudb-graph-eliminate-{}-{}.rdb",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("the clock advances")
.as_nanos()
));
let db = Database::open(path.to_str().expect("a UTF-8 temporary path")).unwrap();
db.execute("CREATE TABLE customer (c_custkey INTEGER, c_name VARCHAR)").unwrap();
db.execute("CREATE TABLE orders (o_orderkey INTEGER, o_custkey INTEGER)").unwrap();
db.execute("CREATE TABLE returns (r_orderkey INTEGER, r_custkey INTEGER)").unwrap();
db.execute("INSERT INTO customer SELECT i, 'c' || i FROM range(1, 4001) AS r(i)").unwrap();
db.execute("INSERT INTO orders SELECT i, 1 + i % 4000 FROM range(1, 10001) AS r(i)").unwrap();
db.execute("INSERT INTO returns SELECT i, 1 + i % 4000 FROM range(1, 10001) AS r(i)").unwrap();
db.execute("INSERT INTO returns VALUES (99999, 99999)").unwrap();
db.execute(
"SET graph_links = 'orders(o_custkey) -> customer(c_custkey), \
returns(r_custkey) -> customer(c_custkey)'",
)
.unwrap();
db.execute("CHECKPOINT").unwrap();
db.execute("SET graph_sections = 'on'").unwrap();
let explained = |sql: &str| match db
.query(&format!("EXPLAIN {sql}"))
.expect("the explain ran")
.value_at(0, 1)
{
Value::Varchar(text) => text,
other => panic!("the plan came back as {other:?}"),
};
let counted = "SELECT count(*), sum(o_orderkey) FROM orders \
JOIN customer ON o_custkey = c_custkey";
assert!(!explained(counted).contains("Join"), "{}", explained(counted));
let answer = rows(&db, counted);
assert_eq!(answer, vec![vec![Value::BigInt(10000), Value::HugeInt(50_005_000)]]);
db.execute("SET stats_join_elimination = false").unwrap();
assert!(explained(counted).contains("Join"), "the rule is still on");
assert_eq!(answer, rows(&db, counted), "the join was doing something after all");
db.execute("SET stats_join_elimination = true").unwrap();
let partial = "SELECT count(*) FROM returns JOIN customer ON r_custkey = c_custkey";
assert!(explained(partial).contains("Join"), "{}", explained(partial));
assert_eq!(rows(&db, partial), vec![vec![Value::BigInt(10000)]], "the stray row is dropped");
let outer = "SELECT max(c_name) FROM orders LEFT JOIN customer ON o_custkey = c_custkey";
let plan = explained(outer);
assert!(plan.contains("Join INNER"), "nothing was padded, so nothing was preserved:\n{plan}");
assert_eq!(rows(&db, outer), vec![vec![Value::Varchar("c999".into())]]);
let partial_outer = "SELECT count(*) FROM returns LEFT JOIN customer ON r_custkey = c_custkey";
assert!(explained(partial_outer).contains("Join LEFT"), "{}", explained(partial_outer));
assert_eq!(rows(&db, partial_outer), vec![vec![Value::BigInt(10001)]]);
drop(db);
std::fs::remove_file(&path).ok();
}
#[test]
fn the_conjunct_the_statistics_call_selective_runs_first_and_the_answer_does_not_change() {
let db = Database::new();
db.execute("CREATE TABLE t (k INTEGER, s VARCHAR)").unwrap();
db.execute("INSERT INTO t SELECT i % 1000, 'row' || i FROM range(0, 2000) AS r(i)").unwrap();
let query = "SELECT count(*) FROM t WHERE upper(s) = 'ROW7' AND k = 7";
let plan = db.plan(query).unwrap();
let filter = plan.lines().find(|line| line.contains("Filter")).expect("a filter is planned");
let (first, second) = filter.split_once(" AND ").expect("two conjuncts are printed");
assert!(first.contains("#0.0"), "the counted equality goes in front:\n{plan}");
assert!(second.contains("upper"), "and the string function goes behind it:\n{plan}");
let answer = rows(&db, query);
db.execute("SET stats_filter_order = false").unwrap();
let unordered = db.plan(query).unwrap();
let written = unordered.lines().find(|line| line.contains("Filter")).expect("still a filter");
assert!(
written.split_once(" AND ").expect("two conjuncts").0.contains("upper"),
"with the rule off the predicate is in the order it was written:\n{unordered}"
);
assert_eq!(rows(&db, query), answer, "the order the conjuncts run in is not an answer");
assert_eq!(answer, vec![vec![Value::BigInt(1)]]);
}
#[test]
fn a_decimal_sum_is_as_wide_as_a_decimal_goes() {
let db = database();
let one = |sql: &str| rows(&db, sql);
assert_eq!(
one("SELECT sum(99.9::DECIMAL(3,1)) FROM range(100)"),
vec![vec![Value::Decimal { unscaled: 99_900, width: 38, scale: 1 }]]
);
assert_eq!(
one("SELECT typeof(sum(x)) FROM (SELECT 1.5::DECIMAL(4,1) x)"),
vec![vec![text("DECIMAL(38,1)")]]
);
}
#[test]
fn an_aggregate_runs_over_the_elements_of_a_list() {
let db = database();
let one = |sql: &str| rows(&db, sql);
assert_eq!(one("SELECT list_aggr([1, 2, 3], 'sum')"), vec![vec![Value::HugeInt(6)]]);
assert_eq!(
db.query("SELECT list_aggr([1, 2, 3], 'sum')").unwrap().names(),
["list_aggr(list_value(1, 2, 3), 'sum')"]
);
assert_eq!(one("SELECT list_aggr([1, 2, 3], 'avg')"), vec![vec![Value::Double(2.0)]]);
assert_eq!(one("SELECT list_aggr([1, NULL, 3], 'count')"), vec![vec![Value::BigInt(2)]]);
assert_eq!(one("SELECT list_aggr(['b', 'a'], 'min')"), vec![vec![text("a")]]);
assert_eq!(one("SELECT list_aggregate([1, 2], 'SUM')"), vec![vec![Value::HugeInt(3)]]);
assert_eq!(one("SELECT array_aggr([1, 2], 'max')"), vec![vec![integer(2)]]);
assert_eq!(one("SELECT aggregate([1], 'sum')"), vec![vec![Value::HugeInt(1)]]);
assert_eq!(one("SELECT list_aggr([1, 2], 's' || 'um')"), vec![vec![Value::HugeInt(3)]]);
assert_eq!(
one("SELECT list_aggr([], 'sum'), list_aggr([], 'count'), list_aggr(NULL, 'count')"),
vec![vec![Value::Null, Value::BigInt(0), Value::Null]]
);
assert_eq!(one("SELECT list_aggr(NULL, 'nope')"), vec![vec![Value::Null]]);
assert_eq!(
one("SELECT typeof(list_aggr([1, 2]::TINYINT[], 'sum')), \
typeof(list_aggr([1, 2]::TINYINT[], 'min')), \
typeof(list_aggr([1.5]::FLOAT[], 'sum')), \
typeof(list_aggr([1, 2]::DECIMAL(4,1)[], 'sum'))"),
vec![vec![text("HUGEINT"), text("TINYINT"), text("DOUBLE"), text("DECIMAL(38,1)")]]
);
assert_eq!(
one("SELECT list_aggr(x, 'sum') FROM (VALUES ([1, 2]), (NULL), ([3])) v(x)"),
vec![vec![Value::HugeInt(3)], vec![Value::Null], vec![Value::HugeInt(3)]]
);
}
#[test]
fn an_aggregate_over_a_list_is_refused_the_way_the_pin_refuses_it() {
let db = database();
let error = |sql: &str| db.query(sql).unwrap_err().to_string();
assert!(
error("SELECT list_aggr([1, 2], 'nope')")
.starts_with("Catalog Error: Aggregate Function with name nope does not exist!")
);
assert!(
error("SELECT list_aggr([1, 2], 'lower')")
.starts_with("Catalog Error: lower is not an aggregate function")
);
assert!(
error("SELECT list_aggr([1, 2], NULL)")
.starts_with("Catalog Error: Aggregate Function with name NULL does not exist!")
);
assert!(error("SELECT list_aggr([1, 2])").starts_with(
"Binder Error: No function matches the given name and argument types \
'list_aggr(INTEGER[])'. You might need to add explicit type casts.\n\tCandidate \
functions:\n\tlist_aggr(col0 ANY[], col1 VARCHAR, [ANY...]) -> ANY\n"
));
db.execute("CREATE TABLE f AS SELECT 'sum' AS f").unwrap();
assert!(error("SELECT list_aggr([1, 2], f) FROM f").starts_with(
"Binder Error: The \"col1\" argument in function \"list_aggr\" must be a constant \
expression"
));
assert!(error("SELECT list_aggr(['a'], 'sum')").starts_with(
"Binder Error: No matching aggregate function\nBinder Error: No function matches the \
given name and argument types 'sum(VARCHAR)'."
));
assert!(error("SELECT list_aggr([1, 2], 'sum', 3)").starts_with(
"Binder Error: No matching aggregate function\nBinder Error: No function matches the \
given name and argument types 'sum(INTEGER, INTEGER)'."
));
}