mz_sql_parser/ast/
display.rs

1// Copyright 2020 sqlparser-rs contributors. All rights reserved.
2// Copyright Materialize, Inc. and contributors. All rights reserved.
3//
4// This file is derived from the sqlparser-rs project, available at
5// https://github.com/andygrove/sqlparser-rs. It was incorporated
6// directly into Materialize on December 21, 2019.
7//
8// Licensed under the Apache License, Version 2.0 (the "License");
9// you may not use this file except in compliance with the License.
10// You may obtain a copy of the License in the LICENSE file at the
11// root of this repository, or online at
12//
13//     http://www.apache.org/licenses/LICENSE-2.0
14//
15// Unless required by applicable law or agreed to in writing, software
16// distributed under the License is distributed on an "AS IS" BASIS,
17// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18// See the License for the specific language governing permissions and
19// limitations under the License.
20
21use std::fmt;
22
23pub struct DisplaySeparated<'a, T>
24where
25    T: AstDisplay,
26{
27    slice: &'a [T],
28    sep: &'static str,
29}
30
31impl<'a, T> AstDisplay for DisplaySeparated<'a, T>
32where
33    T: AstDisplay,
34{
35    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
36        let mut delim = "";
37        for t in self.slice {
38            f.write_str(delim);
39            delim = self.sep;
40            t.fmt(f);
41        }
42    }
43}
44
45impl<'a, T> std::fmt::Display for DisplaySeparated<'a, T>
46where
47    T: AstDisplay,
48{
49    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
50        AstFormatter::new(f, FormatMode::Simple).write_node(self);
51        Ok(())
52    }
53}
54
55pub fn separated<'a, T>(slice: &'a [T], sep: &'static str) -> DisplaySeparated<'a, T>
56where
57    T: AstDisplay,
58{
59    DisplaySeparated { slice, sep }
60}
61
62pub fn comma_separated<T>(slice: &[T]) -> DisplaySeparated<'_, T>
63where
64    T: AstDisplay,
65{
66    DisplaySeparated { slice, sep: ", " }
67}
68
69/// Describes the context in which to print an AST.
70///
71/// TODO: Currently, only the simple format can be redacted, but, ideally, whether it's redacted and
72///       whether it's stable would be orthogonal settings.
73#[derive(Debug, Copy, Clone, PartialEq, Eq)]
74pub enum FormatMode {
75    /// Simple is the normal way of printing for human consumption. Identifiers are quoted only if
76    /// necessary and sensitive information is not redacted.
77    Simple,
78    /// SimpleRedacted is like Simple, but strips out literals, e.g. strings and numbers.
79    /// This makes SQL queries be "usage data", rather than "customer data" according to our
80    /// data management policy, allowing us to introspect it.
81    SimpleRedacted,
82    /// Stable prints out the AST in a form more suitable for persistence. All identifiers are
83    /// quoted, even if not necessary. This mode is used when persisting table information to the
84    /// catalog.
85    Stable,
86}
87
88#[derive(Debug)]
89pub struct AstFormatter<W> {
90    buf: W,
91    mode: FormatMode,
92}
93
94impl<W> AstFormatter<W>
95where
96    W: fmt::Write,
97{
98    pub fn write_node<T: AstDisplay>(&mut self, s: &T) {
99        s.fmt(self);
100    }
101
102    // TODO(justin): make this only accept a &str so that we don't accidentally pass an AstDisplay
103    // to it.
104    pub fn write_str<T: fmt::Display>(&mut self, s: T) {
105        write!(self.buf, "{}", s).expect("unexpected error in fmt::Display implementation");
106    }
107
108    // Whether the AST should be optimized for persistence.
109    pub fn stable(&self) -> bool {
110        self.mode == FormatMode::Stable
111    }
112
113    /// Whether the AST should be printed out in a more human readable format.
114    pub fn simple(&self) -> bool {
115        matches!(self.mode, FormatMode::Simple | FormatMode::SimpleRedacted)
116    }
117
118    /// Whether the AST should be printed in redacted form
119    pub fn redacted(&self) -> bool {
120        self.mode == FormatMode::SimpleRedacted
121    }
122
123    /// Sets the current mode to a compatible version that does not redact
124    /// values; returns the current mode, which should be reset when the
125    /// unredacted printing is complete using [`Self::set_mode`].
126    ///
127    /// Note that this is the simplest means of unredacting values opt-out
128    /// rather than opt-in. We must monitor usage of this API carefully to
129    /// ensure we don't end up leaking values.
130    pub fn unredact(&mut self) -> FormatMode {
131        match self.mode {
132            FormatMode::Simple => FormatMode::Simple,
133            FormatMode::SimpleRedacted => {
134                self.mode = FormatMode::Simple;
135                FormatMode::SimpleRedacted
136            }
137            FormatMode::Stable => FormatMode::Stable,
138        }
139    }
140
141    pub fn set_mode(&mut self, mode: FormatMode) {
142        self.mode = mode;
143    }
144
145    pub fn new(buf: W, mode: FormatMode) -> Self {
146        AstFormatter { buf, mode }
147    }
148}
149
150// AstDisplay is an alternative to fmt::Display to be used for formatting ASTs. It permits
151// configuration global to a printing of a given AST.
152pub trait AstDisplay {
153    fn fmt<W>(&self, f: &mut AstFormatter<W>)
154    where
155        W: fmt::Write;
156
157    fn to_ast_string_simple(&self) -> String {
158        self.to_ast_string(FormatMode::Simple)
159    }
160
161    fn to_ast_string_stable(&self) -> String {
162        self.to_ast_string(FormatMode::Stable)
163    }
164
165    fn to_ast_string_redacted(&self) -> String {
166        self.to_ast_string(FormatMode::SimpleRedacted)
167    }
168
169    fn to_ast_string(&self, format_mode: FormatMode) -> String {
170        let mut buf = String::new();
171        let mut f = AstFormatter::new(&mut buf, format_mode);
172        self.fmt(&mut f);
173        buf
174    }
175}
176
177// Derive a fmt::Display implementation for types implementing AstDisplay.
178#[macro_export]
179macro_rules! impl_display {
180    ($name:ident) => {
181        impl std::fmt::Display for $name {
182            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
183                use $crate::ast::display::{AstFormatter, FormatMode};
184                AstFormatter::new(f, FormatMode::Simple).write_node(self);
185                Ok(())
186            }
187        }
188    };
189}
190
191macro_rules! impl_display_t {
192    ($name:ident) => {
193        impl<T: AstInfo> std::fmt::Display for $name<T> {
194            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
195                use crate::ast::display::{AstFormatter, FormatMode};
196                AstFormatter::new(f, FormatMode::Simple).write_node(self);
197                Ok(())
198            }
199        }
200    };
201}
202
203/// Functions that generalize to AST nodes representing the "name" of a `WITH`
204/// option.
205pub trait WithOptionName {
206    /// Expresses whether or not values should be redacted based on the option
207    /// name (i.e. the option's "key").
208    ///
209    /// # WARNING
210    ///
211    /// Whenever implementing this trait consider very carefully whether or not
212    /// this value could contain sensitive user data.
213    ///
214    /// # Context
215    /// Many statements in MZ use the format `WITH (<options>...)` to modify the
216    /// resulting behavior of the statement. Most often these are modeled in the
217    /// AST as a struct with two fields: an option name and a value.
218    ///
219    /// We do not type check the values of the types until planning, so most
220    /// values represent arbitrary user input. To prevent leaking any PII in
221    /// that data, we default to replacing values with the string `<REDACTED>`.
222    ///
223    /// However, in some cases, the values do not need to be redacted. For our
224    /// `WITH` options, knowing which option we're dealing with should be
225    /// sufficient to understand if a value needs redaction––so this trait
226    /// controls redaction on a per-option basis.
227    ///
228    /// ## Genericizing `WITH` options
229    /// It would be nice to force every AST node we consider a `WITH` option to
230    /// conform to a particular structure––however, we have a proc macro that
231    /// generates visitors over all of our nodes that inhibits our ability to do
232    /// this easily. This means, unfortunately, that we cannot rely on
233    /// compilation guarantees for this and instead must use the honor system.
234    ///
235    /// ## Nothing is ever redacted...
236    ///
237    /// In the initial implementation of this trait, no option requires its
238    /// values to be redacted (except for the one test case). That doesn't mean
239    /// there won't be in the future. When in doubt, take the more conservative
240    /// approach.
241    fn redact_value(&self) -> bool {
242        // We conservatively assume that all values should be redacted.
243        true
244    }
245}
246
247/// To allow `WITH` option AST nodes to be printed without redaction, you should
248/// use this macro's implementation of `AstDisplay`. For more details, consult
249/// the doc strings on the functions used on its implementation.
250macro_rules! impl_display_for_with_option {
251    ($name:ident) => {
252        impl<T: AstInfo> AstDisplay for $name<T> {
253            fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
254                f.write_node(&self.name);
255                if let Some(v) = &self.value {
256                    f.write_str(" = ");
257
258                    // If the formatter is redacted, but the name does not
259                    // require setting the value to be redacted, allow the value
260                    // to be printed without redaction.
261                    if f.redacted() && !self.name.redact_value() {
262                        let mode = f.unredact();
263                        f.write_node(v);
264                        f.set_mode(mode);
265                    } else {
266                        f.write_node(v);
267                    }
268                }
269            }
270        }
271    };
272}
273
274impl<T: AstDisplay> AstDisplay for &Box<T> {
275    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
276        (*self).fmt(f);
277    }
278}
279
280impl<T: AstDisplay> AstDisplay for Box<T> {
281    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
282        (**self).fmt(f);
283    }
284}
285
286// u32 used directly to represent, e.g., oids
287impl AstDisplay for u32 {
288    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
289        f.write_str(self);
290    }
291}
292
293// u64 used directly to represent, e.g., type modifiers
294impl AstDisplay for u64 {
295    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
296        f.write_str(self);
297    }
298}
299
300impl AstDisplay for i64 {
301    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
302        f.write_str(self);
303    }
304}
305
306pub struct EscapeSingleQuoteString<'a>(&'a str);
307
308impl<'a> AstDisplay for EscapeSingleQuoteString<'a> {
309    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
310        for c in self.0.chars() {
311            if c == '\'' {
312                f.write_str("\'\'");
313            } else {
314                f.write_str(c);
315            }
316        }
317    }
318}
319
320impl<'a> fmt::Display for EscapeSingleQuoteString<'a> {
321    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
322        f.write_str(&self.to_ast_string_simple())
323    }
324}
325
326pub fn escape_single_quote_string(s: &str) -> EscapeSingleQuoteString<'_> {
327    EscapeSingleQuoteString(s)
328}
329
330pub struct EscapedStringLiteral<'a>(&'a str);
331
332impl<'a> AstDisplay for EscapedStringLiteral<'a> {
333    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
334        f.write_str("'");
335        f.write_node(&escape_single_quote_string(self.0));
336        f.write_str("'");
337    }
338}
339
340impl<'a> fmt::Display for EscapedStringLiteral<'a> {
341    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
342        f.write_str(&self.to_ast_string_simple())
343    }
344}
345
346pub fn escaped_string_literal(s: &str) -> EscapedStringLiteral<'_> {
347    EscapedStringLiteral(s)
348}