qail_core/parser/grammar/
dml.rs

1use super::base::{parse_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};
9
10/// Parse: values col = val, col2 = val2 (for SET/UPDATE)
11pub fn parse_values_clause(input: &str) -> IResult<&str, Cage> {
12    let (input, _) = tag_no_case("values").parse(input)?;
13    let (input, _) = multispace1(input)?;
14
15    let (input, conditions) = parse_set_assignments(input)?;
16
17    Ok((
18        input,
19        Cage {
20            kind: CageKind::Payload,
21            conditions,
22            logical_op: LogicalOp::And,
23        },
24    ))
25}
26
27/// Parse: values :val1, :val2 (for INSERT/ADD) - just list of values without column names
28pub fn parse_insert_values(input: &str) -> IResult<&str, Cage> {
29    let (input, _) = tag_no_case("values").parse(input)?;
30    let (input, _) = multispace1(input)?;
31
32    let (input, values) =
33        separated_list1((multispace0, char(','), multispace0), parse_value).parse(input)?;
34
35    let conditions: Vec<Condition> = values
36        .into_iter()
37        .enumerate()
38        .map(|(i, val)| {
39            Condition {
40                left: Expr::Named(format!("${}", i + 1)), // Use positional placeholder for column
41                op: Operator::Eq,
42                value: val,
43                is_array_unnest: false,
44            }
45        })
46        .collect();
47
48    Ok((
49        input,
50        Cage {
51            kind: CageKind::Payload,
52            conditions,
53            logical_op: LogicalOp::And,
54        },
55    ))
56}
57
58/// Parse comma-separated assignments: col = val, col2 = val2
59pub fn parse_set_assignments(input: &str) -> IResult<&str, Vec<Condition>> {
60    separated_list1((multispace0, char(','), multispace0), parse_assignment).parse(input)
61}
62
63/// Parse single assignment: column = value or column = expression (supports functions and subqueries)
64pub fn parse_assignment(input: &str) -> IResult<&str, Condition> {
65    use super::expressions::parse_expression;
66    use nom::branch::alt;
67
68    let (input, column) = parse_identifier(input)?;
69    let (input, _) = multispace0(input)?;
70    let (input, _) = char('=').parse(input)?;
71    let (input, _) = multispace0(input)?;
72
73    // Try simple value first (booleans, strings, numbers, params), then subquery, then expression
74    let (input, value) = alt((
75        // Try simple value parsing first (handles booleans, strings, numbers, params)
76        parse_value,
77        // Try parenthesized subquery: (get ...)
78        parse_subquery_value,
79        // Fall back to expression and convert to Value::Function
80        nom::combinator::map(parse_expression, |expr| Value::Function(expr.to_string())),
81    ))
82    .parse(input)?;
83
84    Ok((
85        input,
86        Condition {
87            left: Expr::Named(column.to_string()),
88            op: Operator::Eq,
89            value,
90            is_array_unnest: false,
91        },
92    ))
93}
94
95/// Parse a subquery value: (get ...) -> Value::Subquery
96fn parse_subquery_value(input: &str) -> IResult<&str, Value> {
97    let (input, _) = char('(').parse(input)?;
98    let (input, _) = multispace0(input)?;
99    let (input, subquery) = super::parse_root(input)?;
100    let (input, _) = multispace0(input)?;
101    let (input, _) = char(')').parse(input)?;
102    Ok((input, Value::Subquery(Box::new(subquery))))
103}
104
105/// Parse ON CONFLICT clause: conflict (col1, col2) update col = val OR conflict (col) nothing
106///
107/// Syntax:
108/// - `conflict (col1, col2) nothing` -> ON CONFLICT (col1, col2) DO NOTHING
109/// - `conflict (col1) update col2 = val` -> ON CONFLICT (col1) DO UPDATE SET col2 = val
110pub fn parse_on_conflict(input: &str) -> IResult<&str, OnConflict> {
111    use nom::branch::alt;
112
113    let (input, _) = multispace0(input)?;
114    let (input, _) = tag_no_case("conflict").parse(input)?;
115    let (input, _) = multispace0(input)?;
116
117    let (input, _) = char('(').parse(input)?;
118    let (input, _) = multispace0(input)?;
119    let (input, columns) =
120        separated_list1((multispace0, char(','), multispace0), parse_identifier).parse(input)?;
121    let (input, _) = multispace0(input)?;
122    let (input, _) = char(')').parse(input)?;
123    let (input, _) = multispace0(input)?;
124
125    let (input, action) = alt((parse_conflict_nothing, parse_conflict_update)).parse(input)?;
126
127    Ok((
128        input,
129        OnConflict {
130            columns: columns.iter().map(|s| s.to_string()).collect(),
131            action,
132        },
133    ))
134}
135
136/// Parse: nothing
137fn parse_conflict_nothing(input: &str) -> IResult<&str, ConflictAction> {
138    use nom::combinator::value;
139    value(ConflictAction::DoNothing, tag_no_case("nothing")).parse(input)
140}
141
142/// Parse: update col = val, col2 = val2
143fn parse_conflict_update(input: &str) -> IResult<&str, ConflictAction> {
144    let (input, _) = tag_no_case("update").parse(input)?;
145    let (input, _) = multispace1(input)?;
146    let (input, assignments) = parse_conflict_assignments(input)?;
147
148    Ok((input, ConflictAction::DoUpdate { assignments }))
149}
150
151/// Parse assignments for ON CONFLICT UPDATE: col = val, col2 = excluded.col2
152fn parse_conflict_assignments(input: &str) -> IResult<&str, Vec<(String, Expr)>> {
153    separated_list1(
154        (multispace0, char(','), multispace0),
155        parse_conflict_assignment,
156    )
157    .parse(input)
158}
159
160/// Parse single conflict assignment: column = expression (supports :named_params)
161fn parse_conflict_assignment(input: &str) -> IResult<&str, (String, Expr)> {
162    use super::expressions::parse_expression;
163    use nom::branch::alt;
164
165    let (input, column) = parse_identifier(input)?;
166    let (input, _) = multispace0(input)?;
167    let (input, _) = char('=').parse(input)?;
168    let (input, _) = multispace0(input)?;
169
170    // Try to parse a value first (handles :named_params, literals, etc.)
171    // Then fall back to full expression parsing
172    let (input, expr) = alt((
173        nom::combinator::map(parse_value, |v| match v {
174            Value::NamedParam(name) => Expr::Named(format!(":{}", name)),
175            Value::Param(n) => Expr::Named(format!("${}", n)),
176            Value::String(s) => Expr::Named(format!("'{}'", s)),
177            Value::Int(n) => Expr::Named(n.to_string()),
178            Value::Float(f) => Expr::Named(f.to_string()),
179            Value::Bool(b) => Expr::Named(b.to_string()),
180            Value::Null => Expr::Named("NULL".to_string()),
181            Value::Array(_) => Expr::Named("ARRAY".to_string()),
182            Value::Function(name) => Expr::Named(name),
183            Value::Subquery(_) => Expr::Named("(SUBQUERY)".to_string()),
184            Value::Column(col) => Expr::Named(col),
185            Value::Uuid(u) => Expr::Named(format!("'{}'", u)),
186            Value::NullUuid => Expr::Named("NULL".to_string()),
187            Value::Interval { amount, unit } => {
188                Expr::Named(format!("INTERVAL '{} {}'", amount, unit))
189            }
190            Value::Timestamp(ts) => Expr::Named(format!("'{}'", ts)),
191            Value::Bytes(bytes) => {
192                let hex: String = bytes.iter().map(|b| format!("{:02x}", b)).collect();
193                Expr::Named(format!("'\\x{}'", hex))
194            }
195            Value::Expr(expr) => (*expr).clone(),
196        }),
197        // Fall back to full expression parsing
198        parse_expression,
199    ))
200    .parse(input)?;
201
202    Ok((input, (column.to_string(), expr)))
203}
204
205/// Parse: from (get ...) - source query for INSERT...SELECT
206///
207/// Syntax: `from (get table fields col1, col2 where ...)`
208pub fn parse_source_query(input: &str) -> IResult<&str, Box<crate::ast::Qail>> {
209    let (input, _) = multispace0(input)?;
210    let (input, _) = tag_no_case("from").parse(input)?;
211    let (input, _) = multispace0(input)?;
212    let (input, _) = char('(').parse(input)?;
213    let (input, _) = multispace0(input)?;
214    let (input, subquery) = super::parse_root(input)?;
215    let (input, _) = multispace0(input)?;
216    let (input, _) = char(')').parse(input)?;
217    Ok((input, Box::new(subquery)))
218}