Allow :db/id to mentioned as a pull attribute.
This commit is contained in:
parent
4665eaa4dd
commit
a114bda46b
3 changed files with 49 additions and 9 deletions
|
@ -22,6 +22,11 @@ error_chain! {
|
||||||
description("unnamed attribute")
|
description("unnamed attribute")
|
||||||
display("attribute {:?} has no name", id)
|
display("attribute {:?} has no name", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RepeatedDbId {
|
||||||
|
description(":db/id repeated")
|
||||||
|
display(":db/id repeated")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
links {
|
links {
|
||||||
|
|
|
@ -79,12 +79,14 @@ use std::iter::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use mentat_core::{
|
use mentat_core::{
|
||||||
|
Binding,
|
||||||
Cloned,
|
Cloned,
|
||||||
Entid,
|
Entid,
|
||||||
HasSchema,
|
HasSchema,
|
||||||
NamespacedKeyword,
|
NamespacedKeyword,
|
||||||
Schema,
|
Schema,
|
||||||
StructuredMap,
|
StructuredMap,
|
||||||
|
TypedValue,
|
||||||
ValueRc,
|
ValueRc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -143,6 +145,7 @@ pub struct Puller {
|
||||||
// The range is the set of aliases to use in the output.
|
// The range is the set of aliases to use in the output.
|
||||||
attributes: BTreeMap<Entid, ValueRc<NamespacedKeyword>>,
|
attributes: BTreeMap<Entid, ValueRc<NamespacedKeyword>>,
|
||||||
attribute_spec: cache::AttributeSpec,
|
attribute_spec: cache::AttributeSpec,
|
||||||
|
db_id_alias: Option<ValueRc<NamespacedKeyword>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Puller {
|
impl Puller {
|
||||||
|
@ -166,6 +169,9 @@ impl Puller {
|
||||||
|
|
||||||
let mut names: BTreeMap<Entid, ValueRc<NamespacedKeyword>> = Default::default();
|
let mut names: BTreeMap<Entid, ValueRc<NamespacedKeyword>> = Default::default();
|
||||||
let mut attrs: BTreeSet<Entid> = Default::default();
|
let mut attrs: BTreeSet<Entid> = Default::default();
|
||||||
|
let db_id = ::std::rc::Rc::new(NamespacedKeyword::new("db", "id"));
|
||||||
|
let mut db_id_alias = None;
|
||||||
|
|
||||||
for attr in attributes.iter() {
|
for attr in attributes.iter() {
|
||||||
match attr {
|
match attr {
|
||||||
&PullAttributeSpec::Wildcard => {
|
&PullAttributeSpec::Wildcard => {
|
||||||
|
@ -183,6 +189,14 @@ impl Puller {
|
||||||
let alias = alias.as_ref()
|
let alias = alias.as_ref()
|
||||||
.map(|ref r| r.to_value_rc());
|
.map(|ref r| r.to_value_rc());
|
||||||
match attribute {
|
match attribute {
|
||||||
|
// Handle :db/id.
|
||||||
|
&PullConcreteAttribute::Ident(ref i) if i.as_ref() == db_id.as_ref() => {
|
||||||
|
// We only allow :db/id once.
|
||||||
|
if db_id_alias.is_some() {
|
||||||
|
bail!(ErrorKind::RepeatedDbId);
|
||||||
|
}
|
||||||
|
db_id_alias = Some(alias.unwrap_or_else(|| db_id.to_value_rc()));
|
||||||
|
},
|
||||||
&PullConcreteAttribute::Ident(ref i) => {
|
&PullConcreteAttribute::Ident(ref i) => {
|
||||||
if let Some(entid) = schema.get_entid(i) {
|
if let Some(entid) = schema.get_entid(i) {
|
||||||
let name = alias.unwrap_or_else(|| i.to_value_rc());
|
let name = alias.unwrap_or_else(|| i.to_value_rc());
|
||||||
|
@ -203,6 +217,7 @@ impl Puller {
|
||||||
Ok(Puller {
|
Ok(Puller {
|
||||||
attributes: names,
|
attributes: names,
|
||||||
attribute_spec: cache::AttributeSpec::specified(&attrs, schema),
|
attribute_spec: cache::AttributeSpec::specified(&attrs, schema),
|
||||||
|
db_id_alias,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,6 +249,17 @@ impl Puller {
|
||||||
// TODO: should we walk `e` then `a`, or `a` then `e`? Possibly the right answer
|
// TODO: should we walk `e` then `a`, or `a` then `e`? Possibly the right answer
|
||||||
// is just to collect differently!
|
// is just to collect differently!
|
||||||
let mut maps = BTreeMap::new();
|
let mut maps = BTreeMap::new();
|
||||||
|
|
||||||
|
// Collect :db/id if requested.
|
||||||
|
if let Some(ref alias) = self.db_id_alias {
|
||||||
|
for e in entities.iter() {
|
||||||
|
let mut r = maps.entry(*e)
|
||||||
|
.or_insert(ValueRc::new(StructuredMap::default()));
|
||||||
|
let mut m = ValueRc::get_mut(r).unwrap();
|
||||||
|
m.insert(alias.clone(), Binding::Scalar(TypedValue::Ref(*e)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (name, cache) in self.attributes.iter().filter_map(|(a, name)|
|
for (name, cache) in self.attributes.iter().filter_map(|(a, name)|
|
||||||
caches.forward_attribute_cache_for_attribute(schema, *a)
|
caches.forward_attribute_cache_for_attribute(schema, *a)
|
||||||
.map(|cache| (name.clone(), cache))) {
|
.map(|cache| (name.clone(), cache))) {
|
||||||
|
|
|
@ -116,7 +116,9 @@ fn test_simple_pull() {
|
||||||
assert_eq!(pulled, expected);
|
assert_eq!(pulled, expected);
|
||||||
|
|
||||||
// Now test pull inside the query itself.
|
// Now test pull inside the query itself.
|
||||||
let query = r#"[:find ?hood (pull ?district [[:district/name :as :district/district] :district/region])
|
let query = r#"[:find ?hood (pull ?district [:db/id
|
||||||
|
[:district/name :as :district/district]
|
||||||
|
:district/region])
|
||||||
:where
|
:where
|
||||||
(or [?hood :neighborhood/name "Beacon Hill"]
|
(or [?hood :neighborhood/name "Beacon Hill"]
|
||||||
[?hood :neighborhood/name "Capitol Hill"])
|
[?hood :neighborhood/name "Capitol Hill"])
|
||||||
|
@ -127,22 +129,24 @@ fn test_simple_pull() {
|
||||||
.into_rel_result()
|
.into_rel_result()
|
||||||
.expect("results");
|
.expect("results");
|
||||||
|
|
||||||
let beacon_district: Vec<(NamespacedKeyword, TypedValue)> = vec![
|
let beacon_district_pull: Vec<(NamespacedKeyword, TypedValue)> = vec![
|
||||||
|
(kw!(:db/id), TypedValue::Ref(beacon_district)),
|
||||||
(kw!(:district/district), "Greater Duwamish".into()),
|
(kw!(:district/district), "Greater Duwamish".into()),
|
||||||
(kw!(:district/region), schema.get_entid(&NamespacedKeyword::new("region", "se")).unwrap().into())
|
(kw!(:district/region), schema.get_entid(&NamespacedKeyword::new("region", "se")).unwrap().into())
|
||||||
];
|
];
|
||||||
let beacon_district: StructuredMap = beacon_district.into();
|
let beacon_district_pull: StructuredMap = beacon_district_pull.into();
|
||||||
let capitol_district: Vec<(NamespacedKeyword, TypedValue)> = vec![
|
let capitol_district_pull: Vec<(NamespacedKeyword, TypedValue)> = vec![
|
||||||
|
(kw!(:db/id), TypedValue::Ref(capitol_district)),
|
||||||
(kw!(:district/district), "East".into()),
|
(kw!(:district/district), "East".into()),
|
||||||
(kw!(:district/region), schema.get_entid(&NamespacedKeyword::new("region", "e")).unwrap().into())
|
(kw!(:district/region), schema.get_entid(&NamespacedKeyword::new("region", "e")).unwrap().into())
|
||||||
];
|
];
|
||||||
let capitol_district: StructuredMap = capitol_district.into();
|
let capitol_district_pull: StructuredMap = capitol_district_pull.into();
|
||||||
|
|
||||||
let expected = RelResult {
|
let expected = RelResult {
|
||||||
width: 2,
|
width: 2,
|
||||||
values: vec![
|
values: vec![
|
||||||
TypedValue::Ref(capitol).into(), capitol_district.into(),
|
TypedValue::Ref(capitol).into(), capitol_district_pull.into(),
|
||||||
TypedValue::Ref(beacon).into(), beacon_district.into(),
|
TypedValue::Ref(beacon).into(), beacon_district_pull.into(),
|
||||||
].into(),
|
].into(),
|
||||||
};
|
};
|
||||||
assert_eq!(results, expected.clone());
|
assert_eq!(results, expected.clone());
|
||||||
|
@ -158,14 +162,19 @@ fn test_simple_pull() {
|
||||||
|
|
||||||
// Execute a scalar query where the body is constant.
|
// Execute a scalar query where the body is constant.
|
||||||
// TODO: we shouldn't require `:where`; that makes this non-constant!
|
// TODO: we shouldn't require `:where`; that makes this non-constant!
|
||||||
let query = r#"[:find (pull ?hood [:neighborhood/name]) . :in ?hood
|
let query = r#"[:find (pull ?hood [[:db/id :as :neighborhood/id]
|
||||||
|
:neighborhood/name]) .
|
||||||
|
:in ?hood
|
||||||
:where [?hood :neighborhood/district _]]"#;
|
:where [?hood :neighborhood/district _]]"#;
|
||||||
let result = reader.q_once(query, QueryInputs::with_value_sequence(vec![(var!(?hood), TypedValue::Ref(beacon))]))
|
let result = reader.q_once(query, QueryInputs::with_value_sequence(vec![(var!(?hood), TypedValue::Ref(beacon))]))
|
||||||
.into_scalar_result()
|
.into_scalar_result()
|
||||||
.expect("success")
|
.expect("success")
|
||||||
.expect("result");
|
.expect("result");
|
||||||
|
|
||||||
let expected: StructuredMap = vec![(kw!(:neighborhood/name), TypedValue::from("Beacon Hill"))].into();
|
let expected: StructuredMap = vec![
|
||||||
|
(kw!(:neighborhood/name), TypedValue::from("Beacon Hill")),
|
||||||
|
(kw!(:neighborhood/id), TypedValue::Ref(beacon)),
|
||||||
|
].into();
|
||||||
assert_eq!(result, expected.into());
|
assert_eq!(result, expected.into());
|
||||||
|
|
||||||
// Collect the names and regions of all districts.
|
// Collect the names and regions of all districts.
|
||||||
|
|
Loading…
Reference in a new issue