rudb/syntax.rs
1//! Questions about a statement that do not need a database to answer.
2//!
3//! Everything here works on text alone. There is no catalog, so no name is resolved and no type is
4//! decided, and that is the point: a harness sorting a corpus, a shell deciding whether to keep
5//! reading, and a tool counting what the grammar accepts all need answers before they have anywhere
6//! to run the statement, and none of them should have to depend on `rudb-parse` to get them.
7//!
8//! That last part is the whole reason this module exists. `spec/13-client-api.md` says a program
9//! embedding rudb depends on this crate and nothing else, and `rudb-compat` reaching into
10//! `rudb-parse` for a tokenizer was the counterexample. Every reach it had is answered here.
11
12use rudb_common::{Error, Result};
13use rudb_parse::ast::Statement as Parsed;
14use rudb_parse::{parse, parse_ast};
15
16/// Whether the rows a statement produces come back in an order it asked for.
17///
18/// Three answers rather than a boolean, because "we could not tell" is a different thing from "it
19/// did not ask" and a caller that folds them together makes the wrong mistake somewhere. A
20/// differential harness comparing two engines wants to sort both sides when the order is
21/// unspecified and to compare as written when it is declared, and for the third case it has to
22/// pick, which is a policy decision that belongs to the harness and not here.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum RowOrder {
25 /// The statement has a top level `ORDER BY`, so the order is part of the answer.
26 Declared,
27 /// It parses, it has no top level `ORDER BY`, and so any order is a correct one.
28 Unspecified,
29 /// It does not parse here, so there is nothing to read the answer off.
30 Unknown,
31}
32
33/// How the rows of a statement are ordered, as far as the text says.
34///
35/// Top level only. An `ORDER BY` inside a subquery does not count, because it does not survive into
36/// the outer result and a caller that treated it as an order would be comparing against an order
37/// nothing promised. `ORDER BY ALL` counts, since it is an order clause spelled shorter.
38///
39/// A statement that returns no rows is [`RowOrder::Unspecified`], which is true in the only sense
40/// that matters: there is no order to preserve.
41#[must_use]
42pub fn row_order(sql: &str) -> RowOrder {
43 let Ok(ast) = parse_ast(sql) else {
44 return RowOrder::Unknown;
45 };
46 let declared = ast.statements.iter().any(|statement| match statement {
47 Parsed::Query(at) => {
48 let query = ast.query(*at);
49 query.order_by_all || !query.order_by.is_empty()
50 }
51 // Nothing else returns rows, so nothing else has an order to preserve.
52 _ => false,
53 });
54 if declared { RowOrder::Declared } else { RowOrder::Unspecified }
55}
56
57/// Whether the grammar accepts this statement.
58///
59/// The grammar and nothing above it. A statement this accepts may still fail to run, because the
60/// names in it may not exist and the types may not work out, and a statement this accepts may not
61/// even build an AST yet, because the grammar is vendored whole from DuckDB while the AST is built
62/// one statement at a time.
63///
64/// That gap is the reason this is a separate call from [`crate::Database::prepare`] rather than
65/// something a caller infers from one. A conformance harness asking which of DuckDB's dialect we
66/// accept is asking about the grammar, and answering with the AST instead would report a hole in
67/// the dialect wherever there is a hole in the AST, which is a different and much larger number.
68///
69/// # Errors
70///
71/// A tokenizer error or a parse error, carrying the position in the text.
72pub fn accepts(sql: &str) -> Result<()> {
73 parse(sql).map(|_| ())
74}
75
76/// Whether the grammar accepts this statement, as a plain yes or no.
77///
78/// For a caller counting how much of a corpus parses, where the message is not read.
79#[must_use]
80pub fn parses(sql: &str) -> bool {
81 accepts(sql).is_ok()
82}
83
84/// The text split into statements, with text that does not tokenize handed back whole.
85///
86/// [`crate::statements`] is the same split and refuses text it cannot tokenize, which is the right
87/// answer for a shell, since the commonest reason is a string the user has not closed. It is the
88/// wrong answer for a harness reading a file of other people's SQL, whose job is to hand each
89/// statement to both engines and see what they say rather than to decide in advance that something
90/// is not SQL. This is that second reading.
91///
92/// # Errors
93///
94/// Never. It returns a `Result` so that a caller can use it where [`crate::statements`] would go,
95/// and so that a later reason to refuse has somewhere to live.
96pub fn split(text: &str) -> Result<Vec<String>> {
97 let Ok(found) = crate::statements(text) else {
98 let trimmed = text.trim();
99 return Ok(if trimmed.is_empty() { Vec::new() } else { vec![trimmed.to_string()] });
100 };
101 Ok(found.into_iter().map(|statement| statement.sql().to_string()).collect())
102}
103
104/// Every kind of statement the grammar has, in the order the matcher tries them.
105///
106/// The thirty six alternatives of the `Statement` rule, by their grammar names, which is the
107/// denominator of the statement coverage number in `spec/sql/duckdb/01-what-compatible-means.md`
108/// section 1.1. They come off the vendored rule table rather than out of a list written here, so the
109/// day upstream adds a statement the denominator grows by one on its own.
110///
111/// The names are the grammar's, `SelectStatement` rather than `SELECT`, because that is what a
112/// reader can go and look up. The last one, `ExpressionStatement`, is the alternative that makes a
113/// bare expression a statement, and it is one of the thirty six rather than a special case.
114#[must_use]
115pub fn statement_kinds() -> Vec<&'static str> {
116 rudb_parse::alternatives("Statement")
117}
118
119/// Which kind of statement a piece of text is, by the name of the alternative that matched.
120///
121/// This is the other half of [`statement_kinds`] and the reason that one exists. A harness counting
122/// how much of the statement surface works needs each record in its corpus sorted into one of the
123/// thirty six buckets, and the only thing that can do that correctly is the parser, because
124/// `WITH x AS (...) INSERT INTO t SELECT * FROM x` starts with the word `WITH` and is an
125/// `InsertStatement`.
126///
127/// The first statement in the text, so a caller holding a script should run [`split`] over it first
128/// and ask about each piece. That is what a harness does anyway, since it has to hand them to the
129/// engine one at a time.
130///
131/// `None` when the text does not parse, and `None` for text that parses into no statement at all,
132/// which is a file of comments. Neither is an error worth a message: the caller asking this question
133/// is sorting a corpus and has somewhere to put what it could not read.
134#[must_use]
135pub fn statement_kind(sql: &str) -> Option<&'static str> {
136 let tree = parse(sql).ok()?;
137 // A `TopLevelStatement` is `Statement? (';'+ / EndOfInput)`, so a leading semicolon and a
138 // trailing one each produce one that holds no statement. Walk past those rather than reading the
139 // first one and calling the answer nothing.
140 tree.children(tree.root())
141 .filter_map(|top| tree.children(top).find(|&at| tree.name(at) == STATEMENT))
142 .find_map(|statement| tree.children(statement).next().map(|at| tree.name(at)))
143}
144
145/// The rule whose alternatives are the statement kinds.
146const STATEMENT: &str = "Statement";
147
148/// Where in the text an error is about, as a line and a column, both counting from one.
149///
150/// Byte offsets are what the parser carries, because that is what slicing wants, and a line and a
151/// column are what a person reads. Doing the conversion here rather than in each caller means the
152/// three of them agree about tabs and about what happens at the very end of the text.
153///
154/// The column counts characters rather than bytes, so a multi byte character is one column, which
155/// is what an editor shows. A position past the end of the text is the position just after the last
156/// character, since an error about a statement that ended too early is about the end.
157#[must_use]
158pub fn line_and_column(text: &str, offset: usize) -> (usize, usize) {
159 let upto = &text[..offset.min(text.len())];
160 let line = upto.bytes().filter(|&b| b == b'\n').count() + 1;
161 let column = upto.rsplit('\n').next().unwrap_or("").chars().count() + 1;
162 (line, column)
163}
164
165/// The span of an error, as a line and a column into the statement it came from.
166///
167/// `None` when the error does not say where, which is most of the errors that are not about syntax.
168#[must_use]
169pub fn where_it_happened(sql: &str, error: &Error) -> Option<(usize, usize)> {
170 error.span().map(|span| line_and_column(sql, span.start as usize))
171}
172
173#[cfg(test)]
174mod tests {
175 use rudb_common::Error;
176
177 use super::{
178 RowOrder, accepts, line_and_column, parses, row_order, split, statement_kind,
179 statement_kinds, where_it_happened,
180 };
181
182 #[test]
183 fn the_statement_surface_is_thirty_six_kinds_wide() {
184 let kinds = statement_kinds();
185 assert_eq!(kinds.len(), 36);
186 assert!(kinds.contains(&"SelectStatement"));
187 assert!(kinds.contains(&"MergeIntoStatement"));
188 assert!(kinds.contains(&"ExpressionStatement"));
189 }
190
191 #[test]
192 fn a_statement_says_which_of_the_thirty_six_it_is() {
193 assert_eq!(statement_kind("SELECT 1"), Some("SelectStatement"));
194 assert_eq!(statement_kind("CREATE TABLE t (x INTEGER)"), Some("CreateStatement"));
195 assert_eq!(statement_kind("INSERT INTO t VALUES (1)"), Some("InsertStatement"));
196 assert_eq!(statement_kind("SET memory_limit = '1GB'"), Some("SetStatement"));
197 assert_eq!(statement_kind("EXPLAIN SELECT 1"), Some("ExplainStatement"));
198 }
199
200 #[test]
201 fn every_kind_a_statement_reports_is_one_of_the_kinds_there_are() {
202 let kinds = statement_kinds();
203 for sql in [
204 "SELECT 1",
205 "CREATE TABLE t (x INTEGER)",
206 "DROP TABLE t",
207 "UPDATE t SET x = 1",
208 "DELETE FROM t",
209 "COPY t TO 'out.csv'",
210 "ATTACH 'other.db'",
211 "PRAGMA version",
212 "BEGIN",
213 "MERGE INTO t USING s ON t.x = s.x WHEN MATCHED THEN DELETE",
214 ] {
215 let kind = statement_kind(sql).unwrap_or_else(|| panic!("{sql} parses"));
216 assert!(kinds.contains(&kind), "{kind} is not one of the thirty six");
217 }
218 }
219
220 #[test]
221 fn what_a_statement_starts_with_is_not_what_it_is() {
222 // The word is `WITH` and the statement is an insert, which is the whole reason this asks the
223 // parser instead of looking at the first token.
224 assert_eq!(
225 statement_kind("WITH x AS (SELECT 1 AS a) INSERT INTO t SELECT a FROM x"),
226 Some("InsertStatement")
227 );
228 assert_eq!(statement_kind("WITH x AS (SELECT 1) SELECT * FROM x"), Some("SelectStatement"));
229 }
230
231 #[test]
232 fn text_with_no_statement_in_it_is_no_kind_rather_than_the_first_kind() {
233 assert_eq!(statement_kind("-- nothing but a comment\n"), None);
234 assert_eq!(statement_kind(" "), None);
235 assert_eq!(statement_kind("SELECT FROM WHERE"), None);
236 }
237
238 #[test]
239 fn a_semicolon_in_front_of_a_statement_does_not_hide_it() {
240 assert_eq!(statement_kind(";SELECT 1"), Some("SelectStatement"));
241 assert_eq!(statement_kind("SELECT 1;"), Some("SelectStatement"));
242 }
243
244 #[test]
245 fn a_script_reports_the_first_statement_and_a_caller_wanting_all_of_them_splits_first() {
246 assert_eq!(statement_kind("SELECT 1; DROP TABLE t"), Some("SelectStatement"));
247 let each: Vec<_> = split("SELECT 1; DROP TABLE t")
248 .unwrap()
249 .iter()
250 .map(|one| statement_kind(one))
251 .collect();
252 assert_eq!(each, vec![Some("SelectStatement"), Some("DropStatement")]);
253 }
254
255 #[test]
256 fn a_top_level_order_by_is_a_declared_order() {
257 assert_eq!(row_order("SELECT x FROM t ORDER BY x"), RowOrder::Declared);
258 assert_eq!(row_order("SELECT * FROM t ORDER BY ALL"), RowOrder::Declared);
259 }
260
261 #[test]
262 fn a_query_with_no_order_by_promises_nothing_about_the_order() {
263 assert_eq!(row_order("SELECT x FROM t"), RowOrder::Unspecified);
264 assert_eq!(row_order("SELECT 1"), RowOrder::Unspecified);
265 }
266
267 #[test]
268 fn an_order_by_inside_a_subquery_does_not_survive_into_the_outer_result() {
269 // So it is not an order the outer query promised, and a caller comparing against it would
270 // be comparing against something nothing said.
271 assert_eq!(
272 row_order("SELECT x FROM (SELECT x FROM t ORDER BY x) AS inner_query"),
273 RowOrder::Unspecified
274 );
275 }
276
277 #[test]
278 fn a_statement_that_returns_no_rows_has_no_order_to_preserve() {
279 assert_eq!(row_order("CREATE TABLE t (x INTEGER)"), RowOrder::Unspecified);
280 assert_eq!(row_order("INSERT INTO t VALUES (1)"), RowOrder::Unspecified);
281 }
282
283 #[test]
284 fn text_that_does_not_parse_here_says_it_does_not_know() {
285 assert_eq!(row_order("SELECT FROM WHERE"), RowOrder::Unknown);
286 assert_eq!(row_order("this is not sql at all"), RowOrder::Unknown);
287 }
288
289 #[test]
290 fn the_grammar_accepts_more_than_the_ast_builds() {
291 // `MERGE INTO` is in the vendored grammar and there is no AST for it, and that gap is
292 // exactly why acceptance is a separate question from preparing a statement.
293 assert!(parses("SELECT x FROM t"));
294 assert!(parses("MERGE INTO t USING s ON t.x = s.x WHEN MATCHED THEN DELETE"));
295 assert_eq!(
296 row_order("MERGE INTO t USING s ON t.x = s.x WHEN MATCHED THEN DELETE"),
297 RowOrder::Unknown
298 );
299 }
300
301 #[test]
302 fn something_that_is_not_sql_is_rejected_with_a_parser_error() {
303 let error = accepts("SELECT FROM WHERE").expect_err("that is not valid SQL");
304 assert_eq!(error.code().duckdb_name(), "Parser Error");
305 assert!(!parses("SELECT FROM WHERE"));
306 }
307
308 #[test]
309 fn a_file_splits_into_its_statements() {
310 assert_eq!(split("SELECT 1; SELECT 2;").unwrap(), vec!["SELECT 1", "SELECT 2"]);
311 assert_eq!(split("SELECT ';'").unwrap(), vec!["SELECT ';'"]);
312 assert!(split("-- nothing but a comment\n").unwrap().is_empty());
313 assert!(split(" ").unwrap().is_empty());
314 }
315
316 #[test]
317 fn text_that_does_not_tokenize_comes_back_whole_rather_than_being_refused() {
318 // A harness reading somebody else's SQL hands it to both engines and reports what they say.
319 // Deciding here that it is not SQL is the harness answering its own question.
320 assert_eq!(split("SELECT 'unclosed").unwrap(), vec!["SELECT 'unclosed"]);
321 }
322
323 #[test]
324 fn a_byte_offset_becomes_the_line_and_column_a_person_reads() {
325 let text = "SELECT 1\nFROM t\nWHERE x";
326 assert_eq!(line_and_column(text, 0), (1, 1));
327 assert_eq!(line_and_column(text, 7), (1, 8));
328 assert_eq!(line_and_column(text, 9), (2, 1));
329 assert_eq!(line_and_column(text, 16), (3, 1));
330 // Past the end is the position just after the last character, because an error about a
331 // statement that ended too early is about the end.
332 assert_eq!(line_and_column(text, 9_999), (3, 8));
333 }
334
335 #[test]
336 fn a_column_counts_characters_rather_than_bytes() {
337 // Four bytes, one character, so the next column is two and not five.
338 assert_eq!(line_and_column("\u{1f600}x", 4), (1, 2));
339 }
340
341 #[test]
342 fn an_error_with_no_span_says_nothing_about_where() {
343 assert_eq!(where_it_happened("SELECT 1", &Error::internal("no span on this one")), None);
344 }
345}