use std::ops::Range;
use crate::lexer::CompOp;
#[derive(Debug, Clone, Copy, Default)]
pub struct Span {
bytes: Option<(u32, u32)>,
}
impl Span {
#[must_use]
pub fn new(bytes: Range<usize>) -> Self {
let start = u32::try_from(bytes.start).ok();
let end = u32::try_from(bytes.end).ok();
Self {
bytes: start.zip(end),
}
}
#[must_use]
pub fn bytes(&self) -> Option<Range<usize>> {
let (start, end) = self.bytes?;
let start = usize::try_from(start).ok()?;
let end = usize::try_from(end).ok()?;
Some(start..end)
}
}
impl PartialEq for Span {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for Span {}
#[derive(Debug, Clone, PartialEq)]
pub struct SelectQuery {
pub select: SelectClause,
pub from: ContainsExpr,
pub where_: Option<WhereExpr>,
pub order_by: Vec<OrderByExpr>,
pub limit: Option<Limit>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SelectClause {
pub distinct: bool,
pub top: Option<Top>,
pub columns: Vec<SelectExpr>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Top {
pub count: i64,
pub direction: Option<TopDirection>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TopDirection {
Forward,
Backward,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SelectExpr {
pub column: ColumnExpr,
pub alias: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ColumnExpr {
Path(IdentifiedPath),
Primitive(Primitive),
Aggregate(AggregateCall),
Function(FunctionCall),
}
#[derive(Debug, Clone, PartialEq)]
pub enum ContainsExpr {
Contained {
operand: ClassExprOperand,
contains: Option<Box<ContainsConstraint>>,
},
And(Box<ContainsExpr>, Box<ContainsExpr>),
Or(Box<ContainsExpr>, Box<ContainsExpr>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct ContainsConstraint {
pub negated: bool,
pub expr: ContainsExpr,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ClassExprOperand {
Class {
rm_type: String,
variable: Option<String>,
predicate: Option<PathPredicate>,
},
Version {
variable: Option<String>,
predicate: Option<VersionPredicate>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum WhereExpr {
Identified(IdentifiedExpr, Span),
Not(Box<WhereExpr>),
And(Box<WhereExpr>, Box<WhereExpr>),
Or(Box<WhereExpr>, Box<WhereExpr>),
}
impl WhereExpr {
#[must_use]
pub fn identified(expr: IdentifiedExpr) -> Self {
Self::Identified(expr, Span::default())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum IdentifiedExpr {
Exists(IdentifiedPath),
Compare {
lhs: CompareOperand,
op: CompOp,
rhs: Terminal,
},
Like {
path: IdentifiedPath,
operand: LikeOperand,
},
Matches {
path: IdentifiedPath,
operand: MatchesOperand,
},
Resolved(bool),
}
#[derive(Debug, Clone, PartialEq)]
pub enum CompareOperand {
Path(IdentifiedPath),
Function(FunctionCall),
}
#[derive(Debug, Clone, PartialEq)]
pub enum Terminal {
Primitive(Primitive),
Parameter(String),
Path(IdentifiedPath),
Function(FunctionCall),
}
#[derive(Debug, Clone, PartialEq)]
pub struct OrderByExpr {
pub path: IdentifiedPath,
pub order: Option<SortOrder>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SortOrder {
Ascending,
Descending,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Limit {
pub limit: i64,
pub offset: Option<i64>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct IdentifiedPath {
pub root: String,
pub predicate: Option<PathPredicate>,
pub path: Option<ObjectPath>,
pub span: Span,
}
impl IdentifiedPath {
#[must_use]
pub fn new(root: String, predicate: Option<PathPredicate>, path: Option<ObjectPath>) -> Self {
Self {
root,
predicate,
path,
span: Span::default(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ObjectPath {
pub parts: Vec<PathPart>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PathPart {
pub name: String,
pub predicate: Option<PathPredicate>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PathPredicate {
Standard(Box<StandardPredicate>),
Archetype(ArchetypePredicate),
Node(Box<NodePredicate>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct StandardPredicate {
pub path: ObjectPath,
pub op: CompOp,
pub operand: PathPredicateOperand,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ArchetypePredicate {
Hrid(String),
Parameter(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum NodePredicate {
Code {
code: String,
name: Option<NodeNameConstraint>,
},
Archetype {
hrid: String,
name: Option<NodeNameConstraint>,
},
Parameter(String),
Standard(Box<StandardPredicate>),
MatchesRegex {
path: ObjectPath,
regex: String,
},
And(Box<NodePredicate>, Box<NodePredicate>),
Or(Box<NodePredicate>, Box<NodePredicate>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum NodeNameConstraint {
String(String),
Parameter(String),
TermCode(String),
Code(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum VersionPredicate {
Latest,
All,
Standard(Box<StandardPredicate>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum PathPredicateOperand {
Primitive(Primitive),
Path(ObjectPath),
Parameter(String),
Code(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum LikeOperand {
String(String),
Parameter(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum MatchesOperand {
ValueList(Vec<ValueListItem>),
Terminology(TerminologyFunction),
Uri(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum ValueListItem {
Primitive(Primitive),
Parameter(String),
Terminology(TerminologyFunction),
}
#[derive(Debug, Clone, PartialEq)]
pub enum FunctionCall {
Builtin {
function: BuiltinFunction,
name: String,
args: Vec<Terminal>,
},
Other {
name: String,
args: Vec<Terminal>,
},
Terminology(TerminologyFunction),
}
impl FunctionCall {
#[must_use]
pub fn named(name: String, args: Vec<Terminal>) -> Self {
match BuiltinFunction::from_name(&name) {
Some(function) => Self::Builtin {
function,
name,
args,
},
None => Self::Other { name, args },
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BuiltinFunction {
String(StringFunction),
Numeric(NumericFunction),
DateTime(DateTimeFunction),
}
impl BuiltinFunction {
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
StringFunction::from_name(name)
.map(Self::String)
.or_else(|| NumericFunction::from_name(name).map(Self::Numeric))
.or_else(|| DateTimeFunction::from_name(name).map(Self::DateTime))
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::String(f) => f.as_str(),
Self::Numeric(f) => f.as_str(),
Self::DateTime(f) => f.as_str(),
}
}
}
macro_rules! function_group {
($(#[$meta:meta])* $name:ident { $($(#[$vmeta:meta])* $variant:ident => $spelling:literal,)+ }) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum $name {
$($(#[$vmeta])* $variant,)+
}
impl $name {
pub const ALL: &[Self] = &[$(Self::$variant,)+];
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
Self::ALL
.iter()
.copied()
.find(|f| f.as_str().eq_ignore_ascii_case(name))
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
$(Self::$variant => $spelling,)+
}
}
}
};
}
function_group! {
StringFunction {
Length => "LENGTH",
Contains => "CONTAINS",
Position => "POSITION",
Substring => "SUBSTRING",
Concat => "CONCAT",
ConcatWs => "CONCAT_WS",
}
}
function_group! {
NumericFunction {
Abs => "ABS",
Mod => "MOD",
Ceil => "CEIL",
Floor => "FLOOR",
Round => "ROUND",
}
}
function_group! {
DateTimeFunction {
CurrentDate => "CURRENT_DATE",
CurrentTime => "CURRENT_TIME",
CurrentDateTime => "CURRENT_DATE_TIME",
Now => "NOW",
CurrentTimezone => "CURRENT_TIMEZONE",
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TerminologyFunction {
pub operation: String,
pub arg2: String,
pub arg3: String,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AggregateCall {
Count {
distinct: bool,
path: Option<IdentifiedPath>,
},
Stat {
func: StatFunc,
path: IdentifiedPath,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StatFunc {
Min,
Max,
Sum,
Avg,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Primitive {
String(String),
Integer(i64),
Real(f64),
Boolean(bool),
Null,
}
use std::fmt;
impl IdentifiedPath {
#[must_use]
pub fn column_path_text(&self) -> String {
match &self.path {
None => "/".to_owned(),
Some(p) => format!("/{p}"),
}
}
}
impl fmt::Display for ObjectPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, part) in self.parts.iter().enumerate() {
if i > 0 {
f.write_str("/")?;
}
write!(f, "{part}")?;
}
Ok(())
}
}
impl fmt::Display for PathPart {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.name)?;
if let Some(p) = &self.predicate {
write!(f, "[{p}]")?;
}
Ok(())
}
}
impl fmt::Display for PathPredicate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PathPredicate::Standard(s) => write!(f, "{s}"),
PathPredicate::Archetype(ArchetypePredicate::Hrid(h)) => f.write_str(h),
PathPredicate::Archetype(ArchetypePredicate::Parameter(p)) => f.write_str(p),
PathPredicate::Node(n) => write!(f, "{n}"),
}
}
}
impl fmt::Display for StandardPredicate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}{}", self.path, comp_op_text(self.op), self.operand)
}
}
#[must_use]
pub fn comp_op_text(op: CompOp) -> &'static str {
match op {
CompOp::Eq => "=",
CompOp::Ne => "!=",
CompOp::Gt => ">",
CompOp::Ge => ">=",
CompOp::Lt => "<",
CompOp::Le => "<=",
}
}
impl fmt::Display for NodePredicate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NodePredicate::Code { code, name } | NodePredicate::Archetype { hrid: code, name } => {
f.write_str(code)?;
if let Some(n) = name {
write!(f, ",{n}")?;
}
Ok(())
}
NodePredicate::Parameter(p) => f.write_str(p),
NodePredicate::Standard(s) => write!(f, "{s}"),
NodePredicate::MatchesRegex { path, regex } => {
write!(f, "{path} matches {regex}")
}
NodePredicate::And(a, b) => write!(f, "{a} and {b}"),
NodePredicate::Or(a, b) => write!(f, "{a} or {b}"),
}
}
}
impl fmt::Display for NodeNameConstraint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NodeNameConstraint::String(s) => {
write!(f, "'{}'", crate::printer::escape_string(s))
}
NodeNameConstraint::Parameter(p) | NodeNameConstraint::Code(p) => f.write_str(p),
NodeNameConstraint::TermCode(t) => f.write_str(t),
}
}
}
impl fmt::Display for PathPredicateOperand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PathPredicateOperand::Primitive(p) => write!(f, "{p}"),
PathPredicateOperand::Path(p) => write!(f, "{p}"),
PathPredicateOperand::Parameter(p) | PathPredicateOperand::Code(p) => f.write_str(p),
}
}
}
impl fmt::Display for Primitive {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Primitive::String(s) => write!(f, "'{}'", crate::printer::escape_string(s)),
Primitive::Integer(i) => write!(f, "{i}"),
Primitive::Real(r) => write!(f, "{r}"),
Primitive::Boolean(b) => write!(f, "{b}"),
Primitive::Null => f.write_str("NULL"),
}
}
}