diff --git a/src/db/plan.rs b/src/db/plan.rs index 4a9e5ad5..09a9723b 100644 --- a/src/db/plan.rs +++ b/src/db/plan.rs @@ -36,16 +36,17 @@ // } +use std::collections::BTreeMap; use pest::iterators::Pair; use crate::db::engine::Session; use crate::db::table::TableInfo; use crate::error::CozoError::LogicError; use crate::parser::Rule; use crate::error::{CozoError, Result}; -use crate::parser::text_identifier::build_name_in_def; +use crate::parser::text_identifier::{build_name_in_def, parse_string}; use crate::relation::data::DataKind; use crate::relation::value; -use crate::relation::value::Value; +use crate::relation::value::{build_expr_primary, METHOD_MERGE, Value}; #[derive(Debug, Eq, PartialEq, Clone)] pub enum FromEl { @@ -209,6 +210,117 @@ impl<'a> Session<'a> { let conditions = pair.into_inner().map(Value::from_pair).collect::>>()?; Ok(Value::Apply(value::OP_AND.into(), conditions).to_static()) } + + pub fn parse_select_pattern<'i>(&self, pair: Pair<'i, Rule>) -> Result> { + let mut pairs = pair.into_inner(); + let mut nxt = pairs.next().unwrap(); + let scoped = match nxt.as_rule() { + Rule::scoped_dict => { + let mut pp = nxt.into_inner(); + let name = pp.next().unwrap().as_str(); + nxt = pp.next().unwrap(); + Some(name.to_string()) + } + _ => None + }; + + let mut keys = BTreeMap::new(); + let mut merged = vec![]; + let mut collected_vals = BTreeMap::new(); + + for p in nxt.into_inner() { + match p.as_rule() { + Rule::grouped_pair => { + let mut pp = p.into_inner(); + let id = parse_string(pp.next().unwrap())?; + let val = Value::from_pair(pp.next().unwrap())?; + keys.insert(id, val); + } + Rule::dict_pair => { + let mut inner = p.into_inner(); + let name = parse_string(inner.next().unwrap())?; + let val = build_expr_primary(inner.next().unwrap())?; + collected_vals.insert(name.into(), val); + } + Rule::spreading => { + let el = p.into_inner().next().unwrap(); + let to_concat = build_expr_primary(el)?; + if !matches!(to_concat, Value::Dict(_) | Value::Variable(_) | + Value::IdxAccess(_, _) | Value:: FieldAccess(_, _) | Value::Apply(_, _)) { + return Err(CozoError::LogicError("Cannot spread".to_string())); + } + if !collected_vals.is_empty() { + merged.push(Value::Dict(collected_vals)); + collected_vals = BTreeMap::new(); + } + merged.push(to_concat); + } + Rule::scoped_accessor => { + let name = parse_string(p.into_inner().next().unwrap())?; + let val = Value::FieldAccess(name.clone().into(), + Value::Variable("_".into()).into()); + collected_vals.insert(name.into(), val); + } + _ => unreachable!() + } + } + + let vals = if merged.is_empty() { + Value::Dict(collected_vals) + } else { + if !collected_vals.is_empty() { + merged.push(Value::Dict(collected_vals)); + } + Value::Apply(METHOD_MERGE.into(), merged) + }; + + let mut ordering = vec![]; + let mut limit = None; + let mut offset = None; + + for p in pairs { + match p.as_rule() { + Rule::order_pattern => { + for p in p.into_inner() { + ordering.push((p.as_rule() == Rule::order_asc, parse_string(p.into_inner().next().unwrap())?)) + } + } + Rule::offset_pattern => { + for p in p.into_inner() { + match p.as_rule() { + Rule::limit_clause => { + limit = Some(p.into_inner().next().unwrap().as_str().replace('_', "").parse::()?); + } + Rule::offset_clause => { + offset = Some(p.into_inner().next().unwrap().as_str().replace('_', "").parse::()?); + } + _ => unreachable!() + } + } + } + _ => unreachable!() + } + } + + Ok(Selection { + scoped, + keys, + vals, + ordering, + limit, + offset + }) + } +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct Selection<'a> { + pub scoped: Option, + pub keys: BTreeMap>, + pub vals: Value<'a>, + pub ordering: Vec<(bool, String)>, + pub limit: Option, + pub offset: Option } #[cfg(test)] @@ -272,6 +384,11 @@ mod tests { let parsed = Parser::parse(Rule::where_pattern, s).unwrap().next().unwrap(); let where_result = sess.parse_where_pattern(parsed).unwrap(); println!("{:#?}", where_result); + + let s = "select {*id: a.id, b: a.b, c: a.c} ordered [e, +c, -b] limit 1 offset 2"; + let parsed = Parser::parse(Rule::select_pattern, s).unwrap().next().unwrap(); + let select_result = sess.parse_select_pattern(parsed).unwrap(); + println!("{:#?}", select_result); } drop(engine); let _ = fs::remove_dir_all(db_path); diff --git a/src/grammar.pest b/src/grammar.pest index 03a3bc4c..65801375 100644 --- a/src/grammar.pest +++ b/src/grammar.pest @@ -126,7 +126,11 @@ dict_entry = _{ spreading | dict_pair | scoped_accessor } scoped_accessor = { ident } dict_pair = {(ident | string) ~ ":" ~ expr} -scoped_dict = { ident ~ dict } +select_dict = { "{" ~ (select_dict_entry ~ ",")* ~ select_dict_entry? ~ "}"} +select_dict_entry = _{ grouped_pair | spreading | dict_pair | scoped_accessor } +grouped_pair = { "*" ~ (ident | string) ~ ":" ~ expr} + +scoped_dict = { ident ~ select_dict } name_in_def = {(ident | string)} col_entry = { col_name ~ ":" ~ typing ~ ("=" ~ expr)? } @@ -181,4 +185,15 @@ bwd_edge_pattern = { "<-" ~ edge_pattern_inner ~ "-" } edge_pattern_inner = _{"[" ~ ident? ~ ":" ~ name_in_def ~ "]"} outer_join_marker = { "?" } -where_pattern = { "where" ~ (expr ~ ",")* ~ expr } \ No newline at end of file +where_pattern = { "where" ~ (expr ~ ",")* ~ expr } +select_pattern = { "select" ~ (select_dict | scoped_dict) ~ ( (order_pattern ~ offset_pattern?) | (offset_pattern ~ order_pattern?) )? } + +order_pattern = { "ordered" ~ "[" ~ order_el ~ ("," ~ order_el)* ~ "]" } +order_el = _{order_asc | order_dsc} +order_asc = {"+"? ~ ident} +order_dsc = {"-" ~ ident} +offset_pattern = { limit_clause? ~ offset_clause? } +limit_clause = { "limit" ~ pos_int } +offset_clause = { "offset" ~ pos_int } + +relational_query = { from_pattern ~ where_pattern? ~ select_pattern } \ No newline at end of file diff --git a/src/relation/value.rs b/src/relation/value.rs index 34ae2ff2..80e180e7 100644 --- a/src/relation/value.rs +++ b/src/relation/value.rs @@ -387,7 +387,7 @@ fn build_expr_infix<'a>(lhs: Result>, op: Pair, rhs: Result) -> Result { +pub fn build_expr_primary(pair: Pair) -> Result { match pair.as_rule() { Rule::expr => build_expr_primary(pair.into_inner().next().unwrap()), Rule::term => {