use sql_dialect_fmt_parser::parse;
use sql_dialect_fmt_test_support::parser::assert_parse_roundtrip;
fn only_error(sql: &str) -> sql_dialect_fmt_parser::ParseError {
assert_parse_roundtrip(sql);
let parse = parse(sql);
let errors = parse.errors();
assert_eq!(
errors.len(),
1,
"expected exactly one error for {sql:?}: {errors:?}"
);
errors[0].clone()
}
#[test]
fn expect_message_uses_human_keyword_name() {
let err = parse("MERGE tgt USING src ON a = b")
.errors()
.iter()
.find(|e| e.message.contains("INTO"))
.cloned()
.expect("an INTO diagnostic");
assert_eq!(err.message, "expected INTO");
assert!(!err.message.contains("SyntaxKind"));
assert!(!err.message.contains("_KW"));
}
#[test]
fn expect_message_quotes_punctuation() {
let parse = parse("SELECT * FROM t MATCH_RECOGNIZE");
let msg = &parse
.errors()
.iter()
.find(|e| e.message.contains("'('"))
.expect("a '(' diagnostic")
.message;
assert!(msg.contains("'('"), "{msg:?}");
assert!(!msg.contains("L_PAREN"), "{msg:?}");
}
#[test]
fn span_covers_the_whole_offending_token() {
let sql = "( where )";
let parse = parse(sql);
let err = &parse.errors()[0];
let start = sql.find("where").unwrap();
assert_eq!(err.offset, start);
assert_eq!(err.len, "where".len());
assert_eq!(&sql[err.range()], "where");
}
#[test]
fn span_for_multibyte_token_is_byte_accurate() {
let sql = "WITH c AS \"芋\" SELECT 1";
let err = parse(sql)
.errors()
.iter()
.find(|e| e.message.contains("query"))
.cloned()
.expect("a query diagnostic");
let start = sql.find('"').unwrap();
assert_eq!(err.offset, start);
assert_eq!(err.len, "\"芋\"".len());
assert_eq!(&sql[err.range()], "\"芋\"");
}
#[test]
fn error_at_eof_is_zero_width_at_source_end() {
let sql = "SELECT a FROM";
let err = only_error(sql);
assert!(err.message.contains("table reference"), "{:?}", err.message);
assert_eq!(err.offset, sql.len());
assert_eq!(err.len, 0);
assert_eq!(err.range(), sql.len()..sql.len());
}
#[test]
fn parse_error_displays_human_message_with_location() {
let err = only_error("SELECT a FROM");
let shown = err.to_string();
assert!(shown.starts_with("expected a table reference"), "{shown}");
assert!(shown.contains("at byte 13"), "{shown}");
let _: &dyn std::error::Error = &err;
}
#[test]
fn unbalanced_parens_at_eof_never_panic() {
let sql = "SELECT ((((((((";
assert_parse_roundtrip(sql);
assert!(!parse(sql).errors().is_empty());
}
#[test]
fn truncated_statements_recover_without_panic() {
for sql in [
"MERGE",
"INSERT INTO",
"SELECT a FROM t WHERE",
"CREATE TABLE t (",
"UPDATE t SET",
"GRANT SELECT ON",
] {
assert_parse_roundtrip(sql);
}
}