qail_core/parser/grammar/
dml.rs1use super::base::{parse_bare_identifier, parse_value};
2use crate::ast::*;
3use nom::{
4 IResult, Parser,
5 bytes::complete::tag_no_case,
6 character::complete::{char, multispace0, multispace1},
7 multi::separated_list1,
8};
9use std::collections::HashSet;
10
11pub fn parse_values_clause(input: &str) -> IResult<&str, Cage> {
13 let (input, _) = tag_no_case("values").parse(input)?;
14 let (input, _) = multispace1(input)?;
15
16 let (input, conditions) = parse_set_assignments(input)?;
17
18 Ok((
19 input,
20 Cage {
21 kind: CageKind::Payload,
22 conditions,
23 logical_op: LogicalOp::And,
24 },
25 ))
26}
27
28pub fn parse_insert_values(input: &str) -> IResult<&str, Cage> {
30 let (input, _) = tag_no_case("values").parse(input)?;
31 let (input, _) = multispace1(input)?;
32
33 let (input, values) =
34 separated_list1((multispace0, char(','), multispace0), parse_value).parse(input)?;
35
36 let conditions: Vec<Condition> = values
37 .into_iter()
38 .enumerate()
39 .map(|(i, val)| {
40 Condition {
41 left: Expr::Named(format!("${}", i + 1)), op: Operator::Eq,
43 value: val,
44 is_array_unnest: false,
45 }
46 })
47 .collect();
48
49 Ok((
50 input,
51 Cage {
52 kind: CageKind::Payload,
53 conditions,
54 logical_op: LogicalOp::And,
55 },
56 ))
57}
58
59pub fn parse_set_assignments(input: &str) -> IResult<&str, Vec<Condition>> {
61 let (remaining, conditions) =
62 separated_list1((multispace0, char(','), multispace0), parse_assignment).parse(input)?;
63 if !condition_targets_are_unique(&conditions) {
64 return Err(duplicate_target_error(input));
65 }
66 Ok((remaining, conditions))
67}
68
69pub fn parse_assignment(input: &str) -> IResult<&str, Condition> {
71 use super::expressions::parse_expression;
72 use nom::branch::alt;
73
74 let (input, column) = parse_bare_identifier(input)?;
75 let (input, _) = multispace0(input)?;
76 let (input, _) = char('=').parse(input)?;
77 let (input, _) = multispace0(input)?;
78
79 let (input, value) = alt((
81 parse_value,
83 parse_subquery_value,
85 nom::combinator::map(parse_expression, |expr| Value::Function(expr.to_string())),
87 ))
88 .parse(input)?;
89
90 Ok((
91 input,
92 Condition {
93 left: Expr::Named(column.to_string()),
94 op: Operator::Eq,
95 value,
96 is_array_unnest: false,
97 },
98 ))
99}
100
101fn parse_subquery_value(input: &str) -> IResult<&str, Value> {
103 let (input, _) = char('(').parse(input)?;
104 let (input, _) = multispace0(input)?;
105 let (input, subquery) = super::parse_root(input)?;
106 let (input, _) = multispace0(input)?;
107 let (input, _) = char(')').parse(input)?;
108 Ok((input, Value::Subquery(Box::new(subquery))))
109}
110
111pub fn parse_on_conflict(input: &str) -> IResult<&str, OnConflict> {
116 use nom::branch::alt;
117
118 let (input, _) = multispace0(input)?;
119 let (input, _) = tag_no_case("conflict").parse(input)?;
120 let (input, _) = multispace0(input)?;
121
122 let (input, _) = char('(').parse(input)?;
123 let (input, _) = multispace0(input)?;
124 let (input, columns) =
125 separated_list1((multispace0, char(','), multispace0), parse_bare_identifier)
126 .parse(input)?;
127 if !identifier_targets_are_unique(&columns) {
128 return Err(duplicate_target_error(input));
129 }
130 let (input, _) = multispace0(input)?;
131 let (input, _) = char(')').parse(input)?;
132 let (input, _) = multispace0(input)?;
133
134 let (input, action) = alt((parse_conflict_nothing, parse_conflict_update)).parse(input)?;
135
136 Ok((
137 input,
138 OnConflict {
139 columns: columns.iter().map(|s| s.to_string()).collect(),
140 action,
141 where_conditions: Vec::new(),
142 },
143 ))
144}
145
146fn parse_conflict_nothing(input: &str) -> IResult<&str, ConflictAction> {
148 use nom::combinator::value;
149 value(ConflictAction::DoNothing, tag_no_case("nothing")).parse(input)
150}
151
152fn parse_conflict_update(input: &str) -> IResult<&str, ConflictAction> {
154 let (input, _) = tag_no_case("update").parse(input)?;
155 let (input, _) = multispace1(input)?;
156 let (input, assignments) = parse_conflict_assignments(input)?;
157
158 Ok((input, ConflictAction::DoUpdate { assignments }))
159}
160
161fn parse_conflict_assignments(input: &str) -> IResult<&str, Vec<(String, Expr)>> {
163 let (remaining, assignments) = separated_list1(
164 (multispace0, char(','), multispace0),
165 parse_conflict_assignment,
166 )
167 .parse(input)?;
168 if !conflict_assignment_targets_are_unique(&assignments) {
169 return Err(duplicate_target_error(input));
170 }
171 Ok((remaining, assignments))
172}
173
174fn parse_conflict_assignment(input: &str) -> IResult<&str, (String, Expr)> {
176 use super::expressions::parse_expression;
177 use nom::branch::alt;
178
179 let (input, column) = parse_bare_identifier(input)?;
180 let (input, _) = multispace0(input)?;
181 let (input, _) = char('=').parse(input)?;
182 let (input, _) = multispace0(input)?;
183
184 let (input, expr) = alt((
187 nom::combinator::map(parse_value, super::expressions::value_to_expr),
188 parse_expression,
190 ))
191 .parse(input)?;
192
193 Ok((input, (column.to_string(), expr)))
194}
195
196fn condition_targets_are_unique(conditions: &[Condition]) -> bool {
197 let mut seen = HashSet::new();
198 conditions.iter().all(|condition| match &condition.left {
199 Expr::Named(name) => seen.insert(name.as_str()),
200 _ => true,
201 })
202}
203
204fn identifier_targets_are_unique(columns: &[&str]) -> bool {
205 let mut seen = HashSet::new();
206 columns.iter().all(|column| seen.insert(*column))
207}
208
209fn conflict_assignment_targets_are_unique(assignments: &[(String, Expr)]) -> bool {
210 let mut seen = HashSet::new();
211 assignments
212 .iter()
213 .all(|(column, _)| seen.insert(column.as_str()))
214}
215
216fn duplicate_target_error(input: &str) -> nom::Err<nom::error::Error<&str>> {
217 nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify))
218}
219
220pub fn parse_source_query(input: &str) -> IResult<&str, Box<crate::ast::Qail>> {
223 let (input, _) = multispace0(input)?;
224 let (input, _) = tag_no_case("from").parse(input)?;
225 let (input, _) = multispace0(input)?;
226 let (input, _) = char('(').parse(input)?;
227 let (input, _) = multispace0(input)?;
228 let (input, subquery) = super::parse_root(input)?;
229 let (input, _) = multispace0(input)?;
230 let (input, _) = char(')').parse(input)?;
231 Ok((input, Box::new(subquery)))
232}