use powdb_query::executor::Engine;
use powdb_query::result::QueryResult;
use powdb_storage::types::Value;
fn authors(dir: &std::path::Path) -> Engine {
let mut engine = Engine::new(dir).unwrap();
engine
.execute_powql("type Author { required id: int, name: str, age: int }")
.unwrap();
engine
.execute_powql(r#"insert Author { id := 1, name := "alice", age := 30 }"#)
.unwrap();
engine
.execute_powql(r#"insert Author { id := 2, name := "bob" }"#)
.unwrap();
engine
}
#[track_caller]
fn names_sql(engine: &mut Engine, sql: &str) -> Vec<String> {
names(
engine
.execute_sql(sql)
.unwrap_or_else(|e| panic!("SQL `{sql}` must work, got: {e}")),
sql,
)
}
#[track_caller]
fn names_powql(engine: &mut Engine, powql: &str) -> Vec<String> {
names(
engine
.execute_powql(powql)
.unwrap_or_else(|e| panic!("PowQL `{powql}` must work, got: {e}")),
powql,
)
}
#[track_caller]
fn names(result: QueryResult, query: &str) -> Vec<String> {
match result {
QueryResult::Rows { rows, .. } => rows
.iter()
.map(|row| match &row[0] {
Value::Str(s) => s.clone(),
other => format!("{other:?}"),
})
.collect(),
other => panic!("`{query}`: expected rows, got {other:?}"),
}
}
#[test]
fn eq_null_matches_nothing_in_sql_and_means_is_null_in_powql() {
let dir = tempfile::tempdir().unwrap();
let mut engine = authors(dir.path());
assert!(names_sql(&mut engine, "SELECT name FROM Author WHERE age = NULL").is_empty());
assert!(names_sql(&mut engine, "SELECT name FROM Author WHERE age <> NULL").is_empty());
assert!(names_sql(&mut engine, "SELECT name FROM Author WHERE age != NULL").is_empty());
assert_eq!(
names_sql(&mut engine, "SELECT name FROM Author WHERE age IS NULL"),
vec!["bob"]
);
assert_eq!(
names_powql(&mut engine, "Author filter .age = null { .name }"),
vec!["bob"]
);
assert_eq!(
names_powql(&mut engine, "Author filter .age != null { .name }"),
vec!["alice"]
);
}
#[test]
fn not_of_eq_null_returns_every_row() {
let dir = tempfile::tempdir().unwrap();
let mut engine = authors(dir.path());
assert_eq!(
names_sql(
&mut engine,
"SELECT name FROM Author WHERE NOT (age = NULL)"
),
vec!["alice", "bob"]
);
}
#[test]
fn every_other_comparison_agrees_across_the_two_frontends() {
let dir = tempfile::tempdir().unwrap();
let mut engine = authors(dir.path());
for (sql, powql) in [
(
"SELECT name FROM Author WHERE age > 20",
"Author filter .age > 20 { .name }",
),
(
"SELECT name FROM Author WHERE age != 30",
"Author filter .age != 30 { .name }",
),
(
"SELECT name FROM Author WHERE age IS NOT NULL",
"Author filter .age != null { .name }",
),
(
"SELECT name FROM Author WHERE name LIKE 'a%'",
r#"Author filter .name like "a%" { .name }"#,
),
] {
assert_eq!(
names_sql(&mut engine, sql),
names_powql(&mut engine, powql),
"`{sql}` and `{powql}` must agree"
);
}
}
#[test]
fn every_documented_cast_type_string_is_accepted_in_sql() {
let dir = tempfile::tempdir().unwrap();
let mut engine = authors(dir.path());
for ty in ["int", "float", "str", "bool", "datetime", "uuid", "bytes"] {
let sql = format!("SELECT cast(id, '{ty}') AS c FROM Author WHERE id = 1");
assert!(
engine.execute_sql(&sql).is_ok(),
"`{sql}` must parse: docs/SQL.md lists `{ty}` as a valid cast type"
);
}
assert!(engine
.execute_sql("SELECT cast(id, 'int8') FROM Author")
.is_err());
}
#[test]
fn not_is_two_valued_so_it_admits_null_rows() {
let dir = tempfile::tempdir().unwrap();
let mut engine = authors(dir.path());
assert_eq!(
names_sql(
&mut engine,
"SELECT name FROM Author WHERE NOT (age > 30) ORDER BY id"
),
vec!["alice", "bob"]
);
assert_eq!(
names_sql(
&mut engine,
"SELECT name FROM Author WHERE age IS NOT NULL AND NOT (age > 30)"
),
vec!["alice"]
);
}
#[test]
fn double_quotes_are_identifiers_and_single_quotes_are_strings() {
let dir = tempfile::tempdir().unwrap();
let mut engine = authors(dir.path());
assert_eq!(
names_sql(&mut engine, r#"SELECT "name" FROM "Author" ORDER BY "id""#),
vec!["alice", "bob"]
);
assert_eq!(
names_sql(&mut engine, "SELECT name FROM Author WHERE name = 'alice'"),
vec!["alice"]
);
assert_eq!(
names_sql(
&mut engine,
r#"SELECT name FROM Author WHERE name = "name" ORDER BY id"#
),
vec!["alice", "bob"]
);
}
#[test]
fn a_quoted_identifier_is_never_a_keyword() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_powql("type T { required id: int, `limit`: int, `order`: str }")
.unwrap();
engine
.execute_powql(r#"insert T { id := 1, `limit` := 5, `order` := "x" }"#)
.unwrap();
match engine
.execute_sql(r#"SELECT "limit", "order" FROM T"#)
.unwrap()
{
QueryResult::Rows { columns, rows } => {
assert_eq!(columns, vec!["limit", "order"]);
assert_eq!(rows, vec![vec![Value::Int(5), Value::Str("x".into())]]);
}
other => panic!("expected rows, got {other:?}"),
}
}
#[test]
fn quoted_identifiers_work_in_alias_and_qualified_positions() {
let dir = tempfile::tempdir().unwrap();
let mut engine = authors(dir.path());
assert_eq!(
names_sql(
&mut engine,
r#"SELECT a."name" FROM Author AS a WHERE a."age" > 20"#
),
vec!["alice"]
);
assert_eq!(
names_sql(
&mut engine,
r#"SELECT "a"."name" FROM Author AS "a" WHERE "a"."age" > 20"#
),
vec!["alice"]
);
}