risingwave_common/util/
quote_ident.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt::Display;
16
17/// A wrapper that returns the given string suitably quoted to be used as an identifier in an SQL
18/// statement string in its `Display` implementation.
19/// Quotes are added only if necessary (i.e., if the string contains non-identifier characters or
20/// would be case-folded). Embedded quotes are properly doubled.
21///
22/// Refer to <https://github.com/postgres/postgres/blob/90189eefc1e11822794e3386d9bafafd3ba3a6e8/src/backend/utils/adt/ruleutils.c#L11506>
23pub struct QuoteIdent<'a>(pub &'a str);
24
25impl Display for QuoteIdent<'_> {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        let needs_quotes = self
28            .0
29            .chars()
30            .any(|c| !matches!(c, 'a'..='z' | '0'..='9' | '_'));
31
32        if !needs_quotes {
33            self.0.fmt(f)?;
34        } else {
35            write!(f, "\"")?;
36            for c in self.0.chars() {
37                if c == '"' {
38                    write!(f, "\"\"")?;
39                } else {
40                    write!(f, "{c}")?;
41                }
42            }
43            write!(f, "\"")?;
44        }
45
46        Ok(())
47    }
48}