mik_sql/validate/
column.rs1const MAX_IDENTIFIER_LENGTH: usize = 63;
5
6#[inline]
36#[must_use]
37pub fn is_valid_sql_identifier(s: &str) -> bool {
38 if s.is_empty() || s.len() > MAX_IDENTIFIER_LENGTH {
39 return false;
40 }
41
42 let mut chars = s.chars();
43
44 match chars.next() {
46 Some(c) if c.is_ascii_alphabetic() || c == '_' => {},
47 _ => return false,
48 }
49
50 chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
52}
53
54#[inline]
77pub fn assert_valid_sql_identifier(s: &str, context: &str) {
78 assert!(
79 is_valid_sql_identifier(s),
80 "Invalid SQL {context} name '{s}': must start with letter/underscore, \
81 contain only ASCII alphanumeric/underscore, and be 1-63 chars"
82 );
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_valid_sql_identifiers() {
91 assert!(is_valid_sql_identifier("users"));
93 assert!(is_valid_sql_identifier("user_id"));
94 assert!(is_valid_sql_identifier("_private"));
95 assert!(is_valid_sql_identifier("Table123"));
96 assert!(is_valid_sql_identifier("a"));
97 assert!(is_valid_sql_identifier("_"));
98 assert!(is_valid_sql_identifier("UPPERCASE"));
99 assert!(is_valid_sql_identifier("mixedCase"));
100 assert!(is_valid_sql_identifier("with_123_numbers"));
101 }
102
103 #[test]
104 fn test_invalid_sql_identifiers() {
105 assert!(!is_valid_sql_identifier(""));
107
108 assert!(!is_valid_sql_identifier("123abc"));
110 assert!(!is_valid_sql_identifier("1"));
111
112 assert!(!is_valid_sql_identifier("user-name"));
114 assert!(!is_valid_sql_identifier("user.id"));
115 assert!(!is_valid_sql_identifier("user name"));
116 assert!(!is_valid_sql_identifier("user;drop"));
117 assert!(!is_valid_sql_identifier("table'"));
118 assert!(!is_valid_sql_identifier("table\""));
119 assert!(!is_valid_sql_identifier("table`"));
120 assert!(!is_valid_sql_identifier("table("));
121 assert!(!is_valid_sql_identifier("table)"));
122
123 assert!(!is_valid_sql_identifier("users; DROP TABLE"));
125 assert!(!is_valid_sql_identifier("users--"));
126 assert!(!is_valid_sql_identifier("users/*"));
127 }
128
129 #[test]
130 fn test_sql_identifier_length_limit() {
131 let valid_63 = "a".repeat(63);
133 assert!(is_valid_sql_identifier(&valid_63));
134
135 let invalid_64 = "a".repeat(64);
137 assert!(!is_valid_sql_identifier(&invalid_64));
138 }
139
140 #[test]
141 fn test_identifier_injection_attempts() {
142 assert!(!is_valid_sql_identifier("users; DROP TABLE x"));
144 assert!(!is_valid_sql_identifier("users--"));
145 assert!(!is_valid_sql_identifier("users/*comment*/"));
146 assert!(!is_valid_sql_identifier("users'"));
147 assert!(!is_valid_sql_identifier("users\""));
148 assert!(!is_valid_sql_identifier("users`"));
149 assert!(!is_valid_sql_identifier("users;"));
150 assert!(!is_valid_sql_identifier("(SELECT 1)"));
151 assert!(!is_valid_sql_identifier("1 OR 1=1"));
152
153 assert!(!is_valid_sql_identifier("users\u{0000}")); assert!(!is_valid_sql_identifier("users\u{200B}")); assert!(!is_valid_sql_identifier("usërs")); assert!(!is_valid_sql_identifier("用户")); assert!(!is_valid_sql_identifier("users")); }
162
163 #[test]
164 #[should_panic(expected = "Invalid SQL table name")]
165 fn test_assert_valid_identifier_panics() {
166 assert_valid_sql_identifier("users; DROP TABLE", "table");
167 }
168}