use powdb_query::executor::Engine;
use powdb_query::result::QueryResult;
use powdb_storage::types::Value;
use std::sync::atomic::{AtomicU64, Ordering};
static UNIQUE_DIR: AtomicU64 = AtomicU64::new(0);
fn fresh_dir(tag: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!(
"powdb_sqlquote_{tag}_{}_{}",
std::process::id(),
UNIQUE_DIR.fetch_add(1, Ordering::Relaxed),
));
let _ = std::fs::remove_dir_all(&dir);
dir
}
fn rows_sql(engine: &mut Engine, query: &str) -> Vec<String> {
let result = engine
.execute_sql(query)
.unwrap_or_else(|e| panic!("failed to execute `{query}`: {e}"));
match result {
QueryResult::Rows { rows, .. } => rows
.iter()
.map(|row| {
row.iter()
.map(|value| match value {
Value::Str(s) => s.clone(),
Value::Int(n) => n.to_string(),
other => format!("{other:?}"),
})
.collect::<Vec<_>>()
.join("|")
})
.collect(),
other => panic!("`{query}`: expected rows, got {other:?}"),
}
}
fn authors(dir: &std::path::Path) -> Engine {
let mut engine = Engine::new(dir).unwrap();
engine
.execute_powql("type Author { id: int, name: str }")
.unwrap();
for (id, name) in [(1, "alice"), (2, "bob")] {
engine
.execute_powql(&format!(
"insert Author {{ id := {id}, name := \"{name}\" }}"
))
.unwrap();
}
engine
}
#[test]
fn a_quoted_column_is_the_column_not_a_string() {
let dir = fresh_dir("col");
let mut engine = authors(&dir);
assert_eq!(
rows_sql(&mut engine, r#"SELECT "name" FROM Author"#),
vec!["alice", "bob"]
);
assert_eq!(
rows_sql(&mut engine, "SELECT name FROM Author"),
rows_sql(&mut engine, r#"SELECT "name" FROM Author"#),
);
}
#[test]
fn a_quoted_table_is_the_table() {
let dir = fresh_dir("table");
let mut engine = authors(&dir);
assert_eq!(
rows_sql(&mut engine, r#"SELECT name FROM "Author""#),
vec!["alice", "bob"]
);
assert_eq!(
rows_sql(&mut engine, r#"SELECT "name" FROM "Author" WHERE "id" = 1"#),
vec!["alice"]
);
}
#[test]
fn single_quotes_are_still_string_literals() {
let dir = fresh_dir("str");
let mut engine = authors(&dir);
assert_eq!(
rows_sql(&mut engine, "SELECT name FROM Author WHERE name = 'alice'"),
vec!["alice"]
);
assert_eq!(
rows_sql(
&mut engine,
r#"SELECT name FROM Author WHERE name = "name""#
),
vec!["alice", "bob"]
);
}
#[test]
fn a_quoted_identifier_is_never_a_keyword() {
let dir = fresh_dir("reserved");
let mut engine = Engine::new(&dir).unwrap();
engine
.execute_powql("type T { id: int, `limit`: int, `order`: str }")
.unwrap();
engine
.execute_powql("insert T { id := 1, `limit` := 5, `order` := \"x\" }")
.unwrap();
assert_eq!(
rows_sql(&mut engine, r#"SELECT "limit", "order" FROM T"#),
vec!["5|x"]
);
}
#[test]
fn a_quoted_identifier_that_cannot_be_expressed_is_rejected_loudly() {
let dir = fresh_dir("reject");
let mut engine = authors(&dir);
let err = engine
.execute_sql(r#"SELECT "na`me" FROM Author"#)
.expect_err("a backtick in a quoted identifier must be rejected");
let msg = err.to_string();
assert!(
msg.contains("backtick"),
"error should explain the backtick restriction, got: {msg}"
);
let err = engine
.execute_sql(r#"SELECT "" FROM Author"#)
.expect_err("an empty quoted identifier must be rejected");
assert!(
err.to_string().contains("empty quoted identifier"),
"got: {err}"
);
}