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}