1use crate::error::{BsqlResult, ConnectError};
4
5pub fn validate_savepoint_name(name: &str) -> BsqlResult<()> {
12 if name.is_empty() {
13 return Err(ConnectError::create("savepoint name must not be empty"));
14 }
15 if name.len() > 63 {
16 return Err(ConnectError::create(
17 "savepoint name must not exceed 63 characters",
18 ));
19 }
20 let first = name.as_bytes()[0];
21 if !first.is_ascii_alphabetic() && first != b'_' {
22 return Err(ConnectError::create(
23 "savepoint name must start with a letter or underscore",
24 ));
25 }
26 if !name.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_') {
27 return Err(ConnectError::create(
28 "savepoint name must contain only ASCII letters, digits, and underscores",
29 ));
30 }
31 Ok(())
32}
33
34#[cfg(test)]
35mod tests {
36 use super::*;
37
38 #[test]
39 fn validate_savepoint_name_valid() {
40 assert!(validate_savepoint_name("sp1").is_ok());
41 assert!(validate_savepoint_name("_sp").is_ok());
42 assert!(validate_savepoint_name("my_savepoint_123").is_ok());
43 }
44
45 #[test]
46 fn validate_savepoint_name_empty() {
47 assert!(validate_savepoint_name("").is_err());
48 }
49
50 #[test]
51 fn validate_savepoint_name_too_long() {
52 let long = "a".repeat(64);
53 assert!(validate_savepoint_name(&long).is_err());
54 }
55
56 #[test]
57 fn validate_savepoint_name_max_length() {
58 let max = "a".repeat(63);
59 assert!(validate_savepoint_name(&max).is_ok());
60 }
61
62 #[test]
63 fn validate_savepoint_name_starts_with_digit() {
64 assert!(validate_savepoint_name("1sp").is_err());
65 }
66
67 #[test]
68 fn validate_savepoint_name_starts_with_underscore() {
69 assert!(validate_savepoint_name("_sp").is_ok());
70 }
71
72 #[test]
73 fn validate_savepoint_name_special_chars() {
74 assert!(validate_savepoint_name("sp-1").is_err());
75 assert!(validate_savepoint_name("sp.1").is_err());
76 assert!(validate_savepoint_name("sp 1").is_err());
77 assert!(validate_savepoint_name("sp;1").is_err());
78 assert!(validate_savepoint_name("sp'1").is_err());
79 }
80}