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}