Skip to main content

bsql_core/
util.rs

1//! Shared utility functions used across bsql-core modules.
2
3use crate::error::{BsqlResult, ConnectError};
4
5/// Validate a savepoint name: must be a valid SQL identifier.
6///
7/// Rules:
8/// - Non-empty, at most 63 characters (PG's `NAMEDATALEN - 1`)
9/// - Starts with an ASCII letter or underscore
10/// - Contains only ASCII letters, digits, and underscores
11pub 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}