qail_core/parser/grammar/
mod.rs1pub mod base;
2pub mod binary_ops;
3pub mod case_when;
4pub mod clauses;
5pub mod cte;
6pub mod ddl;
7pub mod dml;
8pub mod expressions;
9pub mod functions;
10pub mod joins;
11pub mod special_funcs;
12
13use crate::ast::*;
14use nom::{
15 IResult, Parser,
16 branch::alt,
17 bytes::complete::{tag, tag_no_case},
18 character::complete::{multispace0, multispace1},
19 combinator::{opt, value},
20 multi::many0,
21};
22use self::base::*;
23use self::clauses::*;
24use self::ddl::*;
25use self::dml::*;
26use self::joins::*;
27pub fn parse(input: &str) -> Result<Qail, String> {
32 let cleaned = strip_sql_comments(input);
33 match parse_root(&cleaned) {
34 Ok((_, cmd)) => Ok(cmd),
35 Err(e) => Err(format!("Parse error: {:?}", e)),
36 }
37}
38
39pub fn parse_root(input: &str) -> IResult<&str, Qail> {
42 let input = input.trim();
43
44 if let Ok((remaining, cmd)) = parse_txn_command(input) {
46 return Ok((remaining, cmd));
47 }
48
49 if let Ok((remaining, cmd)) = parse_create_index(input) {
51 return Ok((remaining, cmd));
52 }
53
54 let lower_input = input.to_lowercase();
56 let (input, ctes) = if lower_input.starts_with("with")
57 && lower_input
58 .chars()
59 .nth(4)
60 .map(|c| c.is_whitespace())
61 .unwrap_or(false)
62 {
63 let (remaining, (cte_defs, _is_recursive)) = cte::parse_with_clause(input)?;
64 let (remaining, _) = multispace0(remaining)?;
65 (remaining, cte_defs)
66 } else {
67 (input, vec![])
68 };
69
70 let (input, (action, distinct)) = parse_action(input)?;
71 let (input, _) = alt((
73 value((), tag("::")),
74 value((), multispace1),
75 )).parse(input)?;
76
77 let (input, distinct_on) = if distinct {
79 if let Ok((remaining, _)) = tag_no_case::<_, _, nom::error::Error<&str>>("on").parse(input)
81 {
82 let (remaining, _) = multispace0(remaining)?;
83 let (remaining, exprs) = nom::sequence::delimited(
84 nom::character::complete::char('('),
85 nom::multi::separated_list1(
86 (
87 multispace0,
88 nom::character::complete::char(','),
89 multispace0,
90 ),
91 expressions::parse_expression,
92 ),
93 nom::character::complete::char(')'),
94 )
95 .parse(remaining)?;
96 let (remaining, _) = multispace1(remaining)?;
97 (remaining, exprs)
98 } else {
99 (input, vec![])
100 }
101 } else {
102 (input, vec![])
103 };
104
105 let (input, table) = parse_identifier(input)?;
107 let (input, _) = multispace0(input)?;
108
109 if matches!(action, Action::Make) {
111 return parse_create_table(input, table);
112 }
113
114 let (input, joins) = many0(parse_join_clause).parse(input)?;
115 let (input, _) = multispace0(input)?;
116
117 let (input, set_cages) = if matches!(action, Action::Set) {
119 opt(parse_values_clause).parse(input)?
120 } else {
121 (input, None)
122 };
123 let (input, _) = multispace0(input)?;
124
125 let (input, columns) = opt(parse_fields_clause).parse(input)?;
126 let (input, _) = multispace0(input)?;
127
128 let (input, source_query) = if matches!(action, Action::Add) {
130 opt(dml::parse_source_query).parse(input)?
131 } else {
132 (input, None)
133 };
134 let (input, _) = multispace0(input)?;
135
136 let (input, add_cages) = if source_query.is_none() && matches!(action, Action::Add) {
138 opt(dml::parse_insert_values).parse(input)?
139 } else {
140 (input, None)
141 };
142 let (input, _) = multispace0(input)?;
143
144 let (input, where_cages) = opt(parse_where_clause).parse(input)?;
145 let (input, _) = multispace0(input)?;
146
147 let (input, having) = opt(parse_having_clause).parse(input)?;
148 let (input, _) = multispace0(input)?;
149
150 let (input, on_conflict) = if matches!(action, Action::Add) {
151 opt(dml::parse_on_conflict).parse(input)?
152 } else {
153 (input, None)
154 };
155 let (input, _) = multispace0(input)?;
156
157 let (input, order_cages) = opt(parse_order_by_clause).parse(input)?;
158 let (input, _) = multispace0(input)?;
159 let (input, limit_cage) = opt(parse_limit_clause).parse(input)?;
160 let (input, _) = multispace0(input)?;
161 let (input, offset_cage) = opt(parse_offset_clause).parse(input)?;
162
163 let mut cages = Vec::new();
164
165 if let Some(sc) = set_cages {
167 cages.push(sc);
168 }
169
170 if let Some(ac) = add_cages {
172 cages.push(ac);
173 }
174
175 if let Some(wc) = where_cages {
176 cages.extend(wc);
177 }
178 if let Some(oc) = order_cages {
179 cages.extend(oc);
180 }
181 if let Some(lc) = limit_cage {
182 cages.push(lc);
183 }
184 if let Some(oc) = offset_cage {
185 cages.push(oc);
186 }
187
188 Ok((
189 input,
190 Qail {
191 action,
192 table: table.to_string(),
193 columns: columns.unwrap_or_else(|| vec![Expr::Star]),
194 joins,
195 cages,
196 distinct,
197 distinct_on,
198 index_def: None,
199 table_constraints: vec![],
200 set_ops: vec![],
201 having: having.unwrap_or_default(),
202 group_by_mode: GroupByMode::default(),
203 returning: None,
204 ctes,
205 on_conflict,
206 source_query,
207 channel: None,
208 payload: None,
209 savepoint_name: None,
210 from_tables: vec![],
211 using_tables: vec![],
212 lock_mode: None,
213 fetch: None,
214 default_values: false,
215 overriding: None,
216 sample: None,
217 only_table: false,
218 },
219 ))
220}
221
222fn strip_sql_comments(input: &str) -> String {
224 let mut result = String::with_capacity(input.len());
225 let mut chars = input.chars().peekable();
226
227 while let Some(c) = chars.next() {
228 if c == '-' && chars.peek() == Some(&'-') {
229 chars.next(); while let Some(&nc) = chars.peek() {
232 if nc == '\n' {
233 result.push('\n'); chars.next();
235 break;
236 }
237 chars.next();
238 }
239 } else if c == '/' && chars.peek() == Some(&'*') {
240 chars.next(); while let Some(nc) = chars.next() {
243 if nc == '*' && chars.peek() == Some(&'/') {
244 chars.next(); result.push(' '); break;
247 }
248 }
249 } else {
250 result.push(c);
251 }
252 }
253
254 result
255}