Skip to main content

uqa_sql/semantics/parameters/
custom.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Custom parameters: the names a session may set without a definition, which `PostgreSQL` keeps as string placeholders (`assignable_custom_variable_name`).
8
9use crate::SQLError;
10
11/// Whether `name` is two or more simple identifiers separated by dots (`valid_custom_variable_name`). A component starts with a letter, an underscore or a byte of a multibyte character, and continues with those, digits and `$`.
12pub fn valid_custom_name(name: &str) -> bool {
13    let mut saw_separator = false;
14    let mut component_start = true;
15    for byte in name.bytes() {
16        if byte == b'.' {
17            if component_start {
18                return false;
19            }
20            saw_separator = true;
21            component_start = true;
22        } else if byte.is_ascii_alphabetic() || byte == b'_' || byte >= 0x80 {
23            component_start = false;
24        } else if component_start || !(byte.is_ascii_digit() || byte == b'$') {
25            return false;
26        }
27    }
28    !component_start && saw_separator
29}
30
31/// Check that a session may create a placeholder for `name`, which no definition names: a valid custom name outside every prefix that a loaded library reserves.
32pub fn check_assignable_custom_name<'a>(
33    name: &str,
34    reserved_prefixes: impl IntoIterator<Item = &'a str>,
35) -> Result<(), SQLError> {
36    let Some((prefix, _)) = name.split_once('.') else {
37        return Err(unrecognized_parameter(name));
38    };
39    if !valid_custom_name(name) {
40        return Err(SQLError::Diagnostic {
41            sqlstate: "42602".into(),
42            message: format!("invalid configuration parameter name \"{name}\""),
43            detail: Some(
44                "Custom parameter names must be two or more simple identifiers separated by dots."
45                    .into(),
46            ),
47            hint: None,
48        });
49    }
50    if let Some(reserved) = reserved_prefixes
51        .into_iter()
52        .find(|reserved| *reserved == prefix)
53    {
54        return Err(SQLError::Diagnostic {
55            sqlstate: "42602".into(),
56            message: format!("invalid configuration parameter name \"{name}\""),
57            detail: Some(format!("\"{reserved}\" is a reserved prefix.")),
58            hint: None,
59        });
60    }
61    Ok(())
62}
63
64/// The warning that removes a placeholder whose prefix a library reserves as it loads (`MarkGUCPrefixReserved`).
65pub fn reserved_placeholder_warning(name: &str, prefix: &str) -> (String, String) {
66    (
67        format!("invalid configuration parameter name \"{name}\", removing it"),
68        format!("\"{prefix}\" is now a reserved prefix."),
69    )
70}
71
72/// `unrecognized configuration parameter "name"`, which `SHOW`, `RESET` of a single-part name and `current_setting` report for a name nothing defines.
73pub fn unrecognized_parameter(name: &str) -> SQLError {
74    SQLError::Routine {
75        sqlstate: "42704".into(),
76        message: format!("unrecognized configuration parameter \"{name}\""),
77    }
78}
79
80#[cfg(test)]
81mod tests;