mz_sql_parser/
lib.rs

1// Copyright 2018 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
21//! SQL parser.
22//!
23//! This crate provides an SQL lexer and parser for Materialize's dialect of
24//! SQL.
25//!
26//! ```
27//! use mz_sql_parser::parser;
28//!
29//! let sql = "SELECT a, b, 123, myfunc(b) \
30//!            FROM table_1 \
31//!            WHERE a > b AND b < 100 \
32//!            ORDER BY a DESC, b";
33//!
34//! let ast = parser::parse_statements(sql).unwrap();
35//! println!("AST: {:?}", ast);
36//! ```
37
38pub mod ast;
39pub mod parser;
40
41mod ident;
42
43#[cfg(feature = "test")]
44pub fn datadriven_testcase(tc: &datadriven::TestCase) -> String {
45    use crate::ast::display::AstDisplay;
46    use crate::ast::{Expr, Statement};
47    use datadriven::TestCase;
48    use mz_ore::collections::CollectionExt;
49    use mz_ore::fmt::FormatBuffer;
50    use unicode_width::UnicodeWidthStr;
51
52    fn render_error(sql: &str, e: parser::ParserError) -> String {
53        let mut s = format!("error: {}\n", e.message);
54
55        // Do our best to emulate psql in rendering a caret pointing at the
56        // offending character in the query. This makes it possible to detect
57        // incorrect error positions by visually scanning the test files.
58        let end = sql.len();
59        let line_start = sql[..e.pos].rfind('\n').map(|p| p + 1).unwrap_or(0);
60        let line_end = sql[e.pos..].find('\n').map(|p| e.pos + p).unwrap_or(end);
61        writeln!(s, "{}", &sql[line_start..line_end]);
62        for _ in 0..sql[line_start..e.pos].width() {
63            write!(s, " ");
64        }
65        writeln!(s, "^");
66
67        s
68    }
69
70    fn parse_statement(tc: &TestCase) -> String {
71        let input = tc.input.strip_suffix('\n').unwrap_or(&tc.input);
72        match parser::parse_statements(input) {
73            Ok(s) => {
74                let stmt = s.into_element().ast;
75                for printed in [stmt.to_ast_string_simple(), stmt.to_ast_string_stable()] {
76                    let mut parsed = match parser::parse_statements(&printed) {
77                        Ok(parsed) => parsed.into_element().ast,
78                        Err(err) => panic!("reparse failed: {}: {}\n", stmt, err),
79                    };
80                    match (&mut parsed, &stmt) {
81                        // DECLARE remembers the original SQL. Erase that here so it can differ if
82                        // needed (for example, quoting identifiers vs not). This is ok because we
83                        // still compare that the resulting ASTs are identical, and it's valid for
84                        // those to come from different original strings.
85                        (Statement::Declare(parsed), Statement::Declare(stmt)) => {
86                            parsed.sql.clone_from(&stmt.sql);
87                        }
88                        _ => {}
89                    }
90                    if parsed != stmt {
91                        panic!(
92                            "reparse comparison failed:\n{:?}\n!=\n{:?}\n{printed}\n",
93                            stmt, parsed
94                        );
95                    }
96                }
97
98                // Also check that the redacted version of the statement can be reparsed. This is
99                // important so that we are still able to pretty-print redacted statements, which
100                // helps during debugging.
101                let redacted = stmt.to_ast_string_redacted();
102                let res = parser::parse_statements(&redacted);
103                assert!(
104                    res.is_ok(),
105                    "redacted statement could not be reparsed: {res:?}\noriginal:\n{stmt}\nredacted:\n{redacted}"
106                );
107
108                if tc.args.contains_key("roundtrip") {
109                    format!("{}\n", stmt)
110                } else {
111                    // TODO(justin): it would be nice to have a middle-ground between this
112                    // all-on-one-line and {:#?}'s huge number of lines.
113                    format!("{}\n=>\n{:?}\n", stmt, stmt)
114                }
115            }
116            Err(e) => render_error(input, e.error),
117        }
118    }
119
120    fn parse_scalar(tc: &TestCase) -> String {
121        let input = tc.input.trim();
122        match parser::parse_expr(input) {
123            Ok(s) => {
124                for printed in [s.to_ast_string_simple(), s.to_ast_string_stable()] {
125                    match parser::parse_expr(&printed) {
126                        Ok(parsed) => {
127                            // TODO: We always coerce the double colon operator into a Cast expr instead
128                            // of keeping it as an Op (see parse_pg_cast). Expr::Cast always prints
129                            // itself as double colon. We're thus unable to perfectly roundtrip
130                            // `CAST(..)`. We could fix this by keeping "::" as a binary operator and
131                            // teaching func.rs how to handle it, similar to how that file handles "~~"
132                            // (without the parser converting that operator directly into an
133                            // Expr::Like).
134                            if !matches!(parsed, Expr::Cast { .. }) {
135                                if parsed != s {
136                                    panic!(
137                                        "reparse comparison failed: {input} != {s}\n{:?}\n!=\n{:?}\n{printed}\n",
138                                        s, parsed
139                                    );
140                                }
141                            }
142                        }
143                        Err(err) => panic!("reparse failed: {printed}: {err}\n{s:?}"),
144                    }
145                }
146
147                if tc.args.contains_key("roundtrip") {
148                    format!("{}\n", s)
149                } else {
150                    // TODO(justin): it would be nice to have a middle-ground between this
151                    // all-on-one-line and {:#?}'s huge number of lines.
152                    format!("{:?}\n", s)
153                }
154            }
155            Err(e) => render_error(input, e),
156        }
157    }
158
159    match tc.directive.as_str() {
160        "parse-statement" => parse_statement(tc),
161        "parse-scalar" => parse_scalar(tc),
162        dir => panic!("unhandled directive {}", dir),
163    }
164}