cleanup
parent
aeb4ae1522
commit
d969e97e70
@ -1,445 +0,0 @@
|
||||
use crate::db::engine::Session;
|
||||
use crate::db::table::TableInfo;
|
||||
use crate::error::CozoError::LogicError;
|
||||
use crate::error::{CozoError, Result};
|
||||
use crate::parser::text_identifier::{build_name_in_def, parse_string};
|
||||
use crate::parser::Rule;
|
||||
use crate::relation::data::DataKind;
|
||||
use crate::relation::value;
|
||||
use crate::relation::value::{StaticValue, Value};
|
||||
use pest::iterators::Pair;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub enum FromEl {
|
||||
Simple(Box<SimpleFromEl>),
|
||||
Chain(Vec<EdgeOrNodeEl>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub struct SimpleFromEl {
|
||||
pub table: String,
|
||||
pub associates: Vec<(String, TableInfo)>,
|
||||
pub binding: String,
|
||||
pub info: TableInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum EdgeOrNodeKind {
|
||||
FwdEdge,
|
||||
BwdEdge,
|
||||
Node,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub struct EdgeOrNodeEl {
|
||||
pub table: String,
|
||||
pub binding: Option<String>,
|
||||
pub associates: Vec<(String, TableInfo)>,
|
||||
pub info: TableInfo,
|
||||
pub kind: EdgeOrNodeKind,
|
||||
pub left_outer_marker: bool,
|
||||
pub right_outer_marker: bool,
|
||||
}
|
||||
|
||||
impl<'a> Session<'a> {
|
||||
pub fn parse_from_pattern(&self, pair: Pair) -> Result<Vec<FromEl>> {
|
||||
let res: Result<Vec<_>> = pair
|
||||
.into_inner()
|
||||
.map(|p| match p.as_rule() {
|
||||
Rule::simple_from_pattern => self.parse_simple_from_pattern(p),
|
||||
Rule::node_edge_pattern => self.parse_node_edge_pattern(p),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.collect();
|
||||
res
|
||||
}
|
||||
|
||||
fn parse_simple_from_pattern(&self, pair: Pair) -> Result<FromEl> {
|
||||
let mut pairs = pair.into_inner();
|
||||
let name = pairs.next().unwrap().as_str();
|
||||
if name.starts_with('_') {
|
||||
return Err(CozoError::LogicError(
|
||||
"Pattern binding cannot start with underscore".to_string(),
|
||||
));
|
||||
}
|
||||
let table_name = build_name_in_def(pairs.next().unwrap(), true)?;
|
||||
let table_info = self.get_table_info(&table_name)?;
|
||||
let associates = pairs
|
||||
.map(|p| -> Result<(String, TableInfo)> {
|
||||
let a_name = build_name_in_def(p, true)?;
|
||||
let a_info = self.get_table_info(&a_name)?;
|
||||
Ok((a_name, a_info))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let ret = FromEl::Simple(Box::new(SimpleFromEl {
|
||||
binding: name.to_string(),
|
||||
table: table_name,
|
||||
info: table_info,
|
||||
associates,
|
||||
}));
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn parse_node_edge_pattern(&self, pair: Pair) -> Result<FromEl> {
|
||||
let res: Result<Vec<_>> = pair
|
||||
.into_inner()
|
||||
.map(|p| match p.as_rule() {
|
||||
Rule::node_pattern => self.parse_node_pattern(p),
|
||||
Rule::edge_pattern => {
|
||||
// let right_join;
|
||||
let mut pairs = p.into_inner();
|
||||
let nxt = pairs.next().unwrap();
|
||||
if nxt.as_rule() == Rule::outer_join_marker {
|
||||
// right_join = true;
|
||||
// nxt = pairs.next().unwrap();
|
||||
return Err(LogicError(
|
||||
"Right outer join not supported here".to_string(),
|
||||
));
|
||||
} else {
|
||||
// right_join = false;
|
||||
}
|
||||
let mut edge = match nxt.as_rule() {
|
||||
Rule::fwd_edge_pattern => self.parse_edge_pattern(nxt, true)?,
|
||||
Rule::bwd_edge_pattern => self.parse_edge_pattern(nxt, false)?,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
edge.left_outer_marker = pairs.next().is_some();
|
||||
edge.right_outer_marker = false; // right_join;
|
||||
Ok(edge)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.collect();
|
||||
let res = res?;
|
||||
let connects = res.windows(2).all(|v| {
|
||||
let left = &v[0];
|
||||
let right = &v[1];
|
||||
match (&left.kind, &right.kind) {
|
||||
(EdgeOrNodeKind::FwdEdge, EdgeOrNodeKind::Node) => {
|
||||
left.info.dst_table_id == right.info.table_id
|
||||
}
|
||||
(EdgeOrNodeKind::BwdEdge, EdgeOrNodeKind::Node) => {
|
||||
left.info.src_table_id == right.info.table_id
|
||||
}
|
||||
(EdgeOrNodeKind::Node, EdgeOrNodeKind::FwdEdge) => {
|
||||
left.info.table_id == right.info.src_table_id
|
||||
}
|
||||
(EdgeOrNodeKind::Node, EdgeOrNodeKind::BwdEdge) => {
|
||||
left.info.table_id == right.info.dst_table_id
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
if !connects {
|
||||
return Err(CozoError::LogicError("Chain does not connect".to_string()));
|
||||
}
|
||||
if res.is_empty() {
|
||||
return Err(CozoError::LogicError("Empty chain not allowed".to_string()));
|
||||
}
|
||||
Ok(FromEl::Chain(res))
|
||||
}
|
||||
|
||||
fn parse_node_pattern(&self, pair: Pair) -> Result<EdgeOrNodeEl> {
|
||||
let (table, binding, info, associates) = self.parse_node_or_edge(pair)?;
|
||||
if info.kind != DataKind::Node {
|
||||
return Err(CozoError::LogicError(format!("{} is not a node", table)));
|
||||
}
|
||||
Ok(EdgeOrNodeEl {
|
||||
table,
|
||||
binding,
|
||||
info,
|
||||
kind: EdgeOrNodeKind::Node,
|
||||
left_outer_marker: false,
|
||||
right_outer_marker: false,
|
||||
associates,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_edge_pattern(&self, pair: Pair, is_fwd: bool) -> Result<EdgeOrNodeEl> {
|
||||
let (table, binding, info, associates) = self.parse_node_or_edge(pair)?;
|
||||
if info.kind != DataKind::Edge {
|
||||
return Err(CozoError::LogicError(format!("{} is not an edge", table)));
|
||||
}
|
||||
Ok(EdgeOrNodeEl {
|
||||
table,
|
||||
binding,
|
||||
info,
|
||||
kind: if is_fwd {
|
||||
EdgeOrNodeKind::FwdEdge
|
||||
} else {
|
||||
EdgeOrNodeKind::BwdEdge
|
||||
},
|
||||
left_outer_marker: false,
|
||||
right_outer_marker: false,
|
||||
associates,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_node_or_edge(
|
||||
&self,
|
||||
pair: Pair,
|
||||
) -> Result<(String, Option<String>, TableInfo, Vec<(String, TableInfo)>)> {
|
||||
let name;
|
||||
|
||||
let mut pairs = pair.into_inner();
|
||||
let mut cur_pair = pairs.next().unwrap();
|
||||
if cur_pair.as_rule() == Rule::ident {
|
||||
name = Some(cur_pair.as_str());
|
||||
cur_pair = pairs.next().unwrap();
|
||||
} else {
|
||||
name = None;
|
||||
}
|
||||
let table_name = build_name_in_def(cur_pair, true)?;
|
||||
let table_info = self.get_table_info(&table_name)?;
|
||||
// println!("{:?}, {}, {:?}", name, table_name, table_info);
|
||||
let associates = pairs
|
||||
.map(|p| -> Result<(String, TableInfo)> {
|
||||
let a_name = build_name_in_def(p, true)?;
|
||||
let a_info = self.get_table_info(&a_name)?;
|
||||
Ok((a_name, a_info))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
Ok((
|
||||
table_name,
|
||||
name.map(|v| v.to_string()),
|
||||
table_info,
|
||||
associates,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn parse_where_pattern(&self, pair: Pair) -> Result<Value> {
|
||||
let conditions = pair
|
||||
.into_inner()
|
||||
.map(Value::from_pair)
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
Ok(Value::Apply(value::OP_AND.into(), conditions).to_static())
|
||||
}
|
||||
|
||||
pub fn parse_select_pattern(&self, pair: Pair) -> Result<Selection> {
|
||||
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 = vec![];
|
||||
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.push((id, val.to_static()));
|
||||
}
|
||||
Rule::dict_pair => {
|
||||
let mut inner = p.into_inner();
|
||||
let name = parse_string(inner.next().unwrap())?;
|
||||
let val_pair = inner.next().unwrap();
|
||||
let val = Value::from_pair(val_pair)?;
|
||||
collected_vals.insert(name.into(), val);
|
||||
}
|
||||
Rule::spreading => {
|
||||
let el = p.into_inner().next().unwrap();
|
||||
let to_concat = Value::from_pair(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() {
|
||||
collected_vals
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v.to_static()))
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
// construct it with help of partial eval
|
||||
todo!()
|
||||
// if !collected_vals.is_empty() {
|
||||
// merged.push(Value::Dict(collected_vals));
|
||||
// }
|
||||
// Value::Apply(value::METHOD_MERGE.into(), merged).to_static()
|
||||
};
|
||||
|
||||
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,
|
||||
Value::from_pair(p.into_inner().next().unwrap())?.to_static(),
|
||||
))
|
||||
}
|
||||
}
|
||||
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::<i64>()?,
|
||||
);
|
||||
}
|
||||
Rule::offset_clause => {
|
||||
offset = Some(
|
||||
p.into_inner()
|
||||
.next()
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.replace('_', "")
|
||||
.parse::<i64>()?,
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
println!("ordering {:?}", ordering);
|
||||
|
||||
Ok(Selection {
|
||||
scoped,
|
||||
keys,
|
||||
vals,
|
||||
ordering,
|
||||
limit,
|
||||
offset,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub struct Selection {
|
||||
pub scoped: Option<String>,
|
||||
pub keys: Vec<(String, StaticValue)>,
|
||||
pub vals: Vec<(String, StaticValue)>,
|
||||
pub ordering: Vec<(bool, StaticValue)>,
|
||||
pub limit: Option<i64>,
|
||||
pub offset: Option<i64>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs;
|
||||
// use super::*;
|
||||
use crate::db::engine::Engine;
|
||||
use crate::parser::{Parser, Rule};
|
||||
use pest::Parser as PestParser;
|
||||
|
||||
#[test]
|
||||
fn parse_patterns() {
|
||||
let db_path = "_test_db_query";
|
||||
let engine = Engine::new(db_path.to_string(), true).unwrap();
|
||||
{
|
||||
let mut sess = engine.session().unwrap();
|
||||
let s = r#"
|
||||
create node "Person" {
|
||||
*id: Int,
|
||||
name: Text,
|
||||
email: ?Text,
|
||||
habits: ?[?Text]
|
||||
}
|
||||
|
||||
create edge (Person)-[Friend]->(Person) {
|
||||
relation: ?Text
|
||||
}
|
||||
|
||||
create node Z {
|
||||
*id: Text
|
||||
}
|
||||
|
||||
create assoc WorkInfo : Person {
|
||||
work_id: Int
|
||||
}
|
||||
|
||||
create assoc RelationshipData: Person {
|
||||
status: Text
|
||||
}
|
||||
"#;
|
||||
for p in Parser::parse(Rule::file, s).unwrap() {
|
||||
if p.as_rule() == Rule::EOI {
|
||||
break;
|
||||
}
|
||||
sess.run_definition(p).unwrap();
|
||||
}
|
||||
sess.commit().unwrap();
|
||||
|
||||
let s = "from a:Friend, (b:Person)-[:Friend]->(c:Z), x:Person";
|
||||
let parsed = Parser::parse(Rule::from_pattern, s)
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap();
|
||||
assert_eq!(parsed.as_rule(), Rule::from_pattern);
|
||||
assert!(sess.parse_from_pattern(parsed).is_err());
|
||||
|
||||
let s = "from a:Friend, (b:Person)-[:Friend]->?(c:Person), x:Person";
|
||||
let parsed = Parser::parse(Rule::from_pattern, s)
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap();
|
||||
assert_eq!(parsed.as_rule(), Rule::from_pattern);
|
||||
let from_pattern = sess.parse_from_pattern(parsed).unwrap();
|
||||
println!("{:#?}", from_pattern);
|
||||
|
||||
let s = "where b.id > c.id || x.name.is_null(), a.id == 5, x.name == 'Joe', x.name.len() == 3";
|
||||
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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue