use crate::ast::aggregate::sort_keys_text;
use crate::ast::{AggregateFunc, Cage, Condition, ModKind, Value};
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum BinaryOp {
Concat,
Add,
Sub,
Mul,
Div,
Rem,
BitAnd,
BitOr,
BitXor,
ShiftLeft,
ShiftRight,
JsonPathExists,
JsonPathMatch,
And,
Or,
Eq,
Ne,
Gt,
Gte,
Lt,
Lte,
IsNull,
IsNotNull,
IsDistinctFrom,
IsNotDistinctFrom,
IsTrue,
IsNotTrue,
IsFalse,
IsNotFalse,
IsUnknown,
IsNotUnknown,
}
impl BinaryOp {
pub fn is_postfix(&self) -> bool {
matches!(
self,
BinaryOp::IsNull
| BinaryOp::IsNotNull
| BinaryOp::IsTrue
| BinaryOp::IsNotTrue
| BinaryOp::IsFalse
| BinaryOp::IsNotFalse
| BinaryOp::IsUnknown
| BinaryOp::IsNotUnknown
)
}
}
impl BinaryOp {
pub fn is_jsonpath(&self) -> bool {
matches!(self, BinaryOp::JsonPathExists | BinaryOp::JsonPathMatch)
}
pub fn infix_sql(&self, left: &str, right: &str) -> String {
if self.is_jsonpath() {
format!("({left} {self} CAST({right} AS jsonpath))")
} else {
format!("({left} {self} {right})")
}
}
}
impl std::fmt::Display for BinaryOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BinaryOp::Concat => write!(f, "||"),
BinaryOp::Add => write!(f, "+"),
BinaryOp::Sub => write!(f, "-"),
BinaryOp::Mul => write!(f, "*"),
BinaryOp::Div => write!(f, "/"),
BinaryOp::Rem => write!(f, "%"),
BinaryOp::BitAnd => write!(f, "&"),
BinaryOp::BitOr => write!(f, "|"),
BinaryOp::BitXor => write!(f, "#"),
BinaryOp::ShiftLeft => write!(f, "<<"),
BinaryOp::ShiftRight => write!(f, ">>"),
BinaryOp::JsonPathExists => write!(f, "@?"),
BinaryOp::JsonPathMatch => write!(f, "@@"),
BinaryOp::And => write!(f, "AND"),
BinaryOp::Or => write!(f, "OR"),
BinaryOp::Eq => write!(f, "="),
BinaryOp::Ne => write!(f, "<>"),
BinaryOp::Gt => write!(f, ">"),
BinaryOp::Gte => write!(f, ">="),
BinaryOp::Lt => write!(f, "<"),
BinaryOp::Lte => write!(f, "<="),
BinaryOp::IsNull => write!(f, "IS NULL"),
BinaryOp::IsNotNull => write!(f, "IS NOT NULL"),
BinaryOp::IsDistinctFrom => write!(f, "IS DISTINCT FROM"),
BinaryOp::IsNotDistinctFrom => write!(f, "IS NOT DISTINCT FROM"),
BinaryOp::IsTrue => write!(f, "IS TRUE"),
BinaryOp::IsNotTrue => write!(f, "IS NOT TRUE"),
BinaryOp::IsFalse => write!(f, "IS FALSE"),
BinaryOp::IsNotFalse => write!(f, "IS NOT FALSE"),
BinaryOp::IsUnknown => write!(f, "IS UNKNOWN"),
BinaryOp::IsNotUnknown => write!(f, "IS NOT UNKNOWN"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum JsonPathSegment {
Key(String),
Index(i64),
}
impl JsonPathSegment {
pub fn from_path_text(text: &str) -> Self {
match text.parse::<i64>() {
Ok(index) => Self::Index(index),
Err(_) => Self::Key(text.to_string()),
}
}
pub fn as_key(&self) -> Option<&str> {
match self {
Self::Key(key) => Some(key),
Self::Index(_) => None,
}
}
}
impl From<&str> for JsonPathSegment {
fn from(key: &str) -> Self {
Self::Key(key.to_string())
}
}
impl From<String> for JsonPathSegment {
fn from(key: String) -> Self {
Self::Key(key)
}
}
impl From<i64> for JsonPathSegment {
fn from(index: i64) -> Self {
Self::Index(index)
}
}
impl std::fmt::Display for JsonPathSegment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Key(key) => write!(f, "'{}'", key.replace('\'', "''")),
Self::Index(index) => write!(f, "{index}"),
}
}
}
impl serde::Serialize for JsonPathSegment {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeMap;
match self {
Self::Index(index) => serializer.serialize_str(&index.to_string()),
Self::Key(key) if key.parse::<i64>().is_ok() => {
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("key", key)?;
map.end()
}
Self::Key(key) => serializer.serialize_str(key),
}
}
}
impl<'de> serde::Deserialize<'de> for JsonPathSegment {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct SegmentVisitor;
impl<'de> serde::de::Visitor<'de> for SegmentVisitor {
type Value = JsonPathSegment;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("a JSON path string, integer index, or {\"key\": text}")
}
fn visit_i64<E: serde::de::Error>(self, index: i64) -> Result<Self::Value, E> {
Ok(JsonPathSegment::Index(index))
}
fn visit_u64<E: serde::de::Error>(self, index: u64) -> Result<Self::Value, E> {
i64::try_from(index)
.map(JsonPathSegment::Index)
.map_err(|_| E::custom("JSON path index out of i64 range"))
}
fn visit_str<E: serde::de::Error>(self, text: &str) -> Result<Self::Value, E> {
Ok(JsonPathSegment::from_path_text(text))
}
fn visit_map<A: serde::de::MapAccess<'de>>(
self,
mut map: A,
) -> Result<Self::Value, A::Error> {
use serde::de::Error;
let Some(tag) = map.next_key::<String>()? else {
return Err(A::Error::custom("empty JSON path segment object"));
};
let segment = match tag.as_str() {
"key" => JsonPathSegment::Key(map.next_value()?),
"index" => JsonPathSegment::Index(map.next_value()?),
other => {
return Err(A::Error::unknown_field(other, &["key", "index"]));
}
};
if map.next_key::<String>()?.is_some() {
return Err(A::Error::custom(
"JSON path segment object takes exactly one field",
));
}
Ok(segment)
}
}
deserializer.deserialize_any(SegmentVisitor)
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Expr {
Star,
Named(String),
Aliased {
name: String,
alias: String,
},
Aggregate {
col: String,
func: AggregateFunc,
distinct: bool,
filter: Option<Vec<Condition>>,
alias: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
args: Vec<Expr>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
order_by: Vec<Cage>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
within_group: Vec<Cage>,
},
Cast {
expr: Box<Expr>,
target_type: String,
alias: Option<String>,
},
Def {
name: String,
data_type: String,
constraints: Vec<Constraint>,
},
Mod {
kind: ModKind,
col: Box<Expr>,
},
Window {
name: String,
func: String,
params: Vec<Expr>,
#[serde(default, skip_serializing_if = "Option::is_none")]
filter: Option<Vec<Condition>>,
partition: Vec<String>,
order: Vec<Cage>,
frame: Option<WindowFrame>,
},
Case {
when_clauses: Vec<(Condition, Box<Expr>)>,
else_value: Option<Box<Expr>>,
alias: Option<String>,
},
JsonAccess {
column: String,
path_segments: Vec<(JsonPathSegment, bool)>,
alias: Option<String>,
},
FunctionCall {
name: String,
args: Vec<Expr>,
alias: Option<String>,
},
SpecialFunction {
name: String,
args: Vec<(Option<String>, Box<Expr>)>,
alias: Option<String>,
},
Binary {
left: Box<Expr>,
op: BinaryOp,
right: Box<Expr>,
alias: Option<String>,
},
Literal(Value),
ArrayConstructor {
elements: Vec<Expr>,
alias: Option<String>,
},
RowConstructor {
elements: Vec<Expr>,
alias: Option<String>,
},
Subscript {
expr: Box<Expr>,
index: Box<Expr>,
alias: Option<String>,
},
ArraySlice {
expr: Box<Expr>,
lower: Option<Box<Expr>>,
upper: Option<Box<Expr>>,
alias: Option<String>,
},
Collate {
expr: Box<Expr>,
collation: String,
alias: Option<String>,
},
FieldAccess {
expr: Box<Expr>,
field: String,
alias: Option<String>,
},
Subquery {
query: Box<super::Qail>,
alias: Option<String>,
},
Exists {
query: Box<super::Qail>,
negated: bool,
alias: Option<String>,
},
Default,
FunctionArg {
name: Option<String>,
variadic: bool,
value: Box<Expr>,
},
}
pub fn validate_function_args(args: &[Expr]) -> Result<(), String> {
let mut seen_named = false;
let mut names: Vec<&str> = Vec::new();
for (i, arg) in args.iter().enumerate() {
let (name, variadic) = match arg {
Expr::FunctionArg { name, variadic, .. } => (name.as_deref(), *variadic),
_ => (None, false),
};
if variadic && i + 1 != args.len() {
return Err("VARIADIC must mark the last function argument".to_string());
}
match name {
Some(name) => {
if names.iter().any(|seen| seen.eq_ignore_ascii_case(name)) {
return Err(format!("function argument `{name}` is named twice"));
}
names.push(name);
seen_named = true;
}
None if seen_named => {
return Err("positional function argument after a named one".to_string());
}
None => {}
}
}
Ok(())
}
impl std::fmt::Display for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Expr::Star => write!(f, "*"),
Expr::Named(name) => write!(f, "{}", name),
Expr::Aliased { name, alias } => write!(f, "{} AS {}", name, alias),
Expr::Aggregate {
col,
func,
distinct,
filter,
alias,
args,
order_by,
within_group,
} => {
write!(f, "{}(", func)?;
if *distinct {
write!(f, "DISTINCT ")?;
}
if args.is_empty() {
write!(f, "{}", col)?;
} else {
for (i, arg) in args.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", arg)?;
}
}
if !order_by.is_empty() {
write!(f, " ORDER BY {}", sort_keys_text(order_by))?;
}
write!(f, ")")?;
if !within_group.is_empty() {
write!(
f,
" WITHIN GROUP (ORDER BY {})",
sort_keys_text(within_group)
)?;
}
if let Some(conditions) = filter {
write!(
f,
" FILTER (WHERE {})",
conditions
.iter()
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join(" AND ")
)?;
}
if let Some(a) = alias {
write!(f, " AS {}", a)?;
}
Ok(())
}
Expr::Cast {
expr,
target_type,
alias,
} => {
write!(f, "{}::{}", expr, target_type)?;
if let Some(a) = alias {
write!(f, " AS {}", a)?;
}
Ok(())
}
Expr::Def {
name,
data_type,
constraints,
} => {
write!(f, "{}:{}", name, data_type)?;
for c in constraints {
write!(f, "^{}", c)?;
}
Ok(())
}
Expr::Mod { kind, col } => match kind {
ModKind::Add => write!(f, "+{}", col),
ModKind::Drop => write!(f, "-{}", col),
},
Expr::Window {
name,
func,
params,
filter,
partition,
order,
frame,
} => {
write!(f, "{}:{}(", name, func)?;
for (i, p) in params.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", p)?;
}
write!(f, ")")?;
if let Some(conditions) = filter {
write!(
f,
" FILTER (WHERE {})",
conditions
.iter()
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join(" AND ")
)?;
}
if !partition.is_empty() {
write!(f, "{{Part=")?;
for (i, p) in partition.iter().enumerate() {
if i > 0 {
write!(f, ",")?;
}
write!(f, "{}", p)?;
}
if let Some(fr) = frame {
write!(f, ", Frame={:?}", fr)?; }
write!(f, "}}")?;
} else if let Some(fr) = frame {
write!(f, "{{Frame={:?}}}", fr)?;
}
for _cage in order {
}
Ok(())
}
Expr::Case {
when_clauses,
else_value,
alias,
} => {
write!(f, "CASE")?;
for (cond, val) in when_clauses {
write!(f, " WHEN {} {}", cond.left, cond.op.sql_symbol())?;
match (&cond.op, &cond.value) {
(crate::ast::Operator::IsNull | crate::ast::Operator::IsNotNull, _) => {}
(
crate::ast::Operator::Between | crate::ast::Operator::NotBetween,
Value::Array(bounds),
) if bounds.len() == 2 => {
write!(f, " {} AND {}", bounds[0], bounds[1])?;
}
(_, value) => write!(f, " {}", value)?,
}
write!(f, " THEN {}", val)?;
}
if let Some(e) = else_value {
write!(f, " ELSE {}", e)?;
}
write!(f, " END")?;
if let Some(a) = alias {
write!(f, " AS {}", a)?;
}
Ok(())
}
Expr::JsonAccess {
column,
path_segments,
alias,
} => {
write!(f, "{}", column)?;
for (segment, as_text) in path_segments {
let op = if *as_text { "->>" } else { "->" };
write!(f, "{}{}", op, segment)?;
}
if let Some(a) = alias {
write!(f, " AS {}", a)?;
}
Ok(())
}
Expr::FunctionCall { name, args, alias } => {
let args_str: Vec<String> = args.iter().map(|a| a.to_string()).collect();
write!(f, "{}({})", name.to_uppercase(), args_str.join(", "))?;
if let Some(a) = alias {
write!(f, " AS {}", a)?;
}
Ok(())
}
Expr::SpecialFunction { name, args, alias } => {
write!(f, "{}(", name.to_uppercase())?;
for (i, (keyword, expr)) in args.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
if let Some(kw) = keyword {
write!(f, "{} ", kw)?;
}
write!(f, "{}", expr)?;
}
write!(f, ")")?;
if let Some(a) = alias {
write!(f, " AS {}", a)?;
}
Ok(())
}
Expr::Binary {
left,
op,
right,
alias,
} => {
if op.is_postfix() {
write!(f, "({} {})", left, op)?;
} else {
write!(f, "{}", op.infix_sql(&left.to_string(), &right.to_string()))?;
}
if let Some(a) = alias {
write!(f, " AS {}", a)?;
}
Ok(())
}
Expr::Literal(value) => write!(f, "{}", value),
Expr::ArrayConstructor { elements, alias } => {
write!(f, "ARRAY[")?;
for (i, elem) in elements.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", elem)?;
}
write!(f, "]")?;
if let Some(a) = alias {
write!(f, " AS {}", a)?;
}
Ok(())
}
Expr::RowConstructor { elements, alias } => {
write!(f, "ROW(")?;
for (i, elem) in elements.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", elem)?;
}
write!(f, ")")?;
if let Some(a) = alias {
write!(f, " AS {}", a)?;
}
Ok(())
}
Expr::Subscript { expr, index, alias } => {
if expr.needs_parens_for_subscript() {
write!(f, "({})[{}]", expr, index)?;
} else {
write!(f, "{}[{}]", expr, index)?;
}
if let Some(a) = alias {
write!(f, " AS {}", a)?;
}
Ok(())
}
Expr::ArraySlice {
expr,
lower,
upper,
alias,
} => {
if expr.needs_parens_for_subscript() {
write!(f, "({})[", expr)?;
} else {
write!(f, "{}[", expr)?;
}
if let Some(lower) = lower {
write!(f, "{}", lower)?;
}
write!(f, ":")?;
if let Some(upper) = upper {
write!(f, "{}", upper)?;
}
write!(f, "]")?;
if let Some(a) = alias {
write!(f, " AS {}", a)?;
}
Ok(())
}
Expr::Collate {
expr,
collation,
alias,
} => {
write!(f, "{} COLLATE \"{}\"", expr, collation)?;
if let Some(a) = alias {
write!(f, " AS {}", a)?;
}
Ok(())
}
Expr::FieldAccess { expr, field, alias } => {
write!(f, "({}).{}", expr, field)?;
if let Some(a) = alias {
write!(f, " AS {}", a)?;
}
Ok(())
}
Expr::Subquery { query, alias } => {
write!(f, "({})", query)?;
if let Some(a) = alias {
write!(f, " AS {}", a)?;
}
Ok(())
}
Expr::Exists {
query,
negated,
alias,
} => {
if *negated {
write!(f, "NOT ")?;
}
write!(f, "EXISTS ({})", query)?;
if let Some(a) = alias {
write!(f, " AS {}", a)?;
}
Ok(())
}
Expr::Default => write!(f, "DEFAULT"),
Expr::FunctionArg {
name,
variadic,
value,
} => {
if *variadic {
write!(f, "VARIADIC ")?;
}
if let Some(name) = name {
write!(f, "{} => ", name)?;
}
write!(f, "{}", value)
}
}
}
}
impl Expr {
pub fn alias_name(&self) -> Option<&str> {
match self {
Expr::Aliased { alias, .. } => Some(alias),
Expr::Window { name, .. } => Some(name),
Expr::Aggregate { alias, .. }
| Expr::Cast { alias, .. }
| Expr::Case { alias, .. }
| Expr::JsonAccess { alias, .. }
| Expr::FunctionCall { alias, .. }
| Expr::SpecialFunction { alias, .. }
| Expr::Binary { alias, .. }
| Expr::ArrayConstructor { alias, .. }
| Expr::RowConstructor { alias, .. }
| Expr::Subscript { alias, .. }
| Expr::ArraySlice { alias, .. }
| Expr::Collate { alias, .. }
| Expr::FieldAccess { alias, .. }
| Expr::Subquery { alias, .. }
| Expr::Exists { alias, .. } => alias.as_deref(),
Expr::Star
| Expr::Named(_)
| Expr::Def { .. }
| Expr::Mod { .. }
| Expr::Literal(_)
| Expr::Default
| Expr::FunctionArg { .. } => None,
}
}
pub fn set_alias(&mut self, alias: impl Into<String>) -> bool {
let alias = alias.into();
let slot = match self {
Expr::Named(name) => {
let name = std::mem::take(name);
*self = Expr::Aliased { name, alias };
return true;
}
Expr::Window { name, .. } => {
*name = alias;
return true;
}
Expr::Aggregate { alias, .. }
| Expr::Cast { alias, .. }
| Expr::Case { alias, .. }
| Expr::JsonAccess { alias, .. }
| Expr::FunctionCall { alias, .. }
| Expr::SpecialFunction { alias, .. }
| Expr::Binary { alias, .. }
| Expr::ArrayConstructor { alias, .. }
| Expr::RowConstructor { alias, .. }
| Expr::Subscript { alias, .. }
| Expr::ArraySlice { alias, .. }
| Expr::Collate { alias, .. }
| Expr::FieldAccess { alias, .. }
| Expr::Subquery { alias, .. }
| Expr::Exists { alias, .. } => alias,
Expr::Star
| Expr::Aliased { .. }
| Expr::Def { .. }
| Expr::Mod { .. }
| Expr::Literal(_)
| Expr::Default
| Expr::FunctionArg { .. } => return false,
};
*slot = Some(alias);
true
}
pub fn needs_parens_for_subscript(&self) -> bool {
match self {
Expr::Named(name) => !is_subscriptable_name(name),
Expr::Subscript { .. } | Expr::ArraySlice { .. } => false,
_ => true,
}
}
}
fn is_subscriptable_name(name: &str) -> bool {
if let Some(digits) = name.strip_prefix('$') {
return !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit());
}
!name.is_empty()
&& name.split('.').all(|part| {
let mut chars = part.chars();
matches!(chars.next(), Some(ch) if ch.is_alphabetic() || ch == '_' || ch == '"')
&& chars.all(|ch| ch.is_alphanumeric() || ch == '_' || ch == '"')
})
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Constraint {
PrimaryKey,
Unique,
Nullable,
Default(String),
Check(Vec<String>),
Comment(String),
References(String),
Generated(ColumnGeneration),
Collate(String),
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum ColumnGeneration {
Stored(String),
Virtual(String),
Identity {
by_default: bool,
options: IdentityOptions,
},
}
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct IdentityOptions {
#[serde(default)]
pub start: Option<i64>,
#[serde(default)]
pub increment: Option<i64>,
#[serde(default)]
pub min_value: Option<i64>,
#[serde(default)]
pub max_value: Option<i64>,
#[serde(default)]
pub cache: Option<i64>,
#[serde(default)]
pub cycle: bool,
}
impl IdentityOptions {
pub fn is_empty(&self) -> bool {
*self == Self::default()
}
pub fn to_sql(&self) -> String {
let mut parts = Vec::new();
if let Some(v) = self.start {
parts.push(format!("START WITH {v}"));
}
if let Some(v) = self.increment {
parts.push(format!("INCREMENT BY {v}"));
}
if let Some(v) = self.min_value {
parts.push(format!("MINVALUE {v}"));
}
if let Some(v) = self.max_value {
parts.push(format!("MAXVALUE {v}"));
}
if let Some(v) = self.cache {
parts.push(format!("CACHE {v}"));
}
if self.cycle {
parts.push("CYCLE".to_string());
}
if parts.is_empty() {
String::new()
} else {
format!("({})", parts.join(" "))
}
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum WindowFrame {
Rows {
start: FrameBound,
end: FrameBound,
#[serde(default, skip_serializing_if = "FrameExclusion::is_no_others")]
exclude: FrameExclusion,
},
Range {
start: FrameBound,
end: FrameBound,
#[serde(default, skip_serializing_if = "FrameExclusion::is_no_others")]
exclude: FrameExclusion,
},
Groups {
start: FrameBound,
end: FrameBound,
#[serde(default, skip_serializing_if = "FrameExclusion::is_no_others")]
exclude: FrameExclusion,
},
}
impl WindowFrame {
pub fn parts(&self) -> (&'static str, &FrameBound, &FrameBound, FrameExclusion) {
match self {
WindowFrame::Rows {
start,
end,
exclude,
} => ("ROWS", start, end, *exclude),
WindowFrame::Range {
start,
end,
exclude,
} => ("RANGE", start, end, *exclude),
WindowFrame::Groups {
start,
end,
exclude,
} => ("GROUPS", start, end, *exclude),
}
}
pub fn to_sql(&self) -> Result<String, &'static str> {
let (mode, start, end, exclude) = self.parts();
for bound in [start, end] {
match bound {
FrameBound::Preceding(n) | FrameBound::Following(n) if *n < 0 => {
return Err("frame offset must not be negative");
}
FrameBound::IntervalPreceding { amount, .. }
| FrameBound::IntervalFollowing { amount, .. } => {
if mode != "RANGE" {
return Err("interval frame offsets require RANGE");
}
if *amount < 0 {
return Err("frame offset must not be negative");
}
}
_ => {}
}
}
if matches!(start, FrameBound::UnboundedFollowing) {
return Err("frame start cannot be UNBOUNDED FOLLOWING");
}
if matches!(end, FrameBound::UnboundedPreceding) {
return Err("frame end cannot be UNBOUNDED PRECEDING");
}
let mut sql = format!("{mode} BETWEEN {} AND {}", start.to_sql(), end.to_sql());
if let Some(exclusion) = exclude.sql_suffix() {
sql.push(' ');
sql.push_str(exclusion);
}
Ok(sql)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum FrameBound {
UnboundedPreceding,
Preceding(i32),
CurrentRow,
Following(i32),
UnboundedFollowing,
IntervalPreceding {
amount: i64,
unit: crate::ast::values::IntervalUnit,
},
IntervalFollowing {
amount: i64,
unit: crate::ast::values::IntervalUnit,
},
}
impl FrameBound {
pub fn to_sql(&self) -> String {
match self {
FrameBound::UnboundedPreceding => "UNBOUNDED PRECEDING".to_string(),
FrameBound::Preceding(n) => format!("{n} PRECEDING"),
FrameBound::CurrentRow => "CURRENT ROW".to_string(),
FrameBound::Following(n) => format!("{n} FOLLOWING"),
FrameBound::UnboundedFollowing => "UNBOUNDED FOLLOWING".to_string(),
FrameBound::IntervalPreceding { amount, unit } => {
format!("INTERVAL '{amount} {unit}' PRECEDING")
}
FrameBound::IntervalFollowing { amount, unit } => {
format!("INTERVAL '{amount} {unit}' FOLLOWING")
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub enum FrameExclusion {
#[default]
NoOthers,
CurrentRow,
Group,
Ties,
}
impl FrameExclusion {
pub fn is_no_others(&self) -> bool {
matches!(self, FrameExclusion::NoOthers)
}
pub fn sql_suffix(self) -> Option<&'static str> {
match self {
FrameExclusion::NoOthers => None,
FrameExclusion::CurrentRow => Some("EXCLUDE CURRENT ROW"),
FrameExclusion::Group => Some("EXCLUDE GROUP"),
FrameExclusion::Ties => Some("EXCLUDE TIES"),
}
}
}
impl std::fmt::Display for Constraint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Constraint::PrimaryKey => write!(f, "pk"),
Constraint::Unique => write!(f, "uniq"),
Constraint::Nullable => write!(f, "?"),
Constraint::Default(val) => write!(f, "={}", val),
Constraint::Check(vals) => write!(f, "check({})", vals.join(",")),
Constraint::Comment(text) => write!(f, "comment(\"{}\")", text),
Constraint::References(target) => write!(f, "ref({})", target),
Constraint::Generated(generation) => match generation {
ColumnGeneration::Stored(expr) => write!(f, "gen({})", expr),
ColumnGeneration::Virtual(expr) => write!(f, "vgen({})", expr),
ColumnGeneration::Identity {
by_default,
options,
} => write!(
f,
"identity({}{})",
if *by_default { "by_default" } else { "always" },
if options.is_empty() {
String::new()
} else {
format!(" {}", options.to_sql())
}
),
},
Constraint::Collate(name) => write!(f, "collate({})", name),
}
}
}
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct IndexDef {
pub name: String,
pub table: String,
pub columns: Vec<String>,
pub unique: bool,
pub index_type: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub include: Vec<String>,
#[serde(default)]
pub concurrently: bool,
pub where_clause: Option<String>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub nulls_not_distinct: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub storage_params: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ForeignKeyOptions {
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub match_full: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub on_delete_columns: Vec<String>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub not_valid: bool,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub not_enforced: bool,
}
impl ForeignKeyOptions {
pub fn is_default(&self) -> bool {
*self == Self::default()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ViewCheckOption {
Local,
Cascaded,
}
impl ViewCheckOption {
pub fn as_sql(self) -> &'static str {
match self {
ViewCheckOption::Local => "LOCAL",
ViewCheckOption::Cascaded => "CASCADED",
}
}
}
pub fn view_with_clause(security_invoker: bool, security_barrier: bool) -> String {
let mut opts = Vec::new();
if security_invoker {
opts.push("security_invoker = true");
}
if security_barrier {
opts.push("security_barrier = true");
}
if opts.is_empty() {
String::new()
} else {
format!(" WITH ({})", opts.join(", "))
}
}
pub fn view_check_option_clause(check_option: Option<ViewCheckOption>) -> String {
match check_option {
Some(option) => format!(" WITH {} CHECK OPTION", option.as_sql()),
None => String::new(),
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum TableConstraint {
Unique(Vec<String>),
PrimaryKey(Vec<String>),
TemporalKey {
name: Option<String>,
primary: bool,
columns: Vec<String>,
period: String,
},
ForeignKey {
name: Option<String>,
columns: Vec<String>,
ref_table: String,
ref_columns: Vec<String>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
period: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
on_delete: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
on_update: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
deferrable: Option<String>,
#[serde(default, skip_serializing_if = "ForeignKeyOptions::is_default")]
options: ForeignKeyOptions,
},
Check {
name: Option<String>,
expr: String,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
not_valid: bool,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
not_enforced: bool,
},
Exclude {
name: String,
definition: String,
},
}
impl From<&str> for Expr {
fn from(s: &str) -> Self {
Expr::Named(s.to_string())
}
}
impl From<String> for Expr {
fn from(s: String) -> Self {
Expr::Named(s)
}
}
impl From<&String> for Expr {
fn from(s: &String) -> Self {
Expr::Named(s.clone())
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FunctionDef {
pub name: String,
pub args: Vec<String>,
pub returns: String,
pub body: String,
pub language: Option<String>,
pub volatility: Option<String>,
#[serde(default, skip_serializing_if = "FunctionOptions::is_empty")]
pub options: FunctionOptions,
}
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FunctionOptions {
#[serde(default)]
pub strict: bool,
#[serde(default)]
pub security_definer: bool,
#[serde(default)]
pub leakproof: bool,
#[serde(default)]
pub parallel: Option<String>,
#[serde(default)]
pub cost: Option<String>,
#[serde(default)]
pub rows: Option<String>,
#[serde(default)]
pub config: Vec<String>,
}
impl FunctionOptions {
pub fn is_empty(&self) -> bool {
*self == Self::default()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum TriggerTiming {
Before,
After,
InsteadOf,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum TriggerEvent {
Insert,
Update,
Delete,
Truncate,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct TriggerDef {
pub name: String,
pub table: String,
pub timing: TriggerTiming,
pub events: Vec<TriggerEvent>,
pub update_columns: Vec<String>,
pub for_each_row: bool,
pub execute_function: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub condition: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub old_table: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub new_table: Option<String>,
}