Skip to main content

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. Returning `false` disables
213    /// redaction for the value AND all of its descendants: the macro fully
214    /// un-redacts the formatter before rendering the value, so every nested
215    /// literal, sub-option, and expression underneath it prints verbatim too,
216    /// not just an immediate scalar.
217    ///
218    /// # Context
219    /// Many statements in MZ use the format `WITH (<options>...)` to modify the
220    /// resulting behavior of the statement. Most often these are modeled in the
221    /// AST as a struct with two fields: an option name and a value.
222    ///
223    /// We do not type check the values of the types until planning, so most
224    /// values represent arbitrary user input. To prevent leaking any PII in
225    /// that data, we default to replacing values with the string `<REDACTED>`.
226    ///
227    /// However, in some cases, the values do not need to be redacted. For our
228    /// `WITH` options, knowing which option we're dealing with should be
229    /// sufficient to understand if a value needs redaction––so this trait
230    /// controls redaction on a per-option basis.
231    ///
232    /// ## Genericizing `WITH` options
233    /// It would be nice to force every AST node we consider a `WITH` option to
234    /// conform to a particular structure––however, we have a proc macro that
235    /// generates visitors over all of our nodes that inhibits our ability to do
236    /// this easily. This means, unfortunately, that we cannot rely on
237    /// compilation guarantees for this and instead must use the honor system.
238    ///
239    /// ## Nothing is ever redacted...
240    ///
241    /// In the initial implementation of this trait, no option requires its
242    /// values to be redacted (except for the one test case). That doesn't mean
243    /// there won't be in the future. When in doubt, take the more conservative
244    /// approach.
245    fn redact_value(&self) -> bool {
246        // We conservatively assume that all values should be redacted.
247        true
248    }
249}
250
251/// To allow `WITH` option AST nodes to be printed without redaction, you should
252/// use this macro's implementation of `AstDisplay`. For more details, consult
253/// the doc strings on the functions used on its implementation.
254macro_rules! impl_display_for_with_option {
255    ($name:ident) => {
256        impl<T: AstInfo> AstDisplay for $name<T> {
257            fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
258                f.write_node(&self.name);
259                if let Some(v) = &self.value {
260                    f.write_str(" = ");
261
262                    // If the formatter is redacted, but the name does not
263                    // require setting the value to be redacted, allow the value
264                    // to be printed without redaction.
265                    if f.redacted() && !self.name.redact_value() {
266                        let mode = f.unredact();
267                        f.write_node(v);
268                        f.set_mode(mode);
269                    } else {
270                        f.write_node(v);
271                    }
272                }
273            }
274        }
275    };
276}
277
278impl<T: AstDisplay> AstDisplay for &Box<T> {
279    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
280        (*self).fmt(f);
281    }
282}
283
284impl<T: AstDisplay> AstDisplay for Box<T> {
285    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
286        (**self).fmt(f);
287    }
288}
289
290// u32 used directly to represent, e.g., oids
291impl AstDisplay for u32 {
292    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
293        f.write_str(self);
294    }
295}
296
297// u64 used directly to represent, e.g., type modifiers
298impl AstDisplay for u64 {
299    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
300        f.write_str(self);
301    }
302}
303
304impl AstDisplay for i64 {
305    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
306        f.write_str(self);
307    }
308}
309
310pub struct EscapeSingleQuoteString<'a>(&'a str);
311
312impl<'a> AstDisplay for EscapeSingleQuoteString<'a> {
313    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
314        for c in self.0.chars() {
315            if c == '\'' {
316                f.write_str("\'\'");
317            } else {
318                f.write_str(c);
319            }
320        }
321    }
322}
323
324impl<'a> fmt::Display for EscapeSingleQuoteString<'a> {
325    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
326        f.write_str(&self.to_ast_string_simple())
327    }
328}
329
330pub fn escape_single_quote_string(s: &str) -> EscapeSingleQuoteString<'_> {
331    EscapeSingleQuoteString(s)
332}
333
334pub struct EscapedStringLiteral<'a>(&'a str);
335
336impl<'a> AstDisplay for EscapedStringLiteral<'a> {
337    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
338        f.write_str("'");
339        f.write_node(&escape_single_quote_string(self.0));
340        f.write_str("'");
341    }
342}
343
344impl<'a> fmt::Display for EscapedStringLiteral<'a> {
345    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
346        f.write_str(&self.to_ast_string_simple())
347    }
348}
349
350pub fn escaped_string_literal(s: &str) -> EscapedStringLiteral<'_> {
351    EscapedStringLiteral(s)
352}