use super::*;
#[test]
fn empty_string() {
assert_eq!(collect_parts(""), vec![]);
}
#[test]
fn no_placeholders() {
let parts = collect_parts("SELECT * FROM users");
assert_eq!(parts, vec![PlaceholderPart::Sql("SELECT * FROM users")]);
}
#[test]
fn single_placeholder() {
let parts = collect_parts("SELECT * FROM users WHERE id = :id");
assert_eq!(
parts,
vec![
PlaceholderPart::Sql("SELECT * FROM users WHERE id = "),
PlaceholderPart::Placeholder("id"),
]
);
}
#[test]
fn multiple_placeholders() {
let parts = collect_parts("SELECT * FROM users WHERE id = :id AND name = :name");
assert_eq!(
parts,
vec![
PlaceholderPart::Sql("SELECT * FROM users WHERE id = "),
PlaceholderPart::Placeholder("id"),
PlaceholderPart::Sql(" AND name = "),
PlaceholderPart::Placeholder("name"),
]
);
}
#[test]
fn placeholder_at_start() {
let parts = collect_parts(":id");
assert_eq!(parts, vec![PlaceholderPart::Placeholder("id"),]);
}
#[test]
fn placeholder_at_end() {
let parts = collect_parts("SELECT :id");
assert_eq!(
parts,
vec![
PlaceholderPart::Sql("SELECT "),
PlaceholderPart::Placeholder("id"),
]
);
}
#[test]
fn placeholder_names_with_numbers_and_underscores() {
let parts = collect_parts(":id_1 :user2 :_private");
assert_eq!(
parts,
vec![
PlaceholderPart::Placeholder("id_1"),
PlaceholderPart::Sql(" "),
PlaceholderPart::Placeholder("user2"),
PlaceholderPart::Sql(" "),
PlaceholderPart::Placeholder("_private"),
]
);
}
#[test]
fn double_colon_not_placeholder() {
let parts = collect_parts("SELECT id::integer");
assert_eq!(parts, vec![PlaceholderPart::Sql("SELECT id::integer")]);
}
#[test]
fn colon_at_end_of_string() {
let parts = collect_parts("SELECT :");
assert_eq!(parts, vec![PlaceholderPart::Sql("SELECT :")]);
}
#[test]
fn colon_followed_by_non_name() {
let parts = collect_parts("SELECT :123");
assert_eq!(parts, vec![PlaceholderPart::Sql("SELECT :123")]);
}
#[test]
fn word_char_before_colon_not_placeholder() {
let parts = collect_parts("abc:param");
assert_eq!(parts, vec![PlaceholderPart::Sql("abc:param")]);
}
#[test]
fn placeholder_in_single_quotes_ignored() {
let parts = collect_parts("SELECT 'text :param' FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT 'text :param' FROM t")]
);
}
#[test]
fn placeholder_in_double_quotes_ignored() {
let parts = collect_parts("SELECT \"col:param\" FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT \"col:param\" FROM t")]
);
}
#[test]
fn placeholder_in_backticks_ignored() {
let parts = collect_parts("SELECT `col:param` FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT `col:param` FROM t")]
);
}
#[test]
fn placeholder_in_bracket_ident_ignored() {
let parts = collect_parts("SELECT [col:param] FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT [col:param] FROM t")]
);
}
#[test]
fn escaped_single_quote_with_doubling() {
let parts = collect_parts("SELECT 'don''t :param' FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT 'don''t :param' FROM t")]
);
}
#[test]
fn escaped_double_quote_with_doubling() {
let parts = collect_parts("SELECT \"col\"\"name:param\" FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT \"col\"\"name:param\" FROM t")]
);
}
#[test]
fn escaped_backtick_with_doubling() {
let parts = collect_parts("SELECT `col``name:param` FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT `col``name:param` FROM t")]
);
}
#[test]
fn backslash_escape_single_quote() {
let parts = collect_parts("SELECT 'don\\'t :param' FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT 'don\\'t :param' FROM t")]
);
}
#[test]
fn backslash_escape_double_quote() {
let parts = collect_parts("SELECT \"col\\\"name:param\" FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT \"col\\\"name:param\" FROM t")]
);
}
#[test]
fn backslash_escape_backtick() {
let parts = collect_parts("SELECT `col\\`name:param` FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT `col\\`name:param` FROM t")]
);
}
#[test]
fn backslash_at_end_of_single_quote() {
let parts = collect_parts("SELECT 'text\\' :param");
assert_eq!(parts, vec![PlaceholderPart::Sql("SELECT 'text\\' :param")]);
}
#[test]
fn backslash_backslash_in_quote() {
let parts = collect_parts("SELECT 'path\\\\:param' FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT 'path\\\\:param' FROM t")]
);
}
#[test]
fn line_comment_double_dash() {
let parts = collect_parts("SELECT * -- :param\nFROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT * -- :param\nFROM t")]
);
}
#[test]
fn line_comment_hash() {
let parts = collect_parts("SELECT * # :param\nFROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT * # :param\nFROM t")]
);
}
#[test]
fn line_comment_at_end_no_newline() {
let parts = collect_parts("SELECT * -- :param");
assert_eq!(parts, vec![PlaceholderPart::Sql("SELECT * -- :param")]);
}
#[test]
fn block_comment() {
let parts = collect_parts("SELECT * /* :param */ FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT * /* :param */ FROM t")]
);
}
#[test]
fn block_comment_multiline() {
let parts = collect_parts("SELECT * /* line1\n:param\nline2 */ FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql(
"SELECT * /* line1\n:param\nline2 */ FROM t"
)]
);
}
#[test]
fn block_comment_not_closed() {
let parts = collect_parts("SELECT * /* :param");
assert_eq!(parts, vec![PlaceholderPart::Sql("SELECT * /* :param")]);
}
#[test]
fn dollar_quote_empty_tag() {
let parts = collect_parts("SELECT $$:param$$ FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT $$:param$$ FROM t")]
);
}
#[test]
fn dollar_quote_with_tag() {
let parts = collect_parts("SELECT $tag$:param$tag$ FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT $tag$:param$tag$ FROM t")]
);
}
#[test]
fn dollar_quote_different_tags_not_matched() {
let parts = collect_parts("SELECT $a$text$b$ FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT $a$text$b$ FROM t")]
);
}
#[test]
fn dollar_quote_tag_must_be_identifier() {
let parts = collect_parts("SELECT $123$ FROM t");
assert_eq!(parts, vec![PlaceholderPart::Sql("SELECT $123$ FROM t")]);
}
#[test]
fn dollar_quote_nested_dollar_signs() {
let parts = collect_parts("SELECT $$text $ more$$ FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT $$text $ more$$ FROM t")]
);
}
#[test]
fn dollar_quote_tag_prefix_matching() {
let parts = collect_parts("SELECT $tag$text$tagg$ FROM t");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("SELECT $tag$text$tagg$ FROM t")]
);
}
#[test]
fn placeholder_before_and_after_quotes() {
let parts = collect_parts(":a 'text' :b");
assert_eq!(
parts,
vec![
PlaceholderPart::Placeholder("a"),
PlaceholderPart::Sql(" 'text' "),
PlaceholderPart::Placeholder("b"),
]
);
}
#[test]
fn complex_query_with_multiple_features() {
let sql = r#"
SELECT * FROM users
WHERE id = :id -- user id
AND name = 'O''Neil :fake'
AND email = :email /* :notthis */
AND data = $${"key": ":value"}$$
AND status::text = :status
"#;
let parts = parts_to_strings(collect_parts(sql));
assert!(parts.contains(&"PARAM:id".to_string()));
assert!(parts.contains(&"PARAM:email".to_string()));
assert!(parts.contains(&"PARAM:status".to_string()));
assert!(!parts.iter().any(|s| s.contains("PARAM:fake")));
assert!(!parts.iter().any(|s| s.contains("PARAM:notthis")));
assert!(!parts.iter().any(|s| s.contains("PARAM:value")));
}
#[test]
fn placeholder_after_various_punctuation() {
let parts = collect_parts("(:a, :b):c {:d}");
assert_eq!(
parts_to_strings(parts),
vec![
"SQL:(".to_string(),
"PARAM:a".to_string(),
"SQL:, ".to_string(),
"PARAM:b".to_string(),
"SQL:)".to_string(),
"PARAM:c".to_string(),
"SQL: {".to_string(),
"PARAM:d".to_string(),
"SQL:}".to_string(),
]
);
}
#[test]
fn unclosed_single_quote() {
let parts = collect_parts("SELECT ':param");
assert_eq!(parts, vec![PlaceholderPart::Sql("SELECT ':param")]);
}
#[test]
fn unclosed_double_quote() {
let parts = collect_parts("SELECT \":param");
assert_eq!(parts, vec![PlaceholderPart::Sql("SELECT \":param")]);
}
#[test]
fn unclosed_backtick() {
let parts = collect_parts("SELECT `:param");
assert_eq!(parts, vec![PlaceholderPart::Sql("SELECT `:param")]);
}
#[test]
fn unclosed_bracket_ident() {
let parts = collect_parts("SELECT [:param");
assert_eq!(parts, vec![PlaceholderPart::Sql("SELECT [:param")]);
}
#[test]
fn unclosed_dollar_quote() {
let parts = collect_parts("SELECT $$:param");
assert_eq!(parts, vec![PlaceholderPart::Sql("SELECT $$:param")]);
}
#[test]
fn empty_placeholder_name() {
let parts = collect_parts("SELECT : FROM t");
assert_eq!(parts, vec![PlaceholderPart::Sql("SELECT : FROM t")]);
}
#[test]
fn consecutive_placeholders() {
let parts = collect_parts(":a:b:c");
assert_eq!(
parts_to_strings(parts),
vec![
"PARAM:a".to_string(),
"PARAM:b".to_string(),
"PARAM:c".to_string(),
]
);
}
#[test]
fn placeholder_with_operators() {
let parts = collect_parts("SELECT :a+:b*:c");
assert_eq!(
parts_to_strings(parts),
vec![
"SQL:SELECT ".to_string(),
"PARAM:a".to_string(),
"SQL:+".to_string(),
"PARAM:b".to_string(),
"SQL:*".to_string(),
"PARAM:c".to_string(),
]
);
}
#[test]
fn all_quote_types_in_sequence() {
let parts = collect_parts("':a' \":b\" `:c` [:d] $$:e$$");
assert_eq!(
parts,
vec![PlaceholderPart::Sql("':a' \":b\" `:c` [:d] $$:e$$")]
);
}
#[test]
fn mixed_comment_types() {
let parts = collect_parts("-- :a\n/* :b */ # :c\n:d");
assert_eq!(
parts,
vec![
PlaceholderPart::Sql("-- :a\n/* :b */ # :c\n"),
PlaceholderPart::Placeholder("d"),
]
);
}
#[test]
fn backslash_at_string_end_boundary() {
let parts = collect_parts("SELECT 'text\\");
assert_eq!(parts, vec![PlaceholderPart::Sql("SELECT 'text\\")]);
}
#[test]
fn single_colon() {
let parts = collect_parts(":");
assert_eq!(parts, vec![PlaceholderPart::Sql(":")]);
}
#[test]
fn only_placeholder() {
let parts = collect_parts(":param");
assert_eq!(parts, vec![PlaceholderPart::Placeholder("param")]);
}
#[test]
fn placeholder_uppercase_letters() {
let parts = collect_parts(":USER_ID :UserName");
assert_eq!(
parts,
vec![
PlaceholderPart::Placeholder("USER_ID"),
PlaceholderPart::Sql(" "),
PlaceholderPart::Placeholder("UserName"),
]
);
}
#[test]
fn dollar_sign_not_quote_start() {
let parts = collect_parts("SELECT $ :param FROM t");
assert_eq!(
parts,
vec![
PlaceholderPart::Sql("SELECT $ "),
PlaceholderPart::Placeholder("param"),
PlaceholderPart::Sql(" FROM t"),
]
);
}
#[test]
fn star_slash_outside_comment() {
let parts = collect_parts("SELECT */ :param");
assert_eq!(
parts,
vec![
PlaceholderPart::Sql("SELECT */ "),
PlaceholderPart::Placeholder("param"),
]
);
}
#[test]
fn dash_not_double() {
let parts = collect_parts("SELECT - :param");
assert_eq!(
parts,
vec![
PlaceholderPart::Sql("SELECT - "),
PlaceholderPart::Placeholder("param"),
]
);
}
#[test]
fn has_placeholder_returns_true() {
assert!(has_named_placeholder("SELECT * WHERE id = :id"));
assert!(has_named_placeholder(":param"));
assert!(has_named_placeholder("text :param more"));
}
#[test]
fn has_placeholder_returns_false() {
assert!(!has_named_placeholder("SELECT * FROM users"));
assert!(!has_named_placeholder(""));
assert!(!has_named_placeholder("SELECT ':param' FROM t"));
assert!(!has_named_placeholder("-- :param"));
assert!(!has_named_placeholder("id::integer"));
}
#[test]
fn has_placeholder_short_circuits() {
assert!(has_named_placeholder(":first :second :third"));
}
#[test]
fn has_placeholder_incomplete_single_quote() {
assert!(has_named_placeholder(":param 'unclosed"));
assert!(!has_named_placeholder("'unclosed :param"));
}
#[test]
fn has_placeholder_incomplete_double_quote() {
assert!(has_named_placeholder(":param \"unclosed"));
assert!(!has_named_placeholder("\"unclosed :param"));
}
#[test]
fn has_placeholder_incomplete_backtick() {
assert!(has_named_placeholder(":param `unclosed"));
assert!(!has_named_placeholder("`unclosed :param"));
}
#[test]
fn has_placeholder_incomplete_bracket() {
assert!(has_named_placeholder(":param [unclosed"));
assert!(!has_named_placeholder("[unclosed :param"));
}
#[test]
fn has_placeholder_incomplete_line_comment() {
assert!(!has_named_placeholder("-- :param"));
assert!(!has_named_placeholder("# :param"));
assert!(has_named_placeholder(":param --"));
}
#[test]
fn has_placeholder_incomplete_block_comment() {
assert!(!has_named_placeholder("/* :param"));
assert!(has_named_placeholder(":param /*"));
}
#[test]
fn has_placeholder_incomplete_dollar_quote() {
assert!(has_named_placeholder(":param $$text"));
assert!(has_named_placeholder(":param $tag$text"));
assert!(!has_named_placeholder("$$:param"));
assert!(!has_named_placeholder("$tag$:param"));
}
#[test]
fn has_placeholder_partial_placeholder() {
assert!(!has_named_placeholder("SELECT :"));
assert!(!has_named_placeholder("SELECT :123"));
assert!(has_named_placeholder("SELECT :p"));
}
#[test]
fn has_placeholder_escaped_quote_incomplete() {
assert!(!has_named_placeholder("'text\\' :param"));
}
#[test]
fn has_placeholder_mixed_incomplete() {
assert!(has_named_placeholder(":a /* :b"));
assert!(has_named_placeholder(":a 'b"));
assert!(!has_named_placeholder("'a :b /* :c"));
assert!(!has_named_placeholder("/* 'quoted :param"));
}
#[test]
fn has_placeholder_empty_and_whitespace() {
assert!(!has_named_placeholder(""));
assert!(!has_named_placeholder(" "));
assert!(!has_named_placeholder("\n\t"));
}
#[test]
fn has_placeholder_only_special_chars() {
assert!(!has_named_placeholder("::::"));
assert!(!has_named_placeholder("/* */ -- "));
assert!(!has_named_placeholder("'''' \"\" ``"));
}