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}