Implement MATCHES throughout SQL machinery.
This commit is contained in:
parent
17c59bbff6
commit
565a0e9ff9
7 changed files with 116 additions and 3 deletions
|
@ -54,6 +54,7 @@ use types::{
|
|||
DatomsColumn,
|
||||
DatomsTable,
|
||||
EmptyBecause,
|
||||
FulltextColumn,
|
||||
QualifiedAlias,
|
||||
QueryValue,
|
||||
SourceAlias,
|
||||
|
@ -398,6 +399,13 @@ impl ConjoiningClauses {
|
|||
self.constrain_column_to_constant(table, column, bound_val);
|
||||
},
|
||||
|
||||
Column::Fulltext(FulltextColumn::Rowid) |
|
||||
Column::Fulltext(FulltextColumn::Text) => {
|
||||
// We never expose `rowid` via queries. We do expose `text`, but only
|
||||
// indirectly, by joining against `datoms`. Therefore, these are meaningless.
|
||||
unimplemented!()
|
||||
},
|
||||
|
||||
Column::Fixed(DatomsColumn::ValueTypeTag) => {
|
||||
// I'm pretty sure this is meaningless right now, because we will never bind
|
||||
// a type tag to a variable -- there's no syntax for doing so.
|
||||
|
|
|
@ -22,6 +22,14 @@ use self::mentat_query::{
|
|||
pub enum BindingError {
|
||||
NoBoundVariable,
|
||||
RepeatedBoundVariable, // TODO: include repeated variable(s).
|
||||
|
||||
/// Expected `[[?x ?y]]` but got some other type of binding. Mentat is deliberately more strict
|
||||
/// than Datomic: we won't try to make sense of non-obvious (and potentially erroneous) bindings.
|
||||
ExpectedBindRel,
|
||||
|
||||
/// Expected `[?x1 … ?xN]` or `[[?x1 … ?xN]]` but got some other number of bindings. Mentat is
|
||||
/// deliberately more strict than Datomic: we prefer placeholders to omission.
|
||||
InvalidNumberOfBindings { number: usize, expected: usize },
|
||||
}
|
||||
|
||||
error_chain! {
|
||||
|
|
|
@ -217,6 +217,7 @@ pub use types::{
|
|||
ComputedTable,
|
||||
DatomsColumn,
|
||||
DatomsTable,
|
||||
FulltextColumn,
|
||||
OrderBy,
|
||||
QualifiedAlias,
|
||||
QueryValue,
|
||||
|
|
|
@ -85,6 +85,13 @@ pub enum DatomsColumn {
|
|||
ValueTypeTag,
|
||||
}
|
||||
|
||||
/// One of the named columns of our fulltext values table.
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
pub enum FulltextColumn {
|
||||
Rowid,
|
||||
Text,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
pub enum VariableColumn {
|
||||
Variable(Variable),
|
||||
|
@ -94,6 +101,7 @@ pub enum VariableColumn {
|
|||
#[derive(PartialEq, Eq, Clone)]
|
||||
pub enum Column {
|
||||
Fixed(DatomsColumn),
|
||||
Fulltext(FulltextColumn),
|
||||
Variable(VariableColumn),
|
||||
}
|
||||
|
||||
|
@ -157,11 +165,34 @@ impl Debug for Column {
|
|||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
&Column::Fixed(ref c) => c.fmt(f),
|
||||
&Column::Fulltext(ref c) => c.fmt(f),
|
||||
&Column::Variable(ref v) => v.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FulltextColumn {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
use self::FulltextColumn::*;
|
||||
match *self {
|
||||
Rowid => "rowid",
|
||||
Text => "text",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColumnName for FulltextColumn {
|
||||
fn column_name(&self) -> String {
|
||||
self.as_str().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for FulltextColumn {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// A specific instance of a table within a query. E.g., "datoms123".
|
||||
pub type TableAlias = String;
|
||||
|
||||
|
@ -301,6 +332,9 @@ pub enum ColumnConstraint {
|
|||
},
|
||||
HasType(TableAlias, ValueType),
|
||||
NotExists(ComputedTable),
|
||||
// TODO: Merge this with NumericInequality? I expect the fine-grained information to be
|
||||
// valuable when optimizing.
|
||||
Matches(QualifiedAlias, QueryValue),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
|
@ -411,6 +445,10 @@ impl Debug for ColumnConstraint {
|
|||
write!(f, "{:?} {:?} {:?}", left, operator, right)
|
||||
},
|
||||
|
||||
&Matches(ref qa, ref thing) => {
|
||||
write!(f, "{:?} MATCHES {:?}", qa, thing)
|
||||
},
|
||||
|
||||
&HasType(ref qa, value_type) => {
|
||||
write!(f, "{:?}.value_type_tag = {:?}", qa, value_type)
|
||||
},
|
||||
|
@ -426,6 +464,7 @@ pub enum EmptyBecause {
|
|||
ConflictingBindings { var: Variable, existing: TypedValue, desired: TypedValue },
|
||||
TypeMismatch { var: Variable, existing: ValueTypeSet, desired: ValueTypeSet },
|
||||
NoValidTypes(Variable),
|
||||
NonAttributeArgument,
|
||||
NonNumericArgument,
|
||||
NonStringFulltextValue,
|
||||
UnresolvedIdent(NamespacedKeyword),
|
||||
|
@ -451,6 +490,9 @@ impl Debug for EmptyBecause {
|
|||
&NoValidTypes(ref var) => {
|
||||
write!(f, "Type mismatch: {:?} has no valid types", var)
|
||||
},
|
||||
&NonAttributeArgument => {
|
||||
write!(f, "Non-attribute argument in attribute place")
|
||||
},
|
||||
&NonNumericArgument => {
|
||||
write!(f, "Non-numeric argument in numeric place")
|
||||
},
|
||||
|
|
|
@ -124,6 +124,14 @@ impl Constraint {
|
|||
right: right,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fulltext_match(left: ColumnOrExpression, right: ColumnOrExpression) -> Constraint {
|
||||
Constraint::Infix {
|
||||
op: Op("MATCH"), // SQLite specific!
|
||||
left: left,
|
||||
right: right,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -198,6 +206,10 @@ fn push_column(qb: &mut QueryBuilder, col: &Column) -> BuildQueryResult {
|
|||
qb.push_sql(d.as_str());
|
||||
Ok(())
|
||||
},
|
||||
&Column::Fulltext(ref d) => {
|
||||
qb.push_sql(d.as_str());
|
||||
Ok(())
|
||||
},
|
||||
&Column::Variable(ref vc) => push_variable_column(qb, vc),
|
||||
}
|
||||
}
|
||||
|
@ -555,22 +567,30 @@ impl SelectQuery {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::rc::Rc;
|
||||
|
||||
use mentat_query_algebrizer::{
|
||||
Column,
|
||||
DatomsColumn,
|
||||
DatomsTable,
|
||||
FulltextColumn,
|
||||
};
|
||||
|
||||
fn build(c: &QueryFragment) -> String {
|
||||
fn build_query(c: &QueryFragment) -> SQLQuery {
|
||||
let mut builder = SQLiteQueryBuilder::new();
|
||||
c.push_sql(&mut builder)
|
||||
.map(|_| builder.finish())
|
||||
.unwrap().sql
|
||||
.expect("to produce a query for the given constraint")
|
||||
}
|
||||
|
||||
fn build(c: &QueryFragment) -> String {
|
||||
build_query(c).sql
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_in_constraint() {
|
||||
let none = Constraint::In {
|
||||
left: ColumnOrExpression::Column(QualifiedAlias::new("datoms01".to_string(), DatomsColumn::Value)),
|
||||
left: ColumnOrExpression::Column(QualifiedAlias::new("datoms01".to_string(), Column::Fixed(DatomsColumn::Value))),
|
||||
list: vec![],
|
||||
};
|
||||
|
||||
|
@ -651,6 +671,25 @@ mod tests {
|
|||
"SELECT 0 AS `?a`, 0 AS `?b` WHERE 0 UNION ALL VALUES (0, 1), (1, 2)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matches_constraint() {
|
||||
let c = Constraint::Infix {
|
||||
op: Op("MATCHES"),
|
||||
left: ColumnOrExpression::Column(QualifiedAlias("fulltext01".to_string(), Column::Fulltext(FulltextColumn::Text))),
|
||||
right: ColumnOrExpression::Value(TypedValue::String(Rc::new("needle".to_string()))),
|
||||
};
|
||||
let q = build_query(&c);
|
||||
assert_eq!("`fulltext01`.text MATCHES $v0", q.sql);
|
||||
assert_eq!(vec![("$v0".to_string(), Rc::new(mentat_sql::Value::Text("needle".to_string())))], q.args);
|
||||
|
||||
let c = Constraint::Infix {
|
||||
op: Op("="),
|
||||
left: ColumnOrExpression::Column(QualifiedAlias("fulltext01".to_string(), Column::Fulltext(FulltextColumn::Rowid))),
|
||||
right: ColumnOrExpression::Column(QualifiedAlias("datoms02".to_string(), Column::Fixed(DatomsColumn::Value))),
|
||||
};
|
||||
assert_eq!("`fulltext01`.rowid = `datoms02`.v", build(&c));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_end_to_end() {
|
||||
// [:find ?x :where [?x 65537 ?v] [?x 65536 ?v]]
|
||||
|
|
|
@ -148,6 +148,14 @@ impl ToConstraint for ColumnConstraint {
|
|||
}
|
||||
},
|
||||
|
||||
Matches(left, right) => {
|
||||
Constraint::Infix {
|
||||
op: Op("MATCH"),
|
||||
left: ColumnOrExpression::Column(left),
|
||||
right: right.into(),
|
||||
}
|
||||
},
|
||||
|
||||
HasType(table, value_type) => {
|
||||
let column = QualifiedAlias::new(table, DatomsColumn::ValueTypeTag).to_column();
|
||||
Constraint::equal(column, ColumnOrExpression::Integer(value_type.value_type_tag()))
|
||||
|
|
|
@ -69,6 +69,13 @@ fn prepopulated_typed_schema(foo_type: ValueType) -> Schema {
|
|||
value_type: foo_type,
|
||||
..Default::default()
|
||||
});
|
||||
associate_ident(&mut schema, NamespacedKeyword::new("foo", "fts"), 100);
|
||||
add_attribute(&mut schema, 100, Attribute {
|
||||
value_type: ValueType::String,
|
||||
index: true,
|
||||
fulltext: true,
|
||||
..Default::default()
|
||||
});
|
||||
schema
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue