Support variable fulltext searches. (#479) r=nalexander
This commit is contained in:
parent
dd39f6df5b
commit
20aa11dcbd
2 changed files with 96 additions and 8 deletions
|
@ -14,6 +14,8 @@ use mentat_core::{
|
||||||
ValueType,
|
ValueType,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use mentat_core::util::Either;
|
||||||
|
|
||||||
use mentat_query::{
|
use mentat_query::{
|
||||||
Binding,
|
Binding,
|
||||||
FnArg,
|
FnArg,
|
||||||
|
@ -153,29 +155,50 @@ impl ConjoiningClauses {
|
||||||
// - It's already bound, either by input or by a previous pattern like `ground`.
|
// - 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 already bound, but it's a defined input of type Text. Not yet implemented: TODO.
|
||||||
// - It's not bound. The query cannot be algebrized.
|
// - 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)) => {
|
FnArg::Constant(NonIntegerConstant::Text(s)) => {
|
||||||
TypedValue::String(s)
|
Either::Left(TypedValue::String(s))
|
||||||
},
|
},
|
||||||
FnArg::Variable(in_var) => {
|
FnArg::Variable(in_var) => {
|
||||||
match self.bound_value(&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)),
|
Some(_) => bail!(ErrorKind::InvalidArgument(where_fn.operator.clone(), "string".into(), 2)),
|
||||||
None => {
|
None => {
|
||||||
if self.input_variables.contains(&in_var) &&
|
// Regardless of whether we'll be providing a string later, or the value
|
||||||
self.known_type(&in_var) == Some(ValueType::String) {
|
// comes from a column, it must be a string.
|
||||||
// Sorry, we haven't implemented late binding.
|
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)),
|
_ => 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(),
|
let constraint = ColumnConstraint::Matches(QualifiedAlias(fulltext_values_alias.clone(),
|
||||||
Column::Fulltext(FulltextColumn::Text)),
|
Column::Fulltext(FulltextColumn::Text)),
|
||||||
QueryValue::TypedValue(search));
|
qv);
|
||||||
self.wheres.add_intersection(constraint);
|
self.wheres.add_intersection(constraint);
|
||||||
|
|
||||||
if let VariableOrPlaceholder::Variable(ref var) = b_entity {
|
if let VariableOrPlaceholder::Variable(ref var) = b_entity {
|
||||||
|
|
|
@ -14,6 +14,7 @@ extern crate time;
|
||||||
extern crate mentat;
|
extern crate mentat;
|
||||||
extern crate mentat_core;
|
extern crate mentat_core;
|
||||||
extern crate mentat_db;
|
extern crate mentat_db;
|
||||||
|
extern crate mentat_query_algebrizer; // For errors.
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ use mentat_core::{
|
||||||
|
|
||||||
use mentat::{
|
use mentat::{
|
||||||
NamespacedKeyword,
|
NamespacedKeyword,
|
||||||
|
PlainSymbol,
|
||||||
QueryInputs,
|
QueryInputs,
|
||||||
QueryResults,
|
QueryResults,
|
||||||
Variable,
|
Variable,
|
||||||
|
@ -259,12 +261,19 @@ fn test_instants_and_uuids() {
|
||||||
fn test_fulltext() {
|
fn test_fulltext() {
|
||||||
let mut c = new_connection("").expect("Couldn't open conn.");
|
let mut c = new_connection("").expect("Couldn't open conn.");
|
||||||
let mut conn = Conn::connect(&mut c).expect("Couldn't open DB.");
|
let mut conn = Conn::connect(&mut c).expect("Couldn't open DB.");
|
||||||
|
|
||||||
conn.transact(&mut c, r#"[
|
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/ident :foo/fts]
|
||||||
[:db/add "s" :db/valueType :db.type/string]
|
[:db/add "s" :db/valueType :db.type/string]
|
||||||
[:db/add "s" :db/fulltext true]
|
[:db/add "s" :db/fulltext true]
|
||||||
[:db/add "s" :db/cardinality :db.cardinality/many]
|
[:db/add "s" :db/cardinality :db.cardinality/many]
|
||||||
]"#).unwrap();
|
]"#).unwrap();
|
||||||
|
|
||||||
let v = conn.transact(&mut c, r#"[
|
let v = conn.transact(&mut c, r#"[
|
||||||
[:db/add "v" :foo/fts "hello darkness my old friend"]
|
[:db/add "v" :foo/fts "hello darkness my old friend"]
|
||||||
[:db/add "v" :foo/fts "I've come to talk with you again"]
|
[: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."),
|
_ => 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."),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue