Support variable fulltext searches. (#479) r=nalexander

This commit is contained in:
Richard Newman 2017-06-14 14:44:16 -07:00
parent dd39f6df5b
commit 20aa11dcbd
2 changed files with 96 additions and 8 deletions

View file

@ -14,6 +14,8 @@ use mentat_core::{
ValueType,
};
use mentat_core::util::Either;
use mentat_query::{
Binding,
FnArg,
@ -153,29 +155,50 @@ impl ConjoiningClauses {
// - It's already bound, either by input or by a previous pattern like `ground`.
// - It's not already bound, but it's a defined input of type Text. Not yet implemented: TODO.
// - It's not bound. The query cannot be algebrized.
let search: TypedValue = match args.next().unwrap() {
let search: Either<TypedValue, QualifiedAlias> = match args.next().unwrap() {
FnArg::Constant(NonIntegerConstant::Text(s)) => {
TypedValue::String(s)
Either::Left(TypedValue::String(s))
},
FnArg::Variable(in_var) => {
match self.bound_value(&in_var) {
Some(t @ TypedValue::String(_)) => t,
Some(t @ TypedValue::String(_)) => Either::Left(t),
Some(_) => bail!(ErrorKind::InvalidArgument(where_fn.operator.clone(), "string".into(), 2)),
None => {
if self.input_variables.contains(&in_var) &&
self.known_type(&in_var) == Some(ValueType::String) {
// Sorry, we haven't implemented late binding.
// Regardless of whether we'll be providing a string later, or the value
// comes from a column, it must be a string.
if self.known_type(&in_var) != Some(ValueType::String) {
bail!(ErrorKind::InvalidArgument(where_fn.operator.clone(), "string".into(), 2));
}
if self.input_variables.contains(&in_var) {
// Sorry, we haven't implemented late binding.
// TODO: implement this.
bail!(ErrorKind::UnboundVariable((*in_var.0).clone()));
} else {
// It must be bound earlier in the query. We already established that
// it must be a string column.
if let Some(binding) = self.column_bindings
.get(&in_var)
.and_then(|bindings| bindings.get(0).cloned()) {
Either::Right(binding)
} else {
bail!(ErrorKind::UnboundVariable((*in_var.0).clone()));
}
}
bail!(ErrorKind::UnboundVariable((*in_var.0).clone()));
},
}
},
_ => bail!(ErrorKind::InvalidArgument(where_fn.operator.clone(), "string".into(), 2)),
};
let qv = match search {
Either::Left(tv) => QueryValue::TypedValue(tv),
Either::Right(qa) => QueryValue::Column(qa),
};
let constraint = ColumnConstraint::Matches(QualifiedAlias(fulltext_values_alias.clone(),
Column::Fulltext(FulltextColumn::Text)),
QueryValue::TypedValue(search));
qv);
self.wheres.add_intersection(constraint);
if let VariableOrPlaceholder::Variable(ref var) = b_entity {

View file

@ -14,6 +14,7 @@ extern crate time;
extern crate mentat;
extern crate mentat_core;
extern crate mentat_db;
extern crate mentat_query_algebrizer; // For errors.
use std::str::FromStr;
@ -28,6 +29,7 @@ use mentat_core::{
use mentat::{
NamespacedKeyword,
PlainSymbol,
QueryInputs,
QueryResults,
Variable,
@ -259,12 +261,19 @@ fn test_instants_and_uuids() {
fn test_fulltext() {
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 "a" :db/ident :foo/term]
[:db/add "a" :db/valueType :db.type/string]
[:db/add "a" :db/fulltext false]
[:db/add "a" :db/cardinality :db.cardinality/many]
[:db/add "s" :db/ident :foo/fts]
[:db/add "s" :db/valueType :db.type/string]
[:db/add "s" :db/fulltext true]
[:db/add "s" :db/cardinality :db.cardinality/many]
]"#).unwrap();
let v = conn.transact(&mut c, r#"[
[:db/add "v" :foo/fts "hello darkness my old friend"]
[:db/add "v" :foo/fts "I've come to talk with you again"]
@ -290,4 +299,60 @@ fn test_fulltext() {
},
_ => panic!("Expected query to work."),
}
let a = conn.transact(&mut c, r#"[[:db/add "a" :foo/term "talk"]]"#)
.unwrap()
.tempids
.get("a").cloned()
.expect("a was mapped");
// If you use a non-constant search term, it must be bound earlier in the query.
let query = r#"[:find ?x ?val
:where
[(fulltext $ :foo/fts ?term) [[?x ?val]]]
[?a :foo/term ?term]
]"#;
let r = conn.q_once(&mut c, query, None);
match r {
Err(Error(ErrorKind::QueryError(mentat_query_algebrizer::ErrorKind::InvalidArgument(PlainSymbol(s), ty, i)), _)) => {
assert_eq!(s, "fulltext");
assert_eq!(ty, "string");
assert_eq!(i, 2);
},
_ => panic!("Expected query to fail."),
}
// Bound to the wrong type? Error.
let query = r#"[:find ?x ?val
:where
[?a :foo/term ?term]
[(fulltext $ :foo/fts ?a) [[?x ?val]]]]"#;
let r = conn.q_once(&mut c, query, None);
match r {
Err(Error(ErrorKind::QueryError(mentat_query_algebrizer::ErrorKind::InvalidArgument(PlainSymbol(s), ty, i)), _)) => {
assert_eq!(s, "fulltext");
assert_eq!(ty, "string");
assert_eq!(i, 2);
},
_ => panic!("Expected query to fail."),
}
// If it's bound, and the right type, it'll work!
let query = r#"[:find ?x ?val
:in ?a
:where
[?a :foo/term ?term]
[(fulltext $ :foo/fts ?term) [[?x ?val]]]]"#;
let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?a"), TypedValue::Ref(a))]);
let r = conn.q_once(&mut c, query, inputs);
match r {
Result::Ok(QueryResults::Rel(rels)) => {
assert_eq!(rels, vec![
vec![TypedValue::Ref(v),
TypedValue::String("I've come to talk with you again".to_string().into()),
]
]);
},
_ => panic!("Expected query to work."),
}
}