Starts to implement algebra for selection

pull/10/head
Tpt 6 years ago
parent a513fdb707
commit 644499dee8
  1. 409
      src/sparql/algebra.rs
  2. 41
      src/sparql/model.rs
  3. 62
      src/sparql/parser.rs
  4. 84
      src/sparql/sparql_grammar.rustpeg

@ -1,5 +1,6 @@
use model::*;
use sparql::model::*;
use std::collections::BTreeSet;
use std::fmt;
use std::ops::Add;
use utils::Escaper;
@ -398,12 +399,24 @@ impl fmt::Display for Expression {
}
}
impl From<NamedNode> for Expression {
fn from(p: NamedNode) -> Self {
Expression::ConstantExpression(p.into())
}
}
impl From<Literal> for Expression {
fn from(p: Literal) -> Self {
Expression::ConstantExpression(p.into())
}
}
impl From<Variable> for Expression {
fn from(v: Variable) -> Self {
Expression::ConstantExpression(v.into())
}
}
struct SparqlExpression<'a>(&'a Expression);
impl<'a> fmt::Display for SparqlExpression<'a> {
@ -794,6 +807,51 @@ impl From<ListPattern> for MultiSetPattern {
}
}
impl MultiSetPattern {
pub fn visible_variables<'a>(&'a self) -> BTreeSet<&'a Variable> {
let mut vars = BTreeSet::default();
self.add_visible_variables(&mut vars);
vars
}
fn add_visible_variables<'a>(&'a self, vars: &mut BTreeSet<&'a Variable>) {
match self {
MultiSetPattern::BGP(p) => {
for pattern in p {
if let TermOrVariable::Variable(ref s) = pattern.subject {
vars.insert(s);
}
//TODO: pred
if let TermOrVariable::Variable(ref o) = pattern.object {
vars.insert(o);
}
}
}
MultiSetPattern::Join(a, b) => {
a.add_visible_variables(vars);
b.add_visible_variables(vars);
}
MultiSetPattern::LeftJoin(a, b, _) => {
a.add_visible_variables(vars);
b.add_visible_variables(vars);
}
MultiSetPattern::Filter(_, p) => p.add_visible_variables(vars),
MultiSetPattern::Union(a, b) => {
a.add_visible_variables(vars);
b.add_visible_variables(vars);
}
MultiSetPattern::Graph(_, p) => p.add_visible_variables(vars),
MultiSetPattern::Extend(p, v, _) => {
p.add_visible_variables(vars);
vars.insert(&v);
}
MultiSetPattern::Minus(a, _) => a.add_visible_variables(vars),
MultiSetPattern::ToMultiSet(l) => l.add_visible_variables(vars),
MultiSetPattern::Service(_, p, _) => p.add_visible_variables(vars),
}
}
}
struct SparqlMultiSetPattern<'a>(&'a MultiSetPattern);
impl<'a> fmt::Display for SparqlMultiSetPattern<'a> {
@ -854,7 +912,14 @@ impl<'a> fmt::Display for SparqlMultiSetPattern<'a> {
SparqlMultiSetPattern(&*a),
SparqlMultiSetPattern(&*b)
),
MultiSetPattern::ToMultiSet(l) => write!(f, "{}", SparqlListPattern(&l)),
MultiSetPattern::ToMultiSet(l) => write!(
f,
"{{ {} }}",
SparqlListPattern {
algebra: &l,
dataset: &EMPTY_DATASET
}
),
MultiSetPattern::Service(n, p, s) => if *s {
write!(
f,
@ -873,14 +938,56 @@ impl<'a> fmt::Display for SparqlMultiSetPattern<'a> {
pub enum ListPattern {
Data(Vec<Binding>),
ToList(MultiSetPattern),
Group(),
Aggregation(),
AggregateJoin(),
OrderBy(Box<MultiSetPattern>),
Project(Box<MultiSetPattern>),
Distinct(Box<MultiSetPattern>),
Reduced(Box<MultiSetPattern>),
Slice(Box<MultiSetPattern>, usize, usize),
OrderBy(Box<ListPattern>, Vec<OrderComparator>),
Project(Box<ListPattern>, Vec<Variable>),
Distinct(Box<ListPattern>),
Reduced(Box<ListPattern>),
Slice(Box<ListPattern>, usize, Option<usize>),
}
impl fmt::Display for ListPattern {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ListPattern::Data(bs) => write!(
f,
"{{ {} }}",
bs.iter()
.map(|c| c.to_string())
.collect::<Vec<String>>()
.join(" ")
),
ListPattern::ToList(l) => write!(f, "{}", l),
ListPattern::OrderBy(l, o) => write!(
f,
"OrderBy({}, ({}))",
l,
o.iter()
.map(|c| c.to_string())
.collect::<Vec<String>>()
.join(", ")
),
ListPattern::Project(l, pv) => write!(
f,
"Project({}, ({}))",
l,
pv.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
.join(", ")
),
ListPattern::Distinct(l) => write!(f, "Distinct({})", l),
ListPattern::Reduced(l) => write!(f, "Reduce({})", l),
ListPattern::Slice(l, start, length) => write!(
f,
"Slice({}, {}, {})",
l,
start,
length
.map(|l| l.to_string())
.unwrap_or_else(|| '?'.to_string())
),
}
}
}
impl Default for ListPattern {
@ -898,22 +1005,190 @@ impl From<MultiSetPattern> for ListPattern {
}
}
impl fmt::Display for ListPattern {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
impl ListPattern {
pub fn visible_variables<'a>(&'a self) -> BTreeSet<&'a Variable> {
let mut vars = BTreeSet::default();
self.add_visible_variables(&mut vars);
vars
}
fn add_visible_variables<'a>(&'a self, vars: &mut BTreeSet<&'a Variable>) {
match self {
ListPattern::ToList(l) => write!(f, "{}", l),
_ => Ok(()), //TODO
ListPattern::Data(b) => {
for binding in b {
for (var, _) in binding {
vars.insert(var);
}
}
}
ListPattern::ToList(p) => p.add_visible_variables(vars),
ListPattern::OrderBy(l, _) => l.add_visible_variables(vars),
ListPattern::Project(_, pv) => vars.extend(pv.iter()),
ListPattern::Distinct(l) => l.add_visible_variables(vars),
ListPattern::Reduced(l) => l.add_visible_variables(vars),
ListPattern::Slice(l, _, _) => l.add_visible_variables(vars),
}
}
}
struct SparqlListPattern<'a>(&'a ListPattern);
struct SparqlListPattern<'a> {
algebra: &'a ListPattern,
dataset: &'a Dataset,
}
impl<'a> fmt::Display for SparqlListPattern<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.algebra {
ListPattern::Data(bs) => if bs.is_empty() {
Ok(())
} else {
let vars: Vec<&Variable> = bs[0].iter().map(|(v, _)| v).collect();
write!(f, "VALUES ( ")?;
for var in &vars {
write!(f, "{} ", var)?;
}
write!(f, ") {{ ")?;
for b in bs {
write!(f, "( ")?;
for var in &vars {
b.get(var)
.map(|v| write!(f, "{} ", v))
.unwrap_or_else(|| write!(f, "UNDEF "))?;
}
write!(f, ") ")?;
}
write!(f, " }}")
},
ListPattern::ToList(l) => write!(f, "{}", SparqlMultiSetPattern(&*l)),
ListPattern::OrderBy(l, o) => write!(
f,
"{} ORDER BY {}",
SparqlListPattern {
algebra: &*l,
dataset: self.dataset
},
o.iter()
.map(|c| SparqlOrderComparator(c).to_string())
.collect::<Vec<String>>()
.join(" ")
),
ListPattern::Project(l, pv) => write!(
f,
"SELECT {} {} WHERE {{ {} }}",
build_sparql_select_arguments(pv),
self.dataset,
SparqlListPattern {
algebra: &*l,
dataset: &EMPTY_DATASET
}
),
ListPattern::Distinct(l) => match l.as_ref() {
ListPattern::Project(l, pv) => write!(
f,
"SELECT DISTINCT {} {} WHERE {{ {} }}",
build_sparql_select_arguments(pv),
self.dataset,
SparqlListPattern {
algebra: &*l,
dataset: &EMPTY_DATASET
}
),
l => write!(
f,
"DISTINCT {}",
SparqlListPattern {
algebra: &l,
dataset: self.dataset
}
),
},
ListPattern::Reduced(l) => match l.as_ref() {
ListPattern::Project(l, pv) => write!(
f,
"SELECT REDUCED {} {} WHERE {{ {} }}",
build_sparql_select_arguments(pv),
self.dataset,
SparqlListPattern {
algebra: &*l,
dataset: &EMPTY_DATASET
}
),
l => write!(
f,
"REDUCED {}",
SparqlListPattern {
algebra: &l,
dataset: self.dataset
}
),
},
ListPattern::Slice(l, start, length) => length
.map(|length| {
write!(
f,
"{} LIMIT {} OFFSET {}",
SparqlListPattern {
algebra: &*l,
dataset: self.dataset
},
start,
length
)
})
.unwrap_or_else(|| {
write!(
f,
"{} LIMIT {}",
SparqlListPattern {
algebra: &*l,
dataset: self.dataset
},
start
)
}),
}
}
}
fn build_sparql_select_arguments(args: &Vec<Variable>) -> String {
if args.is_empty() {
"*".to_owned()
} else {
args.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
.join(" ")
}
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)]
pub enum OrderComparator {
Asc(Expression),
Desc(Expression),
}
impl fmt::Display for OrderComparator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
OrderComparator::Asc(e) => write!(f, "ASC({})", e),
OrderComparator::Desc(e) => write!(f, "DESC({})", e),
}
}
}
impl From<Expression> for OrderComparator {
fn from(e: Expression) -> Self {
OrderComparator::Asc(e)
}
}
struct SparqlOrderComparator<'a>(&'a OrderComparator);
impl<'a> fmt::Display for SparqlOrderComparator<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
ListPattern::ToList(l) => write!(f, "{}", SparqlMultiSetPattern(&l)),
_ => Ok(()), //TODO
OrderComparator::Asc(e) => write!(f, "ASC({})", SparqlExpression(e)),
OrderComparator::Desc(e) => write!(f, "DESC({})", SparqlExpression(e)),
}
}
}
@ -962,103 +1237,46 @@ impl fmt::Display for Dataset {
}
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)]
pub enum SelectionOption {
Distinct,
Reduced,
Default,
}
impl fmt::Display for SelectionOption {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
SelectionOption::Distinct => write!(f, "DISTINCT"),
SelectionOption::Reduced => write!(f, "REDUCED"),
SelectionOption::Default => Ok(()),
}
}
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)]
pub enum SelectionMember {
Variable(Variable),
Expression(Expression, Variable),
}
impl fmt::Display for SelectionMember {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
SelectionMember::Variable(v) => write!(f, "{}", v),
SelectionMember::Expression(e, v) => write!(f, "({} AS {})", e, v),
}
}
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)]
pub struct Selection {
pub option: SelectionOption,
pub variables: Option<Vec<SelectionMember>>,
}
impl fmt::Display for Selection {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
self.variables
.as_ref()
.map(|vars| {
write!(
f,
"{} {}",
self.option,
vars.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
.join(" ")
)
})
.unwrap_or_else(|| write!(f, "{} *", self.option))
}
lazy_static! {
static ref EMPTY_DATASET: Dataset = Dataset::default();
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)]
pub enum Query {
SelectQuery {
selection: Selection,
dataset: Dataset,
filter: ListPattern,
algebra: ListPattern,
},
ConstructQuery {
construct: Vec<TriplePattern>,
dataset: Dataset,
filter: ListPattern,
algebra: ListPattern,
},
DescribeQuery {
dataset: Dataset,
filter: ListPattern,
algebra: ListPattern,
},
AskQuery {
dataset: Dataset,
filter: ListPattern,
algebra: ListPattern,
},
}
impl fmt::Display for Query {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Query::SelectQuery {
selection,
dataset,
filter,
} => write!(
Query::SelectQuery { dataset, algebra } => write!(
f,
"SELECT {} {} WHERE {{ {} }}",
selection,
dataset,
SparqlListPattern(&filter)
"{}",
SparqlListPattern {
algebra: &algebra,
dataset: &dataset
}
),
Query::ConstructQuery {
construct,
dataset,
filter,
algebra,
} => write!(
f,
"CONSTRUCT {{ {} }} {} WHERE {{ {} }}",
@ -1068,19 +1286,28 @@ impl fmt::Display for Query {
.collect::<Vec<String>>()
.join(" . "),
dataset,
SparqlListPattern(&filter)
SparqlListPattern {
algebra: &algebra,
dataset: &EMPTY_DATASET
}
),
Query::DescribeQuery { dataset, filter } => write!(
Query::DescribeQuery { dataset, algebra } => write!(
f,
"DESCRIBE {} WHERE {{ {} }}",
dataset,
SparqlListPattern(&filter)
SparqlListPattern {
algebra: &algebra,
dataset: &EMPTY_DATASET
}
),
Query::AskQuery { dataset, filter } => write!(
Query::AskQuery { dataset, algebra } => write!(
f,
"ASK {} WHERE {{ {} }}",
dataset,
SparqlListPattern(&filter)
SparqlListPattern {
algebra: &algebra,
dataset: &EMPTY_DATASET
}
),
}
}

@ -1,4 +1,5 @@
use model::*;
use std::collections::btree_map;
use std::collections::BTreeMap;
use std::fmt;
use uuid::Uuid;
@ -150,12 +151,20 @@ impl Binding {
self.0.insert(var, value);
}
pub fn get<'a>(&'a self, key: &'a TermOrVariable) -> Option<&'a Term> {
pub fn get<'a>(&'a self, key: &'a Variable) -> Option<&'a Term> {
self.0.get(key)
}
pub fn get_or_constant<'a>(&'a self, key: &'a TermOrVariable) -> Option<&'a Term> {
match key {
TermOrVariable::Term(t) => Some(t),
TermOrVariable::Variable(v) => self.0.get(v),
TermOrVariable::Variable(v) => self.get(v),
}
}
pub fn iter<'a>(&'a self) -> btree_map::Iter<Variable, Term> {
self.0.iter()
}
}
impl Default for Binding {
@ -163,3 +172,31 @@ impl Default for Binding {
Binding(BTreeMap::default())
}
}
impl IntoIterator for Binding {
type Item = (Variable, Term);
type IntoIter = btree_map::IntoIter<Variable, Term>;
fn into_iter(self) -> btree_map::IntoIter<Variable, Term> {
self.0.into_iter()
}
}
impl<'a> IntoIterator for &'a Binding {
type Item = (&'a Variable, &'a Term);
type IntoIter = btree_map::Iter<'a, Variable, Term>;
fn into_iter(self) -> btree_map::Iter<'a, Variable, Term> {
self.0.iter()
}
}
impl fmt::Display for Binding {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{{")?;
for (var, val) in self {
write!(f, " {} → {} ", var, val)?;
}
write!(f, "}}")
}
}

@ -132,6 +132,68 @@ mod grammar {
}).ok_or("The iterator should not be empty")
}
enum SelectionOption {
Distinct,
Reduced,
Default,
}
enum SelectionMember {
Variable(Variable),
Expression(Expression, Variable),
}
struct Selection {
pub option: SelectionOption,
pub variables: Option<Vec<SelectionMember>>,
}
fn build_select(
select: Selection,
wher: MultiSetPattern,
having: Option<Expression>,
order_by: Option<Vec<OrderComparator>>,
values: Option<MultiSetPattern>,
) -> ListPattern {
let mut p = wher;
if let Some(ex) = having {
p = MultiSetPattern::Filter(ex, Box::new(p));
}
if let Some(data) = values {
p = MultiSetPattern::Join(Box::new(p), Box::new(data));
}
let mut pv: Vec<Variable> = Vec::default();
match select.variables {
Some(sel_items) => {
for sel_item in sel_items {
match sel_item {
SelectionMember::Variable(v) => pv.push(v),
SelectionMember::Expression(e, v) => if pv.contains(&v) {
//TODO: fail
} else {
p = MultiSetPattern::Extend(Box::new(p), v.clone(), e);
pv.push(v);
},
}
}
}
None => {
pv.extend(p.visible_variables().into_iter().map(|v| v.clone())) //TODO: is it really useful to do a projection?
}
}
let mut m = ListPattern::from(p);
if let Some(order) = order_by {
m = ListPattern::OrderBy(Box::new(m), order);
}
m = ListPattern::Project(Box::new(m), pv);
match select.option {
SelectionOption::Distinct => m = ListPattern::Distinct(Box::new(m)),
SelectionOption::Reduced => m = ListPattern::Reduced(Box::new(m)),
SelectionOption::Default => (),
}
m
}
enum Either<L, R> {
Left(L),
Right(R),

@ -13,7 +13,7 @@ use std::iter;
QueryUnit -> Query = Query
//[2]
Query -> Query = _ Prologue _ q:(SelectQuery / ConstructQuery / DescribeQuery / AskQuery) _ ValuesClause _ { //TODO: ValuesClause
Query -> Query = _ Prologue _ q:(SelectQuery / ConstructQuery / DescribeQuery / AskQuery) _ { //TODO: ValuesClause
q
}
@ -37,21 +37,16 @@ PrefixDecl -> () = "PREFIX"i _ ns:PNAME_NS _ i:IRIREF {
}
//[7]
SelectQuery -> Query = s:SelectClause _ d:DatasetClauses _ f:WhereClause _ SolutionModifier { //TODO: Modifier
SelectQuery -> Query = s:SelectClause _ d:DatasetClauses _ w:WhereClause _ GroupClause? _ h:HavingClause? _ o:OrderClause? _ l:LimitOffsetClauses? _ v:ValuesClause { //TODO: Modifier
Query::SelectQuery {
selection: s,
dataset: d,
filter: f
algebra: build_select(s, w, h, o, v)
}
}
//[8]
SubSelect -> MultiSetPattern = s:SelectClause _ f:WhereClause _ SolutionModifier _ ValuesClause { //TODO: Modifiers
MultiSetPattern::default()
/*TODO MultiSetPattern::SubSelectPattern {
selection: s,
filter: Box::new(f)
}*/
SubSelect -> MultiSetPattern = s:SelectClause _ w:WhereClause _ GroupClause? _ h:HavingClause? _ o:OrderClause? _ l:LimitOffsetClauses? _ v:ValuesClause { //TODO: Modifiers
build_select(s, w, h, o, v).into()
}
//[9]
@ -74,32 +69,63 @@ SelectClause_member -> SelectionMember =
//[10]
ConstructQuery -> Query =
"CONSTRUCT"i _ c:ConstructTemplate _ d:DatasetClauses _ f:WhereClause _ SolutionModifier {
Query::ConstructQuery { construct: c, dataset: d, filter: f }
"CONSTRUCT"i _ c:ConstructTemplate _ d:DatasetClauses _ w:WhereClause _ GroupClause? _ h:HavingClause? _ o:OrderClause? _ LimitOffsetClauses? _ v:ValuesClause {
Query::ConstructQuery {
construct: c,
dataset: d,
algebra: build_select(Selection {
option: SelectionOption::Reduced,
variables: None
}, w, h, o, v)
}
} /
"CONSTRUCT"i _ d:DatasetClauses _ "WHERE"i _ '{' _ c:ConstructQuery_optional_triple_template _ '}' _ SolutionModifier {
"CONSTRUCT"i _ d:DatasetClauses _ "WHERE"i _ '{' _ c:ConstructQuery_optional_triple_template _ '}' _ GroupClause? _ h:HavingClause? _ o:OrderClause? _ LimitOffsetClauses? _ v:ValuesClause {
Query::ConstructQuery {
construct: c.clone(),
dataset: d,
filter: MultiSetPattern::BGP(c.into_iter().map(|p| PropertyPathPattern::from(p)).collect()).into()
algebra: build_select(Selection {
option: SelectionOption::Reduced,
variables: None
}, MultiSetPattern::BGP(c.into_iter().map(|p| PropertyPathPattern::from(p)).collect()).into(),
h, o, v)
}
}
ConstructQuery_optional_triple_template -> Vec<TriplePattern> = TriplesTemplate / { Vec::default() }
//[11]
DescribeQuery -> Query = "DESCRIBE"i _ ('*' / (VarOrIri _)+) _ d:DatasetClauses f:WhereClause? _ SolutionModifier {
Query::DescribeQuery {
dataset: d,
filter: f.unwrap_or_else(ListPattern::default)
DescribeQuery -> Query =
"DESCRIBE"i _ '*' _ d:DatasetClauses w:WhereClause? _ GroupClause? _ h:HavingClause? _ o:OrderClause? _ LimitOffsetClauses? _ v:ValuesClause {
Query::DescribeQuery {
dataset: d,
algebra: build_select(Selection {
option: SelectionOption::Reduced,
variables: None
}, w.unwrap_or_else(MultiSetPattern::default), h, o, v)
}
} /
"DESCRIBE"i _ p:DescribeQuery_item+ _ d:DatasetClauses w:WhereClause? _ GroupClause? _ h:HavingClause? _ o:OrderClause? _ LimitOffsetClauses? _ v:ValuesClause {
Query::DescribeQuery {
dataset: d,
algebra: build_select(Selection {
option: SelectionOption::Reduced,
variables: Some(p.into_iter().map(|var_or_iri| match var_or_iri {
NamedNodeOrVariable::NamedNode(n) => SelectionMember::Expression(n.into(), Variable::default()),
NamedNodeOrVariable::Variable(v) => SelectionMember::Variable(v)
}).collect())
}, w.unwrap_or_else(MultiSetPattern::default), h, o, v)
}
}
}
DescribeQuery_item -> NamedNodeOrVariable = i:VarOrIri _ { i }
//[12]
AskQuery -> Query = "ASK"i _ d:DatasetClauses f:WhereClause _ SolutionModifier {
AskQuery -> Query = "ASK"i _ d:DatasetClauses w:WhereClause _ GroupClause? _ h:HavingClause? _ o:OrderClause? _ LimitOffsetClauses? _ v:ValuesClause {
Query::AskQuery {
dataset: d,
filter: f
algebra: build_select(Selection {
option: SelectionOption::Reduced,
variables: None
}, w, h, o, v)
}
}
@ -123,13 +149,10 @@ NamedGraphClause -> Dataset = "NAMED"i _ s:SourceSelector {
SourceSelector -> NamedNode = iri
//[17]
WhereClause -> ListPattern = "WHERE"i? _ p:GroupGraphPattern {
p.into()
WhereClause -> MultiSetPattern = "WHERE"i? _ p:GroupGraphPattern {
p
}
//[18]
SolutionModifier -> () = GroupClause? _ HavingClause? _ OrderClause? _ LimitOffsetClauses?
//[19]
GroupClause -> () = "GROUP"i _ "BY"i _ (GroupCondition _)+
@ -145,10 +168,15 @@ HavingClause -> Expression = "HAVING"i _ e:HavingCondition+ {?
HavingCondition -> Expression = Constraint
//[23]
OrderClause -> () = "ORDER"i "BY"i _ OrderCondition+
OrderClause -> Vec<OrderComparator> = "ORDER"i "BY"i _ c:OrderClause_item+ { c }
OrderClause_item -> OrderComparator = c:OrderCondition _ { c }
//[24]
OrderCondition -> () = (( "ASC"i / "DESC"i) _ BrackettedExpression) / (Constraint / Var)
OrderCondition -> OrderComparator =
"ASC"i _ e: BrackettedExpression { OrderComparator::Asc(e) } /
"DESC"i _ e: BrackettedExpression { OrderComparator::Desc(e) } /
e: Constraint { e.into() } /
v: Var { Expression::from(v).into() }
//[25]
LimitOffsetClauses -> () = LimitClause _ OffsetClause? / OffsetClause _ LimitClause?

Loading…
Cancel
Save