use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SparqlQuery {
pub base: Option<Iri>,
pub prefixes: HashMap<String, Iri>,
pub body: QueryBody,
}
impl SparqlQuery {
pub fn new(body: QueryBody) -> Self {
Self {
base: None,
prefixes: HashMap::new(),
body,
}
}
pub fn with_base(mut self, base: Iri) -> Self {
self.base = Some(base);
self
}
pub fn with_prefix(mut self, prefix: impl Into<String>, iri: Iri) -> Self {
self.prefixes.insert(prefix.into(), iri);
self
}
}
impl Default for SparqlQuery {
fn default() -> Self {
Self::new(QueryBody::Select(SelectQuery::default()))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum QueryBody {
Select(SelectQuery),
Construct(ConstructQuery),
Ask(AskQuery),
Describe(DescribeQuery),
Update(Vec<UpdateOperation>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum QueryForm {
Select,
Construct,
Ask,
Describe,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SelectQuery {
pub projection: Projection,
pub dataset: Vec<DatasetClause>,
pub where_clause: GraphPattern,
pub modifier: SolutionModifier,
pub values: Option<ValuesClause>,
}
impl Default for SelectQuery {
fn default() -> Self {
Self {
projection: Projection::All,
dataset: Vec::new(),
where_clause: GraphPattern::Empty,
modifier: SolutionModifier::default(),
values: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Projection {
All,
Distinct(Vec<ProjectionVar>),
Reduced(Vec<ProjectionVar>),
Variables(Vec<ProjectionVar>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectionVar {
pub expression: Expression,
pub alias: Option<String>,
}
impl ProjectionVar {
pub fn variable(name: impl Into<String>) -> Self {
Self {
expression: Expression::Variable(name.into()),
alias: None,
}
}
pub fn expr_as(expr: Expression, alias: impl Into<String>) -> Self {
Self {
expression: expr,
alias: Some(alias.into()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConstructQuery {
pub template: Vec<TriplePattern>,
pub dataset: Vec<DatasetClause>,
pub where_clause: GraphPattern,
pub modifier: SolutionModifier,
}
impl Default for ConstructQuery {
fn default() -> Self {
Self {
template: Vec::new(),
dataset: Vec::new(),
where_clause: GraphPattern::Empty,
modifier: SolutionModifier::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AskQuery {
pub dataset: Vec<DatasetClause>,
pub where_clause: GraphPattern,
}
impl Default for AskQuery {
fn default() -> Self {
Self {
dataset: Vec::new(),
where_clause: GraphPattern::Empty,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DescribeQuery {
pub resources: Vec<VarOrIri>,
pub dataset: Vec<DatasetClause>,
pub where_clause: Option<GraphPattern>,
}
impl Default for DescribeQuery {
fn default() -> Self {
Self {
resources: Vec::new(),
dataset: Vec::new(),
where_clause: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatasetClause {
pub iri: Iri,
pub named: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValuesClause {
pub variables: Vec<String>,
pub bindings: Vec<Vec<Option<RdfTerm>>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum GraphPattern {
Empty,
Bgp(Vec<TriplePattern>),
Join(Box<GraphPattern>, Box<GraphPattern>),
LeftJoin(Box<GraphPattern>, Box<GraphPattern>, Option<Expression>),
Union(Box<GraphPattern>, Box<GraphPattern>),
Filter(Box<GraphPattern>, Expression),
Graph(VarOrIri, Box<GraphPattern>),
Service(Iri, Box<GraphPattern>, bool),
Minus(Box<GraphPattern>, Box<GraphPattern>),
Exists(Box<GraphPattern>, bool),
Bind(Expression, String, Box<GraphPattern>),
Group(
Box<GraphPattern>,
Vec<GroupCondition>,
Vec<(Aggregate, String)>,
),
SubSelect(Box<SelectQuery>),
Values(ValuesClause),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TriplePattern {
pub subject: TermOrVariable,
pub predicate: PropertyPath,
pub object: TermOrVariable,
}
impl TriplePattern {
pub fn new(subject: TermOrVariable, predicate: PropertyPath, object: TermOrVariable) -> Self {
Self {
subject,
predicate,
object,
}
}
pub fn simple(subject: TermOrVariable, predicate: Iri, object: TermOrVariable) -> Self {
Self {
subject,
predicate: PropertyPath::Iri(predicate),
object,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TermOrVariable {
Term(RdfTerm),
Variable(String),
BlankNode(String),
}
impl TermOrVariable {
pub fn var(name: impl Into<String>) -> Self {
Self::Variable(name.into())
}
pub fn iri(iri: Iri) -> Self {
Self::Term(RdfTerm::Iri(iri))
}
pub fn literal(value: impl Into<String>) -> Self {
Self::Term(RdfTerm::Literal(Literal::simple(value)))
}
pub fn blank(id: impl Into<String>) -> Self {
Self::BlankNode(id.into())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum VarOrIri {
Variable(String),
Iri(Iri),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PropertyPath {
Iri(Iri),
Variable(String),
Inverse(Box<PropertyPath>),
Sequence(Box<PropertyPath>, Box<PropertyPath>),
Alternative(Box<PropertyPath>, Box<PropertyPath>),
ZeroOrMore(Box<PropertyPath>),
OneOrMore(Box<PropertyPath>),
ZeroOrOne(Box<PropertyPath>),
NegatedPropertySet(Vec<Iri>),
FixedLength(Box<PropertyPath>, usize),
RangeLength(Box<PropertyPath>, usize, Option<usize>),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum RdfTerm {
Iri(Iri),
Literal(Literal),
BlankNode(String),
}
impl RdfTerm {
pub fn iri(value: impl Into<String>) -> Self {
Self::Iri(Iri::new(value))
}
pub fn literal(value: impl Into<String>) -> Self {
Self::Literal(Literal::simple(value))
}
pub fn typed_literal(value: impl Into<String>, datatype: Iri) -> Self {
Self::Literal(Literal::typed(value, datatype))
}
pub fn lang_literal(value: impl Into<String>, lang: impl Into<String>) -> Self {
Self::Literal(Literal::language(value, lang))
}
pub fn blank(id: impl Into<String>) -> Self {
Self::BlankNode(id.into())
}
pub fn is_iri(&self) -> bool {
matches!(self, Self::Iri(_))
}
pub fn is_literal(&self) -> bool {
matches!(self, Self::Literal(_))
}
pub fn is_blank_node(&self) -> bool {
matches!(self, Self::BlankNode(_))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Iri(pub String);
impl Iri {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn rdf_type() -> Self {
Self::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
}
pub fn rdfs_label() -> Self {
Self::new("http://www.w3.org/2000/01/rdf-schema#label")
}
pub fn rdfs_comment() -> Self {
Self::new("http://www.w3.org/2000/01/rdf-schema#comment")
}
pub fn xsd_string() -> Self {
Self::new("http://www.w3.org/2001/XMLSchema#string")
}
pub fn xsd_integer() -> Self {
Self::new("http://www.w3.org/2001/XMLSchema#integer")
}
pub fn xsd_decimal() -> Self {
Self::new("http://www.w3.org/2001/XMLSchema#decimal")
}
pub fn xsd_double() -> Self {
Self::new("http://www.w3.org/2001/XMLSchema#double")
}
pub fn xsd_boolean() -> Self {
Self::new("http://www.w3.org/2001/XMLSchema#boolean")
}
pub fn xsd_date() -> Self {
Self::new("http://www.w3.org/2001/XMLSchema#date")
}
pub fn xsd_datetime() -> Self {
Self::new("http://www.w3.org/2001/XMLSchema#dateTime")
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Literal {
pub value: String,
pub language: Option<String>,
pub datatype: Iri,
}
impl Literal {
pub fn simple(value: impl Into<String>) -> Self {
Self {
value: value.into(),
language: None,
datatype: Iri::xsd_string(),
}
}
pub fn typed(value: impl Into<String>, datatype: Iri) -> Self {
Self {
value: value.into(),
language: None,
datatype,
}
}
pub fn language(value: impl Into<String>, lang: impl Into<String>) -> Self {
Self {
value: value.into(),
language: Some(lang.into()),
datatype: Iri::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#langString"),
}
}
pub fn integer(value: i64) -> Self {
Self::typed(value.to_string(), Iri::xsd_integer())
}
pub fn decimal(value: f64) -> Self {
Self::typed(value.to_string(), Iri::xsd_decimal())
}
pub fn double(value: f64) -> Self {
Self::typed(value.to_string(), Iri::xsd_double())
}
pub fn boolean(value: bool) -> Self {
Self::typed(if value { "true" } else { "false" }, Iri::xsd_boolean())
}
pub fn as_integer(&self) -> Option<i64> {
self.value.parse().ok()
}
pub fn as_double(&self) -> Option<f64> {
self.value.parse().ok()
}
pub fn as_boolean(&self) -> Option<bool> {
match self.value.as_str() {
"true" | "1" => Some(true),
"false" | "0" => Some(false),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Expression {
Variable(String),
Term(RdfTerm),
Binary(Box<Expression>, BinaryOp, Box<Expression>),
Unary(UnaryOp, Box<Expression>),
Function(FunctionCall),
Aggregate(Aggregate),
In(Box<Expression>, Vec<Expression>),
NotIn(Box<Expression>, Vec<Expression>),
Exists(Box<GraphPattern>),
NotExists(Box<GraphPattern>),
If(Box<Expression>, Box<Expression>, Box<Expression>),
Coalesce(Vec<Expression>),
Bound(String),
IsIri(Box<Expression>),
IsBlank(Box<Expression>),
IsLiteral(Box<Expression>),
IsNumeric(Box<Expression>),
Regex(Box<Expression>, Box<Expression>, Option<Box<Expression>>),
Lang(Box<Expression>),
Datatype(Box<Expression>),
Str(Box<Expression>),
Iri(Box<Expression>),
}
impl Expression {
pub fn var(name: impl Into<String>) -> Self {
Self::Variable(name.into())
}
pub fn term(t: RdfTerm) -> Self {
Self::Term(t)
}
pub fn literal(value: impl Into<String>) -> Self {
Self::Term(RdfTerm::literal(value))
}
pub fn integer(value: i64) -> Self {
Self::Term(RdfTerm::Literal(Literal::integer(value)))
}
pub fn binary(left: Expression, op: BinaryOp, right: Expression) -> Self {
Self::Binary(Box::new(left), op, Box::new(right))
}
pub fn unary(op: UnaryOp, expr: Expression) -> Self {
Self::Unary(op, Box::new(expr))
}
pub fn and(left: Expression, right: Expression) -> Self {
Self::binary(left, BinaryOp::And, right)
}
pub fn or(left: Expression, right: Expression) -> Self {
Self::binary(left, BinaryOp::Or, right)
}
pub fn eq(left: Expression, right: Expression) -> Self {
Self::binary(left, BinaryOp::Eq, right)
}
pub fn neq(left: Expression, right: Expression) -> Self {
Self::binary(left, BinaryOp::NotEq, right)
}
pub fn lt(left: Expression, right: Expression) -> Self {
Self::binary(left, BinaryOp::Lt, right)
}
pub fn gt(left: Expression, right: Expression) -> Self {
Self::binary(left, BinaryOp::Gt, right)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BinaryOp {
And,
Or,
Eq,
NotEq,
Lt,
LtEq,
Gt,
GtEq,
Add,
Sub,
Mul,
Div,
SameTerm,
LangMatches,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum UnaryOp {
Not,
Plus,
Minus,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionCall {
pub name: String,
pub args: Vec<Expression>,
}
impl FunctionCall {
pub fn new(name: impl Into<String>, args: Vec<Expression>) -> Self {
Self {
name: name.into(),
args,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Aggregate {
Count {
expr: Option<Box<Expression>>,
distinct: bool,
},
Sum {
expr: Box<Expression>,
distinct: bool,
},
Avg {
expr: Box<Expression>,
distinct: bool,
},
Min {
expr: Box<Expression>,
},
Max {
expr: Box<Expression>,
},
GroupConcat {
expr: Box<Expression>,
separator: Option<String>,
distinct: bool,
},
Sample {
expr: Box<Expression>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Filter {
pub expression: Expression,
}
impl Filter {
pub fn new(expression: Expression) -> Self {
Self { expression }
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SolutionModifier {
pub order_by: Vec<OrderCondition>,
pub limit: Option<usize>,
pub offset: Option<usize>,
pub having: Option<Expression>,
}
impl SolutionModifier {
pub fn with_limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn with_offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
pub fn with_order(mut self, conditions: Vec<OrderCondition>) -> Self {
self.order_by = conditions;
self
}
pub fn with_having(mut self, expr: Expression) -> Self {
self.having = Some(expr);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderCondition {
pub expression: Expression,
pub ascending: bool,
}
impl OrderCondition {
pub fn asc(expr: Expression) -> Self {
Self {
expression: expr,
ascending: true,
}
}
pub fn desc(expr: Expression) -> Self {
Self {
expression: expr,
ascending: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum GroupCondition {
Variable(String),
Expression(Expression, Option<String>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum UpdateOperation {
InsertData(InsertData),
DeleteData(DeleteData),
Modify(Modify),
Load {
source: Iri,
destination: Option<Iri>,
silent: bool,
},
Clear { target: GraphTarget, silent: bool },
Create { graph: Iri, silent: bool },
Drop { target: GraphTarget, silent: bool },
Copy {
source: GraphTarget,
destination: GraphTarget,
silent: bool,
},
Move {
source: GraphTarget,
destination: GraphTarget,
silent: bool,
},
Add {
source: GraphTarget,
destination: GraphTarget,
silent: bool,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InsertData {
pub quads: Vec<Quad>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteData {
pub quads: Vec<Quad>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Modify {
pub with_graph: Option<Iri>,
pub delete_pattern: Option<Vec<QuadPattern>>,
pub insert_pattern: Option<Vec<QuadPattern>>,
pub using: Vec<DatasetClause>,
pub where_pattern: GraphPattern,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Quad {
pub subject: RdfTerm,
pub predicate: Iri,
pub object: RdfTerm,
pub graph: Option<Iri>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuadPattern {
pub subject: TermOrVariable,
pub predicate: VarOrIri,
pub object: TermOrVariable,
pub graph: Option<VarOrIri>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum GraphTarget {
Default,
Named(Iri),
All,
AllNamed,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rdf_term_creation() {
let iri = RdfTerm::iri("http://example.org/resource");
assert!(iri.is_iri());
let lit = RdfTerm::literal("hello");
assert!(lit.is_literal());
let blank = RdfTerm::blank("b0");
assert!(blank.is_blank_node());
}
#[test]
fn test_literal_parsing() {
let int_lit = Literal::integer(42);
assert_eq!(int_lit.as_integer(), Some(42));
let double_lit = Literal::double(3.14);
assert!((double_lit.as_double().unwrap() - 3.14).abs() < 0.001);
let bool_lit = Literal::boolean(true);
assert_eq!(bool_lit.as_boolean(), Some(true));
}
#[test]
fn test_expression_builder() {
let expr = Expression::and(
Expression::eq(Expression::var("x"), Expression::integer(10)),
Expression::gt(Expression::var("y"), Expression::integer(5)),
);
match expr {
Expression::Binary(_, BinaryOp::And, _) => (),
_ => panic!("Expected AND expression"),
}
}
#[test]
fn test_triple_pattern() {
let pattern = TriplePattern::simple(
TermOrVariable::var("s"),
Iri::rdf_type(),
TermOrVariable::iri(Iri::new("http://example.org/Person")),
);
assert!(matches!(pattern.subject, TermOrVariable::Variable(_)));
assert!(matches!(pattern.predicate, PropertyPath::Iri(_)));
}
}