main
Ziyang Hu 2 years ago
parent 95af831c82
commit 006ba78ba6

@ -31,7 +31,9 @@ pub enum TableRowGetter {
impl Debug for TableRowGetter {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { TableRowGetter::Dummy => write!(f, "DummyRowGetter") }
match self {
TableRowGetter::Dummy => write!(f, "DummyRowGetter"),
}
}
}
@ -55,7 +57,7 @@ pub enum ChainJoinKind {
NodeToFwdEdge,
NodeToBwdEdge,
FwdEdgeToNode,
BwdEdgeToNode
BwdEdgeToNode,
}
#[derive(Debug)]
@ -87,7 +89,7 @@ pub enum ExecPlan<'a> {
right_info: TableInfo,
kind: ChainJoinKind,
left_outer: bool,
right_outer: bool
right_outer: bool,
},
// IndexIt {it: ..}
KeySortedWithAssocItPlan {
@ -163,12 +165,8 @@ impl<'a> ExecPlan<'a> {
}
ExecPlan::KeyedUnionItPlan { left, .. } => left.tuple_widths(),
ExecPlan::KeyedDifferenceItPlan { left, .. } => left.tuple_widths(),
ExecPlan::FilterItPlan { source, .. } => {
source.tuple_widths()
}
ExecPlan::EvalItPlan { source, .. } => {
source.tuple_widths()
}
ExecPlan::FilterItPlan { source, .. } => source.tuple_widths(),
ExecPlan::EvalItPlan { source, .. } => source.tuple_widths(),
ExecPlan::BagsUnionIt { bags } => {
if bags.is_empty() {
(0, 0)
@ -202,7 +200,7 @@ impl<'a> OutputItPlan<'a> {
}
impl<'a> ExecPlan<'a> {
pub fn iter(&'a self) -> Result<Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>> {
pub fn iter(&'a self) -> Result<Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>> {
match self {
ExecPlan::NodeItPlan { it, info, .. } => {
let it = it.try_get()?;
@ -217,7 +215,11 @@ impl<'a> ExecPlan<'a> {
prefix_tuple.push_int(info.src_table_id.id);
it.seek(prefix_tuple);
Ok(Box::new(EdgeIterator { it, started: false, src_table_id: info.src_table_id.id }))
Ok(Box::new(EdgeIterator {
it,
started: false,
src_table_id: info.src_table_id.id,
}))
}
ExecPlan::EdgeKeyOnlyBwdItPlan { it, info, .. } => {
let it = it.try_get()?;
@ -225,7 +227,11 @@ impl<'a> ExecPlan<'a> {
prefix_tuple.push_int(info.dst_table_id.id);
it.seek(prefix_tuple);
Ok(Box::new(EdgeKeyOnlyBwdIterator { it, started: false, dst_table_id: info.dst_table_id.id }))
Ok(Box::new(EdgeKeyOnlyBwdIterator {
it,
started: false,
dst_table_id: info.dst_table_id.id,
}))
}
ExecPlan::KeySortedWithAssocItPlan { main, associates } => {
let buffer = iter::repeat_with(|| None).take(associates.len()).collect();
@ -259,7 +265,7 @@ impl<'a> ExecPlan<'a> {
ExecPlan::EvalItPlan {
source: it,
keys,
vals
vals,
} => Ok(Box::new(EvalIterator {
it: it.iter()?,
keys,
@ -303,12 +309,14 @@ impl<'a> ExecPlan<'a> {
left: left.iter()?,
right: right.iter()?,
})),
ExecPlan::KeyedDifferenceItPlan { left, right } => Ok(Box::new(KeyedDifferenceIterator {
left: left.iter()?,
right: right.iter()?,
right_cache: None,
started: false,
})),
ExecPlan::KeyedDifferenceItPlan { left, right } => {
Ok(Box::new(KeyedDifferenceIterator {
left: left.iter()?,
right: right.iter()?,
right_cache: None,
started: false,
}))
}
ExecPlan::BagsUnionIt { bags } => {
let bags = bags.iter().map(|i| i.iter()).collect::<Result<Vec<_>>>()?;
Ok(Box::new(BagsUnionIterator { bags, current: 0 }))
@ -324,8 +332,8 @@ impl<'a> ExecPlan<'a> {
}
pub struct KeyedUnionIterator<'a> {
left: Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>,
right: Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>,
left: Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>,
right: Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>,
}
impl<'a> Iterator for KeyedUnionIterator<'a> {
@ -376,8 +384,8 @@ impl<'a> Iterator for KeyedUnionIterator<'a> {
}
pub struct KeyedDifferenceIterator<'a> {
left: Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>,
right: Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>,
left: Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>,
right: Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>,
right_cache: Option<MegaTuple>,
started: bool,
}
@ -445,7 +453,7 @@ impl<'a> Iterator for KeyedDifferenceIterator<'a> {
}
pub struct BagsUnionIterator<'a> {
bags: Vec<Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>>,
bags: Vec<Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>>,
current: usize,
}
@ -568,7 +576,7 @@ impl<'a> Iterator for EdgeKeyOnlyBwdIterator<'a> {
}
pub struct KeySortedWithAssocIterator<'a> {
main: Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>,
main: Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>,
associates: Vec<NodeIterator<'a>>,
buffer: Vec<Option<(CowTuple, CowTuple)>>,
}
@ -588,7 +596,8 @@ impl<'a> Iterator for KeySortedWithAssocIterator<'a> {
};
let l = self.associates.len();
// initialize vector for associate values
let mut assoc_vals: Vec<Option<CowTuple>> = iter::repeat_with(|| None).take(l).collect();
let mut assoc_vals: Vec<Option<CowTuple>> =
iter::repeat_with(|| None).take(l).collect();
// let l = assoc_vals.len();
#[allow(clippy::needless_range_loop)]
for i in 0..l {
@ -651,8 +660,8 @@ impl<'a> Iterator for KeySortedWithAssocIterator<'a> {
}
pub struct OuterMergeJoinIterator<'a> {
left: Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>,
right: Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>,
left: Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>,
right: Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>,
left_outer: bool,
right_outer: bool,
left_keys: &'a [(TableId, ColId)],
@ -800,8 +809,8 @@ impl<'a> Iterator for OuterMergeJoinIterator<'a> {
}
pub struct MergeJoinIterator<'a> {
left: Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>,
right: Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>,
left: Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>,
right: Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>,
left_keys: &'a [(TableId, ColId)],
right_keys: &'a [(TableId, ColId)],
}
@ -861,10 +870,10 @@ impl<'a> Iterator for MergeJoinIterator<'a> {
}
pub struct CartesianProdIterator<'a> {
left: Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>,
left: Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>,
left_cache: MegaTuple,
right_source: &'a ExecPlan<'a>,
right: Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>,
right: Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>,
}
impl<'a> Iterator for CartesianProdIterator<'a> {
@ -907,7 +916,7 @@ impl<'a> Iterator for CartesianProdIterator<'a> {
}
pub struct FilterIterator<'a> {
it: Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>,
it: Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>,
filter: &'a Value<'a>,
}
@ -935,7 +944,7 @@ impl<'a> Iterator for FilterIterator<'a> {
}
pub struct OutputIterator<'a> {
it: Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>,
it: Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>,
transform: &'a Value<'a>,
}
@ -961,7 +970,7 @@ impl<'a> Iterator for OutputIterator<'a> {
}
pub struct EvalIterator<'a> {
it: Box<dyn Iterator<Item=Result<MegaTuple>> + 'a>,
it: Box<dyn Iterator<Item = Result<MegaTuple>> + 'a>,
keys: &'a [(String, Value<'a>)],
vals: &'a [(String, Value<'a>)],
}
@ -1072,11 +1081,14 @@ mod tests {
.next()
.unwrap();
let sel_pat = sess.parse_select_pattern(p).unwrap();
let sel_vals = Value::Dict(sel_pat.vals.into_iter().map(|(k, v)| (k.into(), v)).collect());
let amap = sess.node_accessor_map(
&from_pat.binding,
&from_pat.info,
let sel_vals = Value::Dict(
sel_pat
.vals
.into_iter()
.map(|(k, v)| (k.into(), v))
.collect(),
);
let amap = sess.node_accessor_map(&from_pat.binding, &from_pat.info);
let (_, vals) = sess
.partial_eval(sel_vals, &Default::default(), &amap)
.unwrap();
@ -1211,7 +1223,6 @@ mod tests {
let duration = start.elapsed();
println!("Time elapsed {:?}", duration);
let s = r##"from (e:Employee)-[hj:HasJob]->(j:Job)
where e.id == 110
select { fname: e.first_name, salary: hj.salary, job: j.title }"##;

@ -1,16 +1,16 @@
use std::collections::btree_map::Entry;
use crate::db::engine::Session;
use crate::db::iterator::{ChainJoinKind, ExecPlan, IteratorSlot, OutputItPlan, TableRowGetter};
use crate::db::query::{EdgeOrNodeEl, EdgeOrNodeKind, FromEl, Selection};
use crate::db::table::{ColId, TableId, TableInfo};
use crate::error::CozoError::LogicError;
use crate::error::Result;
use crate::parser::Rule;
use crate::relation::data::DataKind;
use crate::relation::value::{StaticValue, Value};
use cozorocks::IteratorPtr;
use pest::iterators::Pair;
use std::collections::btree_map::Entry;
use std::collections::BTreeMap;
use crate::error::CozoError::LogicError;
use crate::relation::data::DataKind;
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum OuterJoinType {
@ -40,20 +40,43 @@ fn merge_accessor_map(mut left: AccessorMap, right: AccessorMap) -> AccessorMap
fn convert_to_relative_accessor_map(amap: AccessorMap) -> AccessorMap {
// TODO this only handles the simplest case
fn convert_inner(inner: BTreeMap<String, (TableId, ColId)>) -> BTreeMap<String, (TableId, ColId)> {
inner.into_iter().map(|(k, (_tid, cid))| {
(k, (TableId::new(false, 0), cid))
}).collect()
fn convert_inner(
inner: BTreeMap<String, (TableId, ColId)>,
) -> BTreeMap<String, (TableId, ColId)> {
inner
.into_iter()
.map(|(k, (_tid, cid))| (k, (TableId::new(false, 0), cid)))
.collect()
}
amap.into_iter().map(|(k, v)| (k, convert_inner(v))).collect()
amap.into_iter()
.map(|(k, v)| (k, convert_inner(v)))
.collect()
}
fn shift_accessor_map(amap: AccessorMap, (keyshift, valshift): (usize, usize)) -> AccessorMap {
let shift_inner = |inner: BTreeMap<String, (TableId, ColId)>| -> BTreeMap<String, (TableId, ColId)> {
inner.into_iter().map(|(k, (tid, cid))| {
(k, (TableId::new(tid.in_root, tid.id + if cid.is_key { keyshift as i64 } else { valshift as i64 }), cid))
}).collect()
};
let shift_inner =
|inner: BTreeMap<String, (TableId, ColId)>| -> BTreeMap<String, (TableId, ColId)> {
inner
.into_iter()
.map(|(k, (tid, cid))| {
(
k,
(
TableId::new(
tid.in_root,
tid.id
+ if cid.is_key {
keyshift as i64
} else {
valshift as i64
},
),
cid,
),
)
})
.collect()
};
amap.into_iter().map(|(k, v)| (k, shift_inner(v))).collect()
}
@ -64,7 +87,10 @@ impl<'a> Session<'a> {
v.0
})
}
fn do_reify_intermediate_plan(&'a self, plan: ExecPlan<'a>) -> Result<(ExecPlan<'a>, AccessorMap)> {
fn do_reify_intermediate_plan(
&'a self,
plan: ExecPlan<'a>,
) -> Result<(ExecPlan<'a>, AccessorMap)> {
let res = match plan {
ExecPlan::NodeItPlan { info, binding, .. } => {
let amap = match &binding {
@ -80,11 +106,7 @@ impl<'a> Session<'a> {
self.txn.iterator(true, &self.temp_cf)
};
let it = IteratorSlot::Reified(it);
let plan = ExecPlan::NodeItPlan {
it,
info,
binding,
};
let plan = ExecPlan::NodeItPlan { it, info, binding };
(plan, amap)
}
ExecPlan::EdgeItPlan { info, binding, .. } => {
@ -101,11 +123,7 @@ impl<'a> Session<'a> {
self.txn.iterator(true, &self.temp_cf)
};
let it = IteratorSlot::Reified(it);
let plan = ExecPlan::EdgeItPlan {
it,
info,
binding,
};
let plan = ExecPlan::EdgeItPlan { it, info, binding };
(plan, amap)
}
ExecPlan::EdgeBwdItPlan { .. } => {
@ -136,16 +154,26 @@ impl<'a> Session<'a> {
};
(plan, amap)
}
ExecPlan::EvalItPlan { source: it, keys, vals } => {
ExecPlan::EvalItPlan {
source: it,
keys,
vals,
} => {
let (inner, amap) = self.do_reify_intermediate_plan(*it)?;
let keys = keys.into_iter().map(|(k, v)| -> Result<_> {
let (_, v) = self.partial_eval(v, &Default::default(), &amap)?;
Ok((k, v))
}).collect::<Result<Vec<_>>>()?;
let vals = vals.into_iter().map(|(k, v)| -> Result<_> {
let (_, v) = self.partial_eval(v, &Default::default(), &amap)?;
Ok((k, v))
}).collect::<Result<Vec<_>>>()?;
let keys = keys
.into_iter()
.map(|(k, v)| -> Result<_> {
let (_, v) = self.partial_eval(v, &Default::default(), &amap)?;
Ok((k, v))
})
.collect::<Result<Vec<_>>>()?;
let vals = vals
.into_iter()
.map(|(k, v)| -> Result<_> {
let (_, v) = self.partial_eval(v, &Default::default(), &amap)?;
Ok((k, v))
})
.collect::<Result<Vec<_>>>()?;
let plan = ExecPlan::EvalItPlan {
source: inner.into(),
keys,
@ -154,7 +182,14 @@ impl<'a> Session<'a> {
(plan, amap)
}
ExecPlan::BagsUnionIt { .. } => todo!(),
ExecPlan::ChainJoinItPlan { .. } => {
ExecPlan::ChainJoinItPlan {
left,
right,
right_info,
kind,
left_outer,
right_outer,
} => {
todo!()
}
};
@ -163,7 +198,11 @@ impl<'a> Session<'a> {
pub fn reify_output_plan(&'a self, plan: ExecPlan<'a>) -> Result<OutputItPlan<'a>> {
let plan = self.reify_intermediate_plan(plan)?;
let plan = match plan {
ExecPlan::EvalItPlan { source: it, mut keys, vals } => {
ExecPlan::EvalItPlan {
source: it,
mut keys,
vals,
} => {
keys.extend(vals);
let filter = Value::Dict(keys.into_iter().map(|(k, v)| (k.into(), v)).collect());
OutputItPlan {
@ -195,82 +234,80 @@ impl<'a> Session<'a> {
self.convert_select_data_to_plan(plan, select_data)
}
fn convert_from_data_to_plan(&self, from_data: Vec<FromEl>) -> Result<ExecPlan> {
let convert_el = |el|
match el {
FromEl::Simple(el) => {
match el.info.kind {
DataKind::Node => {
Ok(ExecPlan::NodeItPlan {
it: IteratorSlot::Dummy,
info: el.info,
binding: Some(el.binding),
})
}
DataKind::Edge => {
Ok(ExecPlan::EdgeItPlan {
it: IteratorSlot::Dummy,
info: el.info,
binding: Some(el.binding),
})
}
_ => Err(LogicError("Wrong type for table binding".to_string()))
}
}
FromEl::Chain(ch) => {
let mut it = ch.into_iter();
let nxt = it.next().ok_or_else(|| LogicError("Empty chain not allowed".to_string()))?;
let mut prev_kind = nxt.kind;
let mut prev_left_outer = nxt.left_outer_marker;
let mut plan = match prev_kind {
EdgeOrNodeKind::Node => {
ExecPlan::NodeItPlan {
it: IteratorSlot::Dummy,
info: nxt.info,
binding: nxt.binding,
let convert_el = |el| match el {
FromEl::Simple(el) => match el.info.kind {
DataKind::Node => Ok(ExecPlan::NodeItPlan {
it: IteratorSlot::Dummy,
info: el.info,
binding: Some(el.binding),
}),
DataKind::Edge => Ok(ExecPlan::EdgeItPlan {
it: IteratorSlot::Dummy,
info: el.info,
binding: Some(el.binding),
}),
_ => Err(LogicError("Wrong type for table binding".to_string())),
},
FromEl::Chain(ch) => {
let mut it = ch.into_iter();
let nxt = it
.next()
.ok_or_else(|| LogicError("Empty chain not allowed".to_string()))?;
let mut prev_kind = nxt.kind;
let mut prev_left_outer = nxt.left_outer_marker;
let mut plan = match prev_kind {
EdgeOrNodeKind::Node => ExecPlan::NodeItPlan {
it: IteratorSlot::Dummy,
info: nxt.info,
binding: nxt.binding,
},
EdgeOrNodeKind::FwdEdge => ExecPlan::EdgeItPlan {
it: IteratorSlot::Dummy,
info: nxt.info,
binding: nxt.binding,
},
EdgeOrNodeKind::BwdEdge => ExecPlan::EdgeBwdItPlan {
it: IteratorSlot::Dummy,
info: nxt.info,
binding: nxt.binding,
getter: TableRowGetter::Dummy,
},
};
for el in it {
plan = ExecPlan::ChainJoinItPlan {
left: plan.into(),
right: TableRowGetter::Dummy,
right_info: el.info,
kind: match (prev_kind, el.kind) {
(EdgeOrNodeKind::Node, EdgeOrNodeKind::FwdEdge) => {
ChainJoinKind::NodeToFwdEdge
}
(EdgeOrNodeKind::Node, EdgeOrNodeKind::BwdEdge) => {
ChainJoinKind::NodeToBwdEdge
}
}
EdgeOrNodeKind::FwdEdge => {
ExecPlan::EdgeItPlan {
it: IteratorSlot::Dummy,
info: nxt.info,
binding: nxt.binding,
(EdgeOrNodeKind::FwdEdge, EdgeOrNodeKind::Node) => {
ChainJoinKind::FwdEdgeToNode
}
}
EdgeOrNodeKind::BwdEdge => {
ExecPlan::EdgeBwdItPlan {
it: IteratorSlot::Dummy,
info: nxt.info,
binding: nxt.binding,
getter: TableRowGetter::Dummy
(EdgeOrNodeKind::BwdEdge, EdgeOrNodeKind::Node) => {
ChainJoinKind::BwdEdgeToNode
}
}
_ => unreachable!(),
},
left_outer: prev_left_outer,
right_outer: el.right_outer_marker,
};
for el in it {
plan = ExecPlan::ChainJoinItPlan {
left: plan.into(),
right: TableRowGetter::Dummy,
right_info: el.info,
kind: match (prev_kind, el.kind) {
(EdgeOrNodeKind::Node, EdgeOrNodeKind::FwdEdge) => ChainJoinKind::NodeToFwdEdge,
(EdgeOrNodeKind::Node, EdgeOrNodeKind::BwdEdge) => ChainJoinKind::NodeToBwdEdge,
(EdgeOrNodeKind::FwdEdge, EdgeOrNodeKind::Node) => ChainJoinKind::FwdEdgeToNode,
(EdgeOrNodeKind::BwdEdge, EdgeOrNodeKind::Node) => ChainJoinKind::BwdEdgeToNode,
_ => unreachable!()
},
left_outer: prev_left_outer,
right_outer: el.right_outer_marker
};
prev_kind = el.kind;
prev_left_outer = el.left_outer_marker;
}
println!("{:#?}", plan);
Ok(plan)
prev_kind = el.kind;
prev_left_outer = el.left_outer_marker;
}
};
println!("{:#?}", plan);
Ok(plan)
}
};
let mut from_data = from_data.into_iter();
let fst = from_data.next().ok_or_else(||
LogicError("Empty from clause".to_string()))?;
let fst = from_data
.next()
.ok_or_else(|| LogicError("Empty from clause".to_string()))?;
let mut res = convert_el(fst)?;
for nxt in from_data {
let nxt = convert_el(nxt)?;
@ -281,11 +318,7 @@ impl<'a> Session<'a> {
}
Ok(res)
}
pub(crate) fn node_accessor_map(
&self,
binding: &str,
info: &TableInfo,
) -> AccessorMap {
pub(crate) fn node_accessor_map(&self, binding: &str, info: &TableInfo) -> AccessorMap {
let mut ret = BTreeMap::new();
for (i, (k, _)) in info.key_typing.iter().enumerate() {
ret.insert(k.into(), (info.table_id, (true, i).into()));
@ -303,22 +336,30 @@ impl<'a> Session<'a> {
}
BTreeMap::from([(binding.to_string(), ret)])
}
pub(crate) fn edge_accessor_map(
&self,
binding: &str,
info: &TableInfo,
) -> AccessorMap {
pub(crate) fn edge_accessor_map(&self, binding: &str, info: &TableInfo) -> AccessorMap {
let mut ret = BTreeMap::new();
let src_key_len = info.src_key_typing.len();
let dst_key_len = info.dst_key_typing.len();
for (i, (k, _)) in info.src_key_typing.iter().enumerate() {
ret.insert("_src_".to_string() + k, (info.table_id, (true, 1 + i).into()));
ret.insert(
"_src_".to_string() + k,
(info.table_id, (true, 1 + i).into()),
);
}
for (i, (k, _)) in info.dst_key_typing.iter().enumerate() {
ret.insert("_dst_".to_string() + k, (info.table_id, (true, 2 + src_key_len + i).into()));
ret.insert(
"_dst_".to_string() + k,
(info.table_id, (true, 2 + src_key_len + i).into()),
);
}
for (i, (k, _)) in info.key_typing.iter().enumerate() {
ret.insert(k.into(), (info.table_id, (true, 2 + src_key_len + dst_key_len + i).into()));
ret.insert(
k.into(),
(
info.table_id,
(true, 2 + src_key_len + dst_key_len + i).into(),
),
);
}
for (i, (k, _)) in info.val_typing.iter().enumerate() {
ret.insert(k.into(), (info.table_id, (false, i).into()));
@ -341,12 +382,10 @@ impl<'a> Session<'a> {
let where_data = self.partial_eval(where_data, &Default::default(), &Default::default());
let plan = match where_data?.1 {
Value::Bool(true) => plan,
v => {
ExecPlan::FilterItPlan {
source: Box::new(plan),
filter: v,
}
}
v => ExecPlan::FilterItPlan {
source: Box::new(plan),
filter: v,
},
};
Ok(plan)
}
@ -396,4 +435,4 @@ impl<'a> Session<'a> {
mod tests {
#[test]
fn from_data() {}
}
}

@ -248,7 +248,10 @@ impl<'a> Session<'a> {
}
let vals = if merged.is_empty() {
collected_vals.into_iter().map(|(k, v)| (k.to_string(), v.to_static())).collect::<Vec<_>>()
collected_vals
.into_iter()
.map(|(k, v)| (k.to_string(), v.to_static()))
.collect::<Vec<_>>()
} else {
// construct it with help of partial eval
todo!()

Loading…
Cancel
Save