use super::base::{parse_identifier, parse_value};
use crate::ast::*;
use nom::{
IResult, Parser,
bytes::complete::tag_no_case,
character::complete::{char, multispace0, multispace1},
multi::separated_list1,
};
pub fn parse_values_clause(input: &str) -> IResult<&str, Cage> {
let (input, _) = tag_no_case("values").parse(input)?;
let (input, _) = multispace1(input)?;
let (input, conditions) = parse_set_assignments(input)?;
Ok((
input,
Cage {
kind: CageKind::Payload,
conditions,
logical_op: LogicalOp::And,
},
))
}
pub fn parse_insert_values(input: &str) -> IResult<&str, Cage> {
let (input, _) = tag_no_case("values").parse(input)?;
let (input, _) = multispace1(input)?;
let (input, values) =
separated_list1((multispace0, char(','), multispace0), parse_value).parse(input)?;
let conditions: Vec<Condition> = values
.into_iter()
.enumerate()
.map(|(i, val)| {
Condition {
left: Expr::Named(format!("${}", i + 1)), op: Operator::Eq,
value: val,
is_array_unnest: false,
}
})
.collect();
Ok((
input,
Cage {
kind: CageKind::Payload,
conditions,
logical_op: LogicalOp::And,
},
))
}
pub fn parse_set_assignments(input: &str) -> IResult<&str, Vec<Condition>> {
separated_list1((multispace0, char(','), multispace0), parse_assignment).parse(input)
}
pub fn parse_assignment(input: &str) -> IResult<&str, Condition> {
use super::expressions::parse_expression;
use nom::branch::alt;
let (input, column) = parse_identifier(input)?;
let (input, _) = multispace0(input)?;
let (input, _) = char('=').parse(input)?;
let (input, _) = multispace0(input)?;
let (input, value) = alt((
parse_value,
parse_subquery_value,
nom::combinator::map(parse_expression, |expr| Value::Function(expr.to_string())),
))
.parse(input)?;
Ok((
input,
Condition {
left: Expr::Named(column.to_string()),
op: Operator::Eq,
value,
is_array_unnest: false,
},
))
}
fn parse_subquery_value(input: &str) -> IResult<&str, Value> {
let (input, _) = char('(').parse(input)?;
let (input, _) = multispace0(input)?;
let (input, subquery) = super::parse_root(input)?;
let (input, _) = multispace0(input)?;
let (input, _) = char(')').parse(input)?;
Ok((input, Value::Subquery(Box::new(subquery))))
}
pub fn parse_on_conflict(input: &str) -> IResult<&str, OnConflict> {
use nom::branch::alt;
let (input, _) = multispace0(input)?;
let (input, _) = tag_no_case("conflict").parse(input)?;
let (input, _) = multispace0(input)?;
let (input, _) = char('(').parse(input)?;
let (input, _) = multispace0(input)?;
let (input, columns) =
separated_list1((multispace0, char(','), multispace0), parse_identifier).parse(input)?;
let (input, _) = multispace0(input)?;
let (input, _) = char(')').parse(input)?;
let (input, _) = multispace0(input)?;
let (input, action) = alt((parse_conflict_nothing, parse_conflict_update)).parse(input)?;
Ok((
input,
OnConflict {
columns: columns.iter().map(|s| s.to_string()).collect(),
action,
},
))
}
fn parse_conflict_nothing(input: &str) -> IResult<&str, ConflictAction> {
use nom::combinator::value;
value(ConflictAction::DoNothing, tag_no_case("nothing")).parse(input)
}
fn parse_conflict_update(input: &str) -> IResult<&str, ConflictAction> {
let (input, _) = tag_no_case("update").parse(input)?;
let (input, _) = multispace1(input)?;
let (input, assignments) = parse_conflict_assignments(input)?;
Ok((input, ConflictAction::DoUpdate { assignments }))
}
fn parse_conflict_assignments(input: &str) -> IResult<&str, Vec<(String, Expr)>> {
separated_list1(
(multispace0, char(','), multispace0),
parse_conflict_assignment,
)
.parse(input)
}
fn parse_conflict_assignment(input: &str) -> IResult<&str, (String, Expr)> {
use super::expressions::parse_expression;
use nom::branch::alt;
let (input, column) = parse_identifier(input)?;
let (input, _) = multispace0(input)?;
let (input, _) = char('=').parse(input)?;
let (input, _) = multispace0(input)?;
let (input, expr) = alt((
nom::combinator::map(parse_value, |v| match v {
Value::NamedParam(name) => Expr::Named(format!(":{}", name)),
Value::Param(n) => Expr::Named(format!("${}", n)),
Value::String(s) => Expr::Named(format!("'{}'", s)),
Value::Int(n) => Expr::Named(n.to_string()),
Value::Float(f) => Expr::Named(f.to_string()),
Value::Bool(b) => Expr::Named(b.to_string()),
Value::Null => Expr::Named("NULL".to_string()),
Value::Array(_) => Expr::Named("ARRAY".to_string()),
Value::Function(name) => Expr::Named(name),
Value::Subquery(_) => Expr::Named("(SUBQUERY)".to_string()),
Value::Column(col) => Expr::Named(col),
Value::Uuid(u) => Expr::Named(format!("'{}'", u)),
Value::NullUuid => Expr::Named("NULL".to_string()),
Value::Interval { amount, unit } => {
Expr::Named(format!("INTERVAL '{} {}'", amount, unit))
}
Value::Timestamp(ts) => Expr::Named(format!("'{}'", ts)),
Value::Bytes(bytes) => {
let hex: String = bytes.iter().map(|b| format!("{:02x}", b)).collect();
Expr::Named(format!("'\\x{}'", hex))
}
Value::Expr(expr) => (*expr).clone(),
Value::Vector(v) => Expr::Named(format!("[{} floats]", v.len())),
Value::Json(json) => Expr::Named(format!("'{}'::jsonb", json.replace('\'', "''"))),
}),
parse_expression,
))
.parse(input)?;
Ok((input, (column.to_string(), expr)))
}
pub fn parse_source_query(input: &str) -> IResult<&str, Box<crate::ast::Qail>> {
let (input, _) = multispace0(input)?;
let (input, _) = tag_no_case("from").parse(input)?;
let (input, _) = multispace0(input)?;
let (input, _) = char('(').parse(input)?;
let (input, _) = multispace0(input)?;
let (input, subquery) = super::parse_root(input)?;
let (input, _) = multispace0(input)?;
let (input, _) = char(')').parse(input)?;
Ok((input, Box::new(subquery)))
}