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 },
142 ))
143}
144
145fn parse_conflict_nothing(input: &str) -> IResult<&str, ConflictAction> {
147 use nom::combinator::value;
148 value(ConflictAction::DoNothing, tag_no_case("nothing")).parse(input)
149}
150
151fn parse_conflict_update(input: &str) -> IResult<&str, ConflictAction> {
153 let (input, _) = tag_no_case("update").parse(input)?;
154 let (input, _) = multispace1(input)?;
155 let (input, assignments) = parse_conflict_assignments(input)?;
156
157 Ok((input, ConflictAction::DoUpdate { assignments }))
158}
159
160fn parse_conflict_assignments(input: &str) -> IResult<&str, Vec<(String, Expr)>> {
162 let (remaining, assignments) = separated_list1(
163 (multispace0, char(','), multispace0),
164 parse_conflict_assignment,
165 )
166 .parse(input)?;
167 if !conflict_assignment_targets_are_unique(&assignments) {
168 return Err(duplicate_target_error(input));
169 }
170 Ok((remaining, assignments))
171}
172
173fn parse_conflict_assignment(input: &str) -> IResult<&str, (String, Expr)> {
175 use super::expressions::parse_expression;
176 use nom::branch::alt;
177
178 let (input, column) = parse_bare_identifier(input)?;
179 let (input, _) = multispace0(input)?;
180 let (input, _) = char('=').parse(input)?;
181 let (input, _) = multispace0(input)?;
182
183 let (input, expr) = alt((
186 nom::combinator::map(parse_value, super::expressions::value_to_expr),
187 parse_expression,
189 ))
190 .parse(input)?;
191
192 Ok((input, (column.to_string(), expr)))
193}
194
195fn condition_targets_are_unique(conditions: &[Condition]) -> bool {
196 let mut seen = HashSet::new();
197 conditions.iter().all(|condition| match &condition.left {
198 Expr::Named(name) => seen.insert(name.as_str()),
199 _ => true,
200 })
201}
202
203fn identifier_targets_are_unique(columns: &[&str]) -> bool {
204 let mut seen = HashSet::new();
205 columns.iter().all(|column| seen.insert(*column))
206}
207
208fn conflict_assignment_targets_are_unique(assignments: &[(String, Expr)]) -> bool {
209 let mut seen = HashSet::new();
210 assignments
211 .iter()
212 .all(|(column, _)| seen.insert(column.as_str()))
213}
214
215fn duplicate_target_error(input: &str) -> nom::Err<nom::error::Error<&str>> {
216 nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify))
217}
218
219pub fn parse_source_query(input: &str) -> IResult<&str, Box<crate::ast::Qail>> {
222 let (input, _) = multispace0(input)?;
223 let (input, _) = tag_no_case("from").parse(input)?;
224 let (input, _) = multispace0(input)?;
225 let (input, _) = char('(').parse(input)?;
226 let (input, _) = multispace0(input)?;
227 let (input, subquery) = super::parse_root(input)?;
228 let (input, _) = multispace0(input)?;
229 let (input, _) = char(')').parse(input)?;
230 Ok((input, Box::new(subquery)))
231}