restored recursive triple insertion

main
Ziyang Hu 2 years ago
parent 09a2273b45
commit dc0cc7b104

@ -187,8 +187,10 @@ tx_clause = { (tx_put | tx_retract )? ~ tx_map ~ ";"? }
tx_put = {"put" ~ ("@" ~ expr)?}
tx_retract = {"retract" ~ ("@" ~ expr)?}
tx_map = {"{" ~ (tx_pair ~ ",")* ~ tx_pair? ~ "}"}
tx_pair = {tx_ident ~ ":" ~ expr}
tx_ident = _{ tx_special_ident | compound_ident | string}
tx_pair = {tx_ident ~ ":" ~ (tx_list | tx_map | expr )}
tx_list = {"[" ~ ((expr | tx_map) ~ ",")* ~ (expr | tx_map)? ~ "]"}
tx_ident = _{ tx_special_ident | compound_ident_with_maybe_star | string}
compound_ident_with_maybe_star = @{"*"? ~ compound_ident}
tx_ident_id = {"_id" | "'_id'" | "\"_id\""}
tx_ident_temp_id = {"_tid" | "'_tid'" | "\"_tid\""}
tx_ident_key = {"_key" | "'_key'" | "\"_key\""}

@ -1,13 +1,12 @@
use std::collections::BTreeMap;
use std::fmt::{Display, Formatter};
use either::{Either, Left, Right};
use miette::{bail, ensure, miette, Result};
use smartstring::{LazyCompact, SmartString};
use crate::data::id::{EntityId, Validity};
use crate::data::symb::Symbol;
use crate::data::value::DataValue;
use crate::data::value::{DataValue, LARGEST_UTF_CHAR};
use crate::parse::expr::{build_expr, parse_string};
use crate::parse::{Pair, Pairs, Rule};
@ -28,10 +27,21 @@ impl Display for TxAction {
pub(crate) enum EntityRep {
Id(EntityId),
UserTempId(SmartString<LazyCompact>),
SysTempId(usize),
PullByKey(Symbol, DataValue),
}
impl EntityRep {
fn as_datavalue(&self) -> DataValue {
match self {
EntityRep::Id(i) => DataValue::from(i.0 as i64),
EntityRep::UserTempId(s) => DataValue::Str(s.clone()),
EntityRep::PullByKey(attr, data) => {
DataValue::List(vec![DataValue::Str(attr.0.clone()), data.clone()])
}
}
}
}
#[derive(Debug)]
pub(crate) struct Quintuple {
pub(crate) entity: EntityRep,
@ -52,7 +62,7 @@ pub(crate) fn parse_tx(
if pair.as_rule() == Rule::EOI {
break;
}
ret.extend(parse_tx_clause(pair, param_pool, &mut temp_id_serial)?);
parse_tx_clause(pair, param_pool, &mut temp_id_serial, &mut ret)?;
}
Ok(ret)
}
@ -61,7 +71,8 @@ fn parse_tx_clause(
pair: Pair<'_>,
param_pool: &BTreeMap<String, DataValue>,
temp_id_serial: &mut usize,
) -> Result<Vec<Quintuple>> {
coll: &mut Vec<Quintuple>,
) -> Result<()> {
let mut src = pair.into_inner();
let nxt = src.next().unwrap();
let mut op = TxAction::Put;
@ -95,89 +106,199 @@ fn parse_tx_clause(
}
_ => unreachable!(),
};
parse_tx_map(map_p, op, vld, param_pool, temp_id_serial, coll)?;
Ok(())
}
fn parse_tx_map(
map_p: Pair<'_>,
op: TxAction,
vld: Option<Validity>,
param_pool: &BTreeMap<String, DataValue>,
temp_id_serial: &mut usize,
coll: &mut Vec<Quintuple>,
) -> Result<EntityRep> {
let mut identifier = None;
let mut entities = vec![];
for pair in map_p.into_inner() {
match parse_tx_pair(pair, param_pool)? {
Left(entity) => {
if identifier.is_some() {
bail!("duplicate id specified")
}
identifier = Some(entity)
for pair in map_p.clone().into_inner() {
let mut src = pair.into_inner();
let fst = src.next().unwrap();
match fst.as_rule() {
Rule::tx_ident_id => {
ensure!(identifier.is_none(), "duplicate specification of key");
let expr = build_expr(src.next().unwrap(), param_pool)?;
let c = expr.eval_to_const()?;
let c = c
.get_non_neg_int()
.ok_or_else(|| miette!("integer id required, got {:?}", c))?;
let eid = EntityId(c);
ensure!(eid.is_perm(), "entity id invalid: {:?}", eid);
identifier = Some(EntityRep::Id(eid))
}
Rule::tx_ident_temp_id => {
ensure!(identifier.is_none(), "duplicate specification of key");
let expr = build_expr(src.next().unwrap(), param_pool)?;
let c = expr.eval_to_const()?;
let c = c
.get_string()
.ok_or_else(|| miette!("tid requires string, got {:?}", c))?;
identifier = Some(EntityRep::UserTempId(SmartString::from(c)))
}
Rule::tx_ident_key => {
ensure!(identifier.is_none(), "duplicate specification of key");
let expr = build_expr(src.next().unwrap(), param_pool)?;
let c = expr.eval_to_const()?;
let c = match c {
DataValue::List(l) => l,
v => bail!("key requires a list, got {:?}", v),
};
ensure!(c.len() == 2, "key requires a list of length 2");
let mut c = c.into_iter();
let attr = match c.next().unwrap() {
DataValue::Str(s) => Symbol(s),
v => bail!("attr name requires a string, got {:?}", v),
};
let val = c.next().unwrap();
identifier = Some(EntityRep::PullByKey(attr, val))
}
Right(ent) => entities.push(ent),
_ => {}
}
}
let identifier = identifier.unwrap_or_else(|| {
*temp_id_serial += 1;
EntityRep::SysTempId(*temp_id_serial)
let s_id = format!("{}{}{}", LARGEST_UTF_CHAR, *temp_id_serial, LARGEST_UTF_CHAR);
EntityRep::UserTempId(SmartString::from(s_id))
});
let mut coll = vec![];
for (attr, val) in entities {
coll.push(Quintuple {
entity: identifier.clone(),
attr_name: attr,
value: val,
action: op,
validity: vld.clone(),
})
for pair in map_p.into_inner() {
parse_tx_pair(pair, op, vld, param_pool, temp_id_serial, &identifier, coll)?;
}
Ok(coll)
Ok(identifier)
}
fn parse_tx_pair(
pair: Pair<'_>,
op: TxAction,
vld: Option<Validity>,
param_pool: &BTreeMap<String, DataValue>,
) -> Result<Either<EntityRep, (Symbol, DataValue)>> {
temp_id_serial: &mut usize,
parent_id: &EntityRep,
coll: &mut Vec<Quintuple>,
) -> Result<()> {
let mut src = pair.into_inner();
let fst = src.next().unwrap();
Ok(match fst.as_rule() {
Rule::compound_ident => {
let name = Symbol::from(fst.as_str());
let expr = build_expr(src.next().unwrap(), param_pool)?;
let c = expr.eval_to_const()?;
Right((name, c))
let (attr_name, is_multi) = match fst.as_rule() {
Rule::compound_ident_with_maybe_star => {
if fst.as_str().starts_with('*') {
(Symbol::from(fst.as_str().strip_prefix('*').unwrap()), true)
} else {
(Symbol::from(fst.as_str()), false)
}
}
Rule::raw_string | Rule::s_quoted_string | Rule::quoted_string => {
let name = Symbol(parse_string(fst)?);
let expr = build_expr(src.next().unwrap(), param_pool)?;
let c = expr.eval_to_const()?;
Right((name, c))
let s = parse_string(fst)?;
if s.starts_with('*') {
(Symbol::from(s.as_str().strip_prefix('*').unwrap()), true)
} else {
(Symbol::from(s.as_str()), false)
}
}
Rule::tx_ident_id => {
let expr = build_expr(src.next().unwrap(), param_pool)?;
let c = expr.eval_to_const()?;
let c = c
.get_non_neg_int()
.ok_or_else(|| miette!("integer id required, got {:?}", c))?;
let eid = EntityId(c);
ensure!(eid.is_perm(), "entity id invalid: {:?}", eid);
Left(EntityRep::Id(eid))
Rule::tx_ident_id | Rule::tx_ident_temp_id | Rule::tx_ident_key => return Ok(()),
_ => unreachable!(),
};
let tx_val = src.next().unwrap();
if is_multi {
match tx_val.as_rule() {
Rule::tx_list => {
for sub_val in tx_val.into_inner() {
parse_tx_val(
sub_val,
op,
attr_name.clone(),
vld,
param_pool,
temp_id_serial,
parent_id,
coll,
)?;
}
}
Rule::expr | Rule::tx_map => {
bail!("multi elements require a list")
}
_ => unreachable!(),
}
} else {
parse_tx_val(
tx_val,
op,
attr_name,
vld,
param_pool,
temp_id_serial,
parent_id,
coll,
)?;
}
Ok(())
}
fn parse_tx_val(
pair: Pair<'_>,
op: TxAction,
attr_name: Symbol,
vld: Option<Validity>,
param_pool: &BTreeMap<String, DataValue>,
temp_id_serial: &mut usize,
parent_id: &EntityRep,
coll: &mut Vec<Quintuple>,
) -> Result<()> {
match pair.as_rule() {
Rule::expr => {
let expr = build_expr(pair, param_pool)?;
let value = expr.eval_to_const()?;
coll.push(Quintuple {
entity: parent_id.clone(),
attr_name,
value,
action: op,
validity: vld,
})
}
Rule::tx_ident_temp_id => {
let expr = build_expr(src.next().unwrap(), param_pool)?;
let c = expr.eval_to_const()?;
let c = c
.get_string()
.ok_or_else(|| miette!("tid requires string, got {:?}", c))?;
Left(EntityRep::UserTempId(SmartString::from(c)))
Rule::tx_list => {
let mut list_coll = vec![];
for el in pair.into_inner() {
match el.as_rule() {
Rule::tx_map => bail!("map not allowed here"),
Rule::expr => {
let expr = build_expr(el, param_pool)?;
let value = expr.eval_to_const()?;
list_coll.push(value)
}
_ => unreachable!()
}
}
coll.push(Quintuple {
entity: parent_id.clone(),
attr_name,
value: DataValue::List(list_coll),
action: op,
validity: vld,
})
}
Rule::tx_ident_key => {
let expr = build_expr(src.next().unwrap(), param_pool)?;
let c = expr.eval_to_const()?;
let c = match c {
DataValue::List(l) => l,
v => bail!("key requires a list, got {:?}", v),
};
ensure!(c.len() == 2, "key requires a list of length 2");
let mut c = c.into_iter();
let attr = match c.next().unwrap() {
DataValue::Str(s) => Symbol(s),
v => bail!("attr name requires a string, got {:?}", v),
};
let val = c.next().unwrap();
Left(EntityRep::PullByKey(attr, val))
Rule::tx_map => {
let id = parse_tx_map(pair, op, vld, param_pool, temp_id_serial, coll)?;
coll.push(Quintuple {
entity: parent_id.clone(),
attr_name,
value: id.as_datavalue(),
action: op,
validity: vld,
})
}
_ => unreachable!(),
})
}
Ok(())
}

@ -1,4 +1,3 @@
use std::collections::btree_map::Entry;
use std::collections::BTreeMap;
use std::sync::atomic::Ordering;
@ -31,7 +30,6 @@ impl SessionTx {
let mut ret = Vec::with_capacity(payloads.len());
let mut str_temp_to_perm_ids: BTreeMap<SmartString<LazyCompact>, EntityId> =
BTreeMap::new();
let mut num_temp_to_perm_ids: BTreeMap<usize, EntityId> = BTreeMap::new();
for payload in &mut payloads {
if let EntityRep::UserTempId(symb) = &payload.entity {
ensure!(
@ -65,28 +63,6 @@ impl SessionTx {
1,
));
}
EntityRep::SysTempId(tid) => {
let eid = match num_temp_to_perm_ids.entry(tid) {
Entry::Vacant(e) => {
let new_eid = EntityId(
self.last_ent_id.fetch_add(1, Ordering::AcqRel) + 1,
);
e.insert(new_eid);
new_eid
}
Entry::Occupied(e) => *e.get(),
};
ret.push((
self.new_triple(
eid,
&attr,
&val,
vld,
)?,
1,
));
}
EntityRep::UserTempId(tempid) => {
let eid = *str_temp_to_perm_ids.get(&tempid).unwrap();
@ -119,7 +95,7 @@ impl SessionTx {
let attr = self.attr_by_name(&payload.attr_name)?.unwrap();
let eid = match payload.entity {
EntityRep::Id(id) => id,
EntityRep::UserTempId(_) | EntityRep::SysTempId(_) => {
EntityRep::UserTempId(_) => {
bail!("cannot retract with temp id")
}
EntityRep::PullByKey(symb, val) => {

@ -45,7 +45,7 @@ fn simple() {
r#"
:tx
{
_temp_id: "alice",
_tid: "alice",
person.first_name: "Alice",
person.age: 7,
person.last_name: "Amorist",
@ -54,7 +54,7 @@ fn simple() {
person.friend: "eve"
}
{
_temp_id: "bob",
_tid: "bob",
person.first_name: "Bob",
person.age: 70,
person.last_name: "Wonderland",
@ -63,13 +63,13 @@ fn simple() {
person.friend: "alice"
}
{
_temp_id: "eve",
_tid: "eve",
person.first_name: "Eve",
person.age: 18,
person.last_name: "Faking",
person.id: "eve_faking",
person.weight: 50,
person.friend: [
*person.friend: [
"alice",
"bob",
{
@ -83,14 +83,14 @@ fn simple() {
]
}
{
_temp_id: "david",
_tid: "david",
person.first_name: "David",
person.age: 7,
person.last_name: "Dull",
person.id: "david_dull",
person.weight: 25,
person.friend: {
_temp_id: "george",
_tid: "george",
person.first_name: "George",
person.age: 7,
person.last_name: "Geomancer",
@ -102,20 +102,20 @@ fn simple() {
)
.unwrap();
let query = r#"
friend_of_friend[?a, ?b] := [?a person.friend ?b];
friend_of_friend[?a, ?b] := [?a person.friend ?c], friend_of_friend[?c, ?b];
friend_of_friend[a, b] := [a person.friend b];
friend_of_friend[a, b] := [a person.friend c], friend_of_friend[c, b];
?[?a, ?n] := [?alice person.first_name "Alice"],
not friend_of_friend[?alice, ?a],
[?a person.first_name ?n];
?[a, n] := [alice person.first_name "Alice"],
not friend_of_friend[alice, a],
[a person.first_name n];
:limit 1;
:out {friend: ?a[person.first_name as first_name,
person.last_name as last_name]};
:sort -?n;
// :limit 1;
// :out {friend: ?a[person.first_name as first_name,
// person.last_name as last_name]};
:sort -n;
"#;
let ret = db.run_script(query).unwrap();
let res = to_string_pretty(&ret).unwrap();
info!("{}", res);
println!("{}", res);
}

Loading…
Cancel
Save