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}