Implement MATCHES throughout SQL machinery.

This commit is contained in:
Richard Newman 2017-06-12 14:24:56 -07:00
parent 17c59bbff6
commit 565a0e9ff9
7 changed files with 116 additions and 3 deletions

View file

@ -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.

View file

@ -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! {

View file

@ -217,6 +217,7 @@ pub use types::{
ComputedTable,
DatomsColumn,
DatomsTable,
FulltextColumn,
OrderBy,
QualifiedAlias,
QueryValue,

View file

@ -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")
},

View file

@ -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]]

View file

@ -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()))

View file

@ -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
}