diff --git a/query-algebrizer/src/clauses/mod.rs b/query-algebrizer/src/clauses/mod.rs index e457987a..7ee93032 100644 --- a/query-algebrizer/src/clauses/mod.rs +++ b/query-algebrizer/src/clauses/mod.rs @@ -611,13 +611,8 @@ impl ConjoiningClauses { } /// Ensure that the given place has the correct types to be a tx-id. - /// Right now this is mostly unimplemented: we fail hard if anything but a placeholder is - /// present. fn constrain_to_tx(&mut self, tx: &PatternNonValuePlace) { - match *tx { - PatternNonValuePlace::Placeholder => (), - _ => unimplemented!(), // TODO: #440. - } + self.constrain_to_ref(tx); } /// Ensure that the given place can be an entity, and is congruent with existing types. diff --git a/query-algebrizer/src/clauses/pattern.rs b/query-algebrizer/src/clauses/pattern.rs index 7874ebdf..516816b6 100644 --- a/query-algebrizer/src/clauses/pattern.rs +++ b/query-algebrizer/src/clauses/pattern.rs @@ -81,7 +81,7 @@ impl ConjoiningClauses { // Sorry for the duplication; Rust makes it a pain to abstract this. // The transaction part of a pattern must be an entid, variable, or placeholder. - self.constrain_to_tx(&pattern.tx); // See #440. + self.constrain_to_tx(&pattern.tx); self.constrain_to_ref(&pattern.entity); self.constrain_to_ref(&pattern.attribute); @@ -238,9 +238,27 @@ impl ConjoiningClauses { if value_type.is_none() { self.wheres.add_intersection(ColumnConstraint::HasType(col.clone(), typed_value_type)); } - }, } + + match pattern.tx { + PatternNonValuePlace::Placeholder => (), + PatternNonValuePlace::Variable(ref v) => { + self.bind_column_to_var(schema, col.clone(), DatomsColumn::Tx, v.clone()); + }, + PatternNonValuePlace::Entid(entid) => { + self.constrain_column_to_entity(col.clone(), DatomsColumn::Tx, entid); + }, + PatternNonValuePlace::Ident(ref ident) => { + if let Some(entid) = self.entid_for_ident(schema, ident.as_ref()) { + self.constrain_column_to_entity(col.clone(), DatomsColumn::Tx, entid) + } else { + // A resolution failure means we're done here. + self.mark_known_empty(EmptyBecause::UnresolvedIdent(ident.cloned())); + return; + } + } + } } pub fn apply_pattern<'s, 'p>(&mut self, schema: &'s Schema, pattern: Pattern) { diff --git a/tests/query.rs b/tests/query.rs index 9a1f73e9..6bf2853a 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -232,11 +232,9 @@ fn test_instants_and_uuids() { conn.transact(&mut c, r#"[ [:db/add "u" :foo/uuid #uuid "cf62d552-6569-4d1b-b667-04703041dfc4"] ]"#).unwrap(); - - // We don't yet support getting the tx from a pattern (#440), so run wild. let r = conn.q_once(&mut c, r#"[:find [?x ?u ?when] - :where [?x :foo/uuid ?u] + :where [?x :foo/uuid ?u ?tx] [?tx :db/txInstant ?when]]"#, None); match r { Result::Ok(QueryResults::Tuple(Some(vals))) => { @@ -257,6 +255,72 @@ fn test_instants_and_uuids() { } } +#[test] +fn test_tx() { + let mut c = new_connection("").expect("Couldn't open conn."); + let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); + conn.transact(&mut c, r#"[ + [:db/add "s" :db/ident :foo/uuid] + [:db/add "s" :db/valueType :db.type/uuid] + [:db/add "s" :db/cardinality :db.cardinality/one] + ]"#).expect("successful transaction"); + + let t = conn.transact(&mut c, r#"[ + [:db/add "u" :foo/uuid #uuid "cf62d552-6569-4d1b-b667-04703041dfc4"] + ]"#).expect("successful transaction"); + + conn.transact(&mut c, r#"[ + [:db/add "u" :foo/uuid #uuid "550e8400-e29b-41d4-a716-446655440000"] + ]"#).expect("successful transaction"); + + let r = conn.q_once(&mut c, + r#"[:find ?tx + :where [?x :foo/uuid #uuid "cf62d552-6569-4d1b-b667-04703041dfc4" ?tx]]"#, None); + match r { + Result::Ok(QueryResults::Rel(ref v)) => { + assert_eq!(*v, vec![ + vec![TypedValue::Ref(t.tx_id),] + ]); + }, + _ => panic!("Expected query to work."), + } +} + +#[test] +fn test_tx_as_input() { + let mut c = new_connection("").expect("Couldn't open conn."); + let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); + conn.transact(&mut c, r#"[ + [:db/add "s" :db/ident :foo/uuid] + [:db/add "s" :db/valueType :db.type/uuid] + [:db/add "s" :db/cardinality :db.cardinality/one] + ]"#).expect("successful transaction"); + conn.transact(&mut c, r#"[ + [:db/add "u" :foo/uuid #uuid "550e8400-e29b-41d4-a716-446655440000"] + ]"#).expect("successful transaction"); + let t = conn.transact(&mut c, r#"[ + [:db/add "u" :foo/uuid #uuid "cf62d552-6569-4d1b-b667-04703041dfc4"] + ]"#).expect("successful transaction"); + conn.transact(&mut c, r#"[ + [:db/add "u" :foo/uuid #uuid "267bab92-ee39-4ca2-b7f0-1163a85af1fb"] + ]"#).expect("successful transaction"); + + let tx = (Variable::from_valid_name("?tx"), TypedValue::Ref(t.tx_id)); + let inputs = QueryInputs::with_value_sequence(vec![tx]); + let r = conn.q_once(&mut c, + r#"[:find ?uuid + :in ?tx + :where [?x :foo/uuid ?uuid ?tx]]"#, inputs); + match r { + Result::Ok(QueryResults::Rel(ref v)) => { + assert_eq!(*v, vec![ + vec![TypedValue::Uuid(Uuid::from_str("cf62d552-6569-4d1b-b667-04703041dfc4").expect("Valid UUID")),] + ]); + }, + _ => panic!("Expected query to work."), + } +} + #[test] fn test_fulltext() { let mut c = new_connection("").expect("Couldn't open conn.");