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 bytes::complete::tag_no_case,
17 character::complete::{multispace0, multispace1},
18 combinator::opt,
19 multi::many0,
20};
21use self::base::*;
22use self::clauses::*;
23use self::ddl::*;
24use self::dml::*;
25use self::joins::*;
26pub fn parse(input: &str) -> Result<Qail, String> {
32 let cleaned = strip_sql_comments(input);
33 let desugared = desugar_bracket_filter(&cleaned);
36 match parse_root(&desugared) {
37 Ok(("", cmd)) => Ok(cmd),
38 Ok((remaining, _)) => Err(format!(
39 "Unexpected trailing content: '{}'", remaining
40 )),
41 Err(e) => Err(format!("Parse error: {:?}", e)),
42 }
43}
44
45fn desugar_bracket_filter(input: &str) -> String {
48 let trimmed = input.trim();
49 if let Some(bracket_start) = trimmed.find('[') {
52 let before_bracket = &trimmed[..bracket_start];
54 if !before_bracket.contains(' ') {
56 return trimmed.to_string();
57 }
58
59 let after_bracket = &trimmed[bracket_start + 1..];
61 let mut depth = 1;
62 let mut in_single_quote = false;
63 let mut in_double_quote = false;
64 let mut bracket_end = None;
65
66 for (i, c) in after_bracket.char_indices() {
67 match c {
68 '\'' if !in_double_quote => in_single_quote = !in_single_quote,
69 '"' if !in_single_quote => in_double_quote = !in_double_quote,
70 '[' if !in_single_quote && !in_double_quote => depth += 1,
71 ']' if !in_single_quote && !in_double_quote => {
72 depth -= 1;
73 if depth == 0 {
74 bracket_end = Some(i);
75 break;
76 }
77 }
78 _ => {}
79 }
80 }
81
82 if let Some(end_pos) = bracket_end {
83 let filter = &after_bracket[..end_pos];
84 let rest = &after_bracket[end_pos + 1..].trim();
85
86 let rest_lower = rest.to_lowercase();
88 if rest_lower.contains("where ") || rest_lower.contains("where\n") {
89 return format!(
91 "{} {} AND {}",
92 before_bracket, rest, filter
93 );
94 } else if rest.is_empty() {
95 return format!("{} where {}", before_bracket, filter);
96 } else {
97 return format!("{} {} where {}", before_bracket, rest, filter);
98 }
99 }
100 }
101 trimmed.to_string()
102}
103
104pub fn parse_root(input: &str) -> IResult<&str, Qail> {
107 let input = input.trim();
108
109 if let Ok((remaining, cmd)) = parse_txn_command(input) {
111 return Ok((remaining, cmd));
112 }
113
114 if let Ok((remaining, cmd)) = parse_create_index(input) {
116 return Ok((remaining, cmd));
117 }
118
119 let lower_input = input.to_lowercase();
121 let (input, ctes) = if lower_input.starts_with("with")
122 && lower_input
123 .chars()
124 .nth(4)
125 .map(|c| c.is_whitespace())
126 .unwrap_or(false)
127 {
128 let (remaining, (cte_defs, _is_recursive)) = cte::parse_with_clause(input)?;
129 let (remaining, _) = multispace0(remaining)?;
130 (remaining, cte_defs)
131 } else {
132 (input, vec![])
133 };
134
135 let (input, (action, distinct)) = parse_action(input)?;
136 let (input, _) = multispace1(input)?;
138
139 let (input, distinct_on) = if distinct {
141 if let Ok((remaining, _)) = tag_no_case::<_, _, nom::error::Error<&str>>("on").parse(input)
143 {
144 let (remaining, _) = multispace0(remaining)?;
145 let (remaining, exprs) = nom::sequence::delimited(
146 nom::character::complete::char('('),
147 nom::multi::separated_list1(
148 (
149 multispace0,
150 nom::character::complete::char(','),
151 multispace0,
152 ),
153 expressions::parse_expression,
154 ),
155 nom::character::complete::char(')'),
156 )
157 .parse(remaining)?;
158 let (remaining, _) = multispace1(remaining)?;
159 (remaining, exprs)
160 } else {
161 (input, vec![])
162 }
163 } else {
164 (input, vec![])
165 };
166
167 let (input, table) = parse_identifier(input)?;
169 let (input, _) = multispace0(input)?;
170
171 if matches!(action, Action::Make) {
173 return parse_create_table(input, table);
174 }
175
176 let (input, joins) = many0(parse_join_clause).parse(input)?;
177 let (input, _) = multispace0(input)?;
178
179 let (input, set_cages) = if matches!(action, Action::Set) {
181 opt(parse_values_clause).parse(input)?
182 } else {
183 (input, None)
184 };
185 let (input, _) = multispace0(input)?;
186
187 let (input, columns) = opt(parse_fields_clause).parse(input)?;
188 let (input, _) = multispace0(input)?;
189
190 let (input, source_query) = if matches!(action, Action::Add) {
192 opt(dml::parse_source_query).parse(input)?
193 } else {
194 (input, None)
195 };
196 let (input, _) = multispace0(input)?;
197
198 let (input, add_cages) = if source_query.is_none() && matches!(action, Action::Add) {
200 opt(dml::parse_insert_values).parse(input)?
201 } else {
202 (input, None)
203 };
204 let (input, _) = multispace0(input)?;
205
206 let (input, where_cages) = opt(parse_where_clause).parse(input)?;
207 let (input, _) = multispace0(input)?;
208
209 let (input, having) = opt(parse_having_clause).parse(input)?;
210 let (input, _) = multispace0(input)?;
211
212 let (input, on_conflict) = if matches!(action, Action::Add) {
213 opt(dml::parse_on_conflict).parse(input)?
214 } else {
215 (input, None)
216 };
217 let (input, _) = multispace0(input)?;
218
219 let (input, order_cages) = opt(parse_order_by_clause).parse(input)?;
220 let (input, _) = multispace0(input)?;
221 let (input, limit_cage) = opt(parse_limit_clause).parse(input)?;
222 let (input, _) = multispace0(input)?;
223 let (input, offset_cage) = opt(parse_offset_clause).parse(input)?;
224
225 let mut cages = Vec::new();
226
227 if let Some(sc) = set_cages {
229 cages.push(sc);
230 }
231
232 if let Some(ac) = add_cages {
234 cages.push(ac);
235 }
236
237 if let Some(wc) = where_cages {
238 cages.extend(wc);
239 }
240 if let Some(oc) = order_cages {
241 cages.extend(oc);
242 }
243 if let Some(lc) = limit_cage {
244 cages.push(lc);
245 }
246 if let Some(oc) = offset_cage {
247 cages.push(oc);
248 }
249
250 Ok((
251 input,
252 Qail {
253 action,
254 table: table.to_string(),
255 columns: columns.unwrap_or_else(|| vec![Expr::Star]),
256 joins,
257 cages,
258 distinct,
259 distinct_on,
260 index_def: None,
261 table_constraints: vec![],
262 set_ops: vec![],
263 having: having.unwrap_or_default(),
264 group_by_mode: GroupByMode::default(),
265 returning: None,
266 ctes,
267 on_conflict,
268 source_query,
269 channel: None,
270 payload: None,
271 savepoint_name: None,
272 from_tables: vec![],
273 using_tables: vec![],
274 lock_mode: None,
275 fetch: None,
276 default_values: false,
277 overriding: None,
278 sample: None,
279 only_table: false,
280 vector: None,
281 score_threshold: None,
282 vector_name: None,
283 with_vector: false,
284 vector_size: None,
285 distance: None,
286 on_disk: None,
287 function_def: None,
288 trigger_def: None,
289 raw_value: None,
290 redis_ttl: None,
291 redis_set_condition: None,
292 },
293 ))
294}
295
296fn strip_sql_comments(input: &str) -> String {
298 let mut result = String::with_capacity(input.len());
299 let mut chars = input.chars().peekable();
300
301 while let Some(c) = chars.next() {
302 if c == '-' && chars.peek() == Some(&'-') {
303 chars.next(); while let Some(&nc) = chars.peek() {
306 if nc == '\n' {
307 result.push('\n'); chars.next();
309 break;
310 }
311 chars.next();
312 }
313 } else if c == '/' && chars.peek() == Some(&'*') {
314 chars.next(); while let Some(nc) = chars.next() {
317 if nc == '*' && chars.peek() == Some(&'/') {
318 chars.next(); result.push(' '); break;
321 }
322 }
323 } else {
324 result.push(c);
325 }
326 }
327
328 result
329}