Skip to main content

uqa_sql/expr/scalar_helpers/
quoting.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! SQL quoting scans borrowed input and admits escaped output through the shared string owner.
8
9use uqa_core::{
10    memory::{Produced, ProductionControl, ProductionString},
11    ValueRetentionError,
12};
13
14/// Catalog definitions follow the current string setting and never emit an `E` prefix (`simple_quote_literal`).
15pub(crate) fn append_sql_literal(output: &mut String, value: &str, standard_strings: bool) {
16    output.push('\'');
17    for character in value.chars() {
18        if character == '\'' || (character == '\\' && !standard_strings) {
19            output.push(character);
20        }
21        output.push(character);
22    }
23    output.push('\'');
24}
25
26/// Double-quote unless the identifier starts with a lowercase letter or an underscore, holds only lowercase letters, digits and underscores, and is not a keyword other than an unreserved one (`quote_identifier`).
27pub fn quote_ident(ident: &str) -> String {
28    quote_ident_with_control(ident, &ProductionControl::uncontrolled())
29        .expect("ordinary identifier quoting")
30        .into_uncontrolled()
31        .expect("ordinary quoted identifier")
32}
33
34pub(crate) fn quote_ident_with_control(
35    ident: &str,
36    control: &ProductionControl<'_>,
37) -> Result<Produced<String>, ValueRetentionError> {
38    control.check()?;
39    let mut safe = !ident.is_empty();
40    for (index, character) in ident.chars().enumerate() {
41        control.check()?;
42        safe &= character.is_ascii_lowercase()
43            || character == '_'
44            || (index > 0 && character.is_ascii_digit());
45    }
46    if safe && !super::is_quoted_keyword(ident) {
47        return control.copy_text(ident);
48    }
49    let mut output = ProductionString::new(*control);
50    output.push('"')?;
51    for character in ident.chars() {
52        if character == '"' {
53            output.push('"')?;
54        }
55        output.push(character)?;
56    }
57    output.push('"')?;
58    output.finish()
59}
60
61/// Single-quote with doubled quotes; backslashes select the escaped-literal form.
62pub(in crate::expr) fn quote_literal_with_control(
63    text: &str,
64    control: &ProductionControl<'_>,
65) -> Result<Produced<String>, ValueRetentionError> {
66    control.check()?;
67    let mut escape = false;
68    for character in text.chars() {
69        control.check()?;
70        escape |= character == '\\';
71    }
72    let mut output = ProductionString::new(*control);
73    if escape {
74        output.push('E')?;
75    }
76    output.push('\'')?;
77    for character in text.chars() {
78        if matches!(character, '\'' | '\\') {
79            output.push(character)?;
80        }
81        output.push(character)?;
82    }
83    output.push('\'')?;
84    output.finish()
85}