use crate::value::Value;
#[cfg(feature = "json")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
pub const EMPTY: Span = Span { start: 0, end: 0 };
pub const fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
}
#[cfg(feature = "json")]
impl Serialize for Span {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
[self.start, self.end].serialize(serializer)
}
}
#[cfg(feature = "json")]
impl<'de> Deserialize<'de> for Span {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let [start, end] = <[usize; 2]>::deserialize(deserializer)?;
Ok(Span { start, end })
}
}
macro_rules! serde_as_word {
($ty:ident) => {
#[cfg(feature = "json")]
impl Serialize for $ty {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
#[cfg(feature = "json")]
impl<'de> Deserialize<'de> for $ty {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let word = String::deserialize(deserializer)?;
$ty::from_word(&word)
.ok_or_else(|| serde::de::Error::unknown_variant(&word, <$ty>::WORDS))
}
}
};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Consumer {
Collect,
Exists,
None,
Count,
First,
Single,
}
impl Consumer {
const WORDS: &'static [&'static str] =
&["collect", "exists", "none", "count", "first", "single"];
pub fn as_str(self) -> &'static str {
match self {
Consumer::Collect => "collect",
Consumer::Exists => "exists",
Consumer::None => "none",
Consumer::Count => "count",
Consumer::First => "first",
Consumer::Single => "single",
}
}
pub fn from_word(word: &str) -> Option<Self> {
Some(match word {
"collect" => Consumer::Collect,
"exists" => Consumer::Exists,
"none" => Consumer::None,
"count" => Consumer::Count,
"first" => Consumer::First,
"single" => Consumer::Single,
_ => return Option::None,
})
}
}
serde_as_word!(Consumer);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum RelOp {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
}
impl RelOp {
const WORDS: &'static [&'static str] = &["==", "!=", "<", "<=", ">", ">="];
pub fn as_str(self) -> &'static str {
match self {
RelOp::Eq => "==",
RelOp::Ne => "!=",
RelOp::Lt => "<",
RelOp::Le => "<=",
RelOp::Gt => ">",
RelOp::Ge => ">=",
}
}
pub fn from_word(word: &str) -> Option<Self> {
Some(match word {
"==" => RelOp::Eq,
"!=" => RelOp::Ne,
"<" => RelOp::Lt,
"<=" => RelOp::Le,
">" => RelOp::Gt,
">=" => RelOp::Ge,
_ => return None,
})
}
}
serde_as_word!(RelOp);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BinaryOp {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
Is,
IsNot,
Add,
Sub,
Mul,
Div,
Mod,
}
impl BinaryOp {
const WORDS: &'static [&'static str] = &[
"==", "!=", "<", "<=", ">", ">=", "is", "is not", "+", "-", "*", "/", "%",
];
pub fn as_str(self) -> &'static str {
match self {
BinaryOp::Eq => "==",
BinaryOp::Ne => "!=",
BinaryOp::Lt => "<",
BinaryOp::Le => "<=",
BinaryOp::Gt => ">",
BinaryOp::Ge => ">=",
BinaryOp::Is => "is",
BinaryOp::IsNot => "is not",
BinaryOp::Add => "+",
BinaryOp::Sub => "-",
BinaryOp::Mul => "*",
BinaryOp::Div => "/",
BinaryOp::Mod => "%",
}
}
pub fn from_word(word: &str) -> Option<Self> {
Some(match word {
"==" => BinaryOp::Eq,
"!=" => BinaryOp::Ne,
"<" => BinaryOp::Lt,
"<=" => BinaryOp::Le,
">" => BinaryOp::Gt,
">=" => BinaryOp::Ge,
"is" => BinaryOp::Is,
"is not" => BinaryOp::IsNot,
"+" => BinaryOp::Add,
"-" => BinaryOp::Sub,
"*" => BinaryOp::Mul,
"/" => BinaryOp::Div,
"%" => BinaryOp::Mod,
_ => return None,
})
}
pub fn is_comparison(self) -> bool {
matches!(
self,
BinaryOp::Eq | BinaryOp::Ne | BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge
)
}
pub fn is_identity(self) -> bool {
matches!(self, BinaryOp::Is | BinaryOp::IsNot)
}
}
serde_as_word!(BinaryOp);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum UnaryOp {
Not,
Neg,
}
impl UnaryOp {
const WORDS: &'static [&'static str] = &["!", "-"];
pub fn as_str(self) -> &'static str {
match self {
UnaryOp::Not => "!",
UnaryOp::Neg => "-",
}
}
pub fn from_word(word: &str) -> Option<Self> {
Some(match word {
"!" => UnaryOp::Not,
"-" => UnaryOp::Neg,
_ => return None,
})
}
}
serde_as_word!(UnaryOp);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum LogicalOp {
And,
Or,
}
impl LogicalOp {
const WORDS: &'static [&'static str] = &["&&", "||"];
pub fn as_str(self) -> &'static str {
match self {
LogicalOp::And => "&&",
LogicalOp::Or => "||",
}
}
pub fn from_word(word: &str) -> Option<Self> {
Some(match word {
"&&" => LogicalOp::And,
"||" => LogicalOp::Or,
_ => return None,
})
}
}
serde_as_word!(LogicalOp);
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "json",
derive(Serialize, Deserialize),
serde(tag = "kind", rename_all = "camelCase")
)]
pub enum Expr {
Lit {
#[cfg_attr(feature = "json", serde(with = "json_lit"))]
value: Value,
span: Span,
},
Ident { name: String, span: Span },
Outer {
levels: usize,
name: String,
span: Span,
},
Binding { index: usize, span: Span },
Member {
recv: Box<Expr>,
name: String,
span: Span,
},
Call {
recv: Option<Box<Expr>>,
name: String,
args: Vec<Expr>,
span: Span,
},
Unary {
op: UnaryOp,
expr: Box<Expr>,
span: Span,
},
Required { expr: Box<Expr>, span: Span },
Binary {
op: BinaryOp,
left: Box<Expr>,
right: Box<Expr>,
span: Span,
},
Logical {
op: LogicalOp,
left: Box<Expr>,
right: Box<Expr>,
span: Span,
},
In {
left: Box<Expr>,
right: Box<Expr>,
span: Span,
},
#[cfg_attr(feature = "json", serde(rename_all = "camelCase"))]
Range {
lo: Option<Box<Expr>>,
hi: Option<Box<Expr>>,
exclusive_end: bool,
span: Span,
},
#[cfg_attr(feature = "json", serde(untagged))]
Op(Box<OpNode>),
}
impl Expr {
pub fn span(&self) -> Span {
match self {
Expr::Lit { span, .. }
| Expr::Ident { span, .. }
| Expr::Outer { span, .. }
| Expr::Binding { span, .. }
| Expr::Member { span, .. }
| Expr::Call { span, .. }
| Expr::Unary { span, .. }
| Expr::Required { span, .. }
| Expr::Binary { span, .. }
| Expr::Logical { span, .. }
| Expr::In { span, .. }
| Expr::Range { span, .. } => *span,
Expr::Op(op) => op.span,
}
}
pub fn with_span(mut self, new: Span) -> Self {
*self.span_mut() = new;
self
}
pub(crate) fn span_mut(&mut self) -> &mut Span {
match self {
Expr::Lit { span, .. }
| Expr::Ident { span, .. }
| Expr::Outer { span, .. }
| Expr::Binding { span, .. }
| Expr::Member { span, .. }
| Expr::Call { span, .. }
| Expr::Unary { span, .. }
| Expr::Required { span, .. }
| Expr::Binary { span, .. }
| Expr::Logical { span, .. }
| Expr::In { span, .. }
| Expr::Range { span, .. } => span,
Expr::Op(op) => &mut op.span,
}
}
pub fn kind(&self) -> &'static str {
match self {
Expr::Lit { .. } => "lit",
Expr::Ident { .. } => "ident",
Expr::Outer { .. } => "outer",
Expr::Binding { .. } => "binding",
Expr::Member { .. } => "member",
Expr::Call { .. } => "call",
Expr::Unary { .. } => "unary",
Expr::Required { .. } => "required",
Expr::Op(_) => "op",
Expr::Binary { .. } => "binary",
Expr::Logical { .. } => "logical",
Expr::In { .. } => "in",
Expr::Range { .. } => "range",
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "json",
derive(Serialize, Deserialize),
serde(tag = "kind", rename = "order")
)]
pub struct OrderSpec {
pub expr: Expr,
pub desc: bool,
pub span: Span,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "json",
derive(Serialize, Deserialize),
serde(tag = "kind", rename_all = "camelCase")
)]
pub enum SelectItem {
Field {
name: String,
expr: Expr,
lift: usize,
span: Span,
},
Collect {
name: String,
op: Box<OpNode>,
span: Span,
},
}
impl SelectItem {
pub fn name(&self) -> &str {
match self {
SelectItem::Field { name, .. } | SelectItem::Collect { name, .. } => name,
}
}
pub fn span(&self) -> Span {
match self {
SelectItem::Field { span, .. } | SelectItem::Collect { span, .. } => *span,
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct CountCmp {
pub op: RelOp,
#[cfg_attr(feature = "json", serde(with = "json_number"))]
pub value: f64,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "json",
derive(Serialize, Deserialize),
serde(tag = "kind", rename = "op", rename_all = "camelCase")
)]
pub struct OpNode {
pub receiver: Expr,
pub op: Consumer,
pub sub: Subquery,
pub count_cmp: Option<CountCmp>,
pub distinct: bool,
pub span: Span,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize), serde(untagged))]
pub enum FollowDestination {
Relation(Expr),
Block(Box<OpNode>),
}
impl FollowDestination {
pub fn span(&self) -> Span {
match self {
FollowDestination::Relation(e) => e.span(),
FollowDestination::Block(op) => op.span,
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "json",
derive(Serialize, Deserialize),
serde(tag = "kind", rename = "follow")
)]
pub struct Follow {
pub destinations: Vec<FollowDestination>,
pub distinct: bool,
#[cfg_attr(feature = "json", serde(rename = "where"))]
pub r#where: Option<Expr>,
pub frontier: Option<Expr>,
pub depth: Option<u32>,
pub by: Option<Expr>,
pub span: Span,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "json",
derive(Serialize, Deserialize),
serde(tag = "kind", rename = "subquery", rename_all = "camelCase")
)]
pub struct Subquery {
pub from: Vec<Expr>,
#[cfg_attr(feature = "json", serde(rename = "where"))]
pub r#where: Option<Where>,
pub select: Vec<SelectItem>,
pub order_by: Option<Vec<OrderSpec>>,
pub follow: Option<Follow>,
pub values: bool,
pub limit: Option<Expr>,
pub offset: Option<Expr>,
pub span: Span,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "json",
derive(Serialize, Deserialize),
serde(tag = "kind", rename_all = "camelCase")
)]
pub enum Where {
And {
parts: Vec<Where>,
span: Span,
},
Or {
parts: Vec<Where>,
span: Span,
},
Not {
expr: Box<Where>,
span: Span,
},
Scalar {
expr: Expr,
span: Span,
},
#[cfg_attr(feature = "json", serde(untagged))]
Op(Box<OpNode>),
}
impl Where {
pub fn span(&self) -> Span {
match self {
Where::And { span, .. }
| Where::Or { span, .. }
| Where::Not { span, .. }
| Where::Scalar { span, .. } => *span,
Where::Op(op) => op.span,
}
}
pub(crate) fn with_span(mut self, new: Span) -> Self {
match &mut self {
Where::And { span, .. }
| Where::Or { span, .. }
| Where::Not { span, .. }
| Where::Scalar { span, .. } => *span = new,
Where::Op(op) => op.span = new,
}
self
}
pub fn kind(&self) -> &'static str {
match self {
Where::And { .. } => "and",
Where::Or { .. } => "or",
Where::Not { .. } => "not",
Where::Scalar { .. } => "scalar",
Where::Op(_) => "op",
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "json",
derive(Serialize, Deserialize),
serde(tag = "kind", rename = "query", rename_all = "camelCase")
)]
pub struct Query {
pub source: Expr,
pub from: Vec<Expr>,
#[cfg_attr(feature = "json", serde(rename = "where"))]
pub r#where: Option<Where>,
pub select: Vec<SelectItem>,
pub order_by: Option<Vec<OrderSpec>>,
pub consumer: Consumer,
pub follow: Option<Follow>,
pub distinct: bool,
pub values: bool,
pub limit: Option<Expr>,
pub offset: Option<Expr>,
pub span: Span,
}
#[cfg(feature = "json")]
mod json_lit {
use super::Value;
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(v: &Value, s: S) -> Result<S::Ok, S::Error> {
match v {
Value::Str(x) => s.serialize_str(x),
Value::Number(n) => super::json_number::serialize(n, s),
Value::Bool(b) => s.serialize_bool(*b),
Value::Null | Value::Undefined => s.serialize_unit(),
other => Err(serde::ser::Error::custom(format!(
"a literal is a string, number, boolean or null, not {other:?}"
))),
}
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Value, D::Error> {
let j = serde_json::Value::deserialize(d)?;
match j {
serde_json::Value::Null => Ok(Value::Null),
serde_json::Value::Bool(b) => Ok(Value::Bool(b)),
serde_json::Value::Number(n) => Ok(Value::Number(n.as_f64().unwrap_or(f64::NAN))),
serde_json::Value::String(s) => Ok(Value::Str(s)),
other => Err(serde::de::Error::custom(format!(
"a literal is a string, number, boolean or null, not {other}"
))),
}
}
}
#[cfg(feature = "json")]
mod json_number {
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(n: &f64, s: S) -> Result<S::Ok, S::Error> {
const MAX_SAFE_INTEGER: f64 = 9_007_199_254_740_992.0; if n.fract() == 0.0 && n.abs() < MAX_SAFE_INTEGER {
s.serialize_i64(*n as i64)
} else {
s.serialize_f64(*n)
}
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<f64, D::Error> {
f64::deserialize(d)
}
}