Skip to content
This repository has been archived by the owner on Sep 12, 2018. It is now read-only.

Commit

Permalink
Implement MATCHES throughout SQL machinery.
Browse files Browse the repository at this point in the history
  • Loading branch information
rnewman committed Jun 15, 2017
1 parent 17c59bb commit 565a0e9
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 3 deletions.
8 changes: 8 additions & 0 deletions query-algebrizer/src/clauses/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ use types::{
DatomsColumn,
DatomsTable,
EmptyBecause,
FulltextColumn,
QualifiedAlias,
QueryValue,
SourceAlias,
Expand Down Expand Up @@ -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.
Expand Down
8 changes: 8 additions & 0 deletions query-algebrizer/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand Down
1 change: 1 addition & 0 deletions query-algebrizer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ pub use types::{
ComputedTable,
DatomsColumn,
DatomsTable,
FulltextColumn,
OrderBy,
QualifiedAlias,
QueryValue,
Expand Down
42 changes: 42 additions & 0 deletions query-algebrizer/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -94,6 +101,7 @@ pub enum VariableColumn {
#[derive(PartialEq, Eq, Clone)]
pub enum Column {
Fixed(DatomsColumn),
Fulltext(FulltextColumn),
Variable(VariableColumn),
}

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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)
},
Expand All @@ -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),
Expand All @@ -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")
},
Expand Down
45 changes: 42 additions & 3 deletions query-sql/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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),
}
}
Expand Down Expand Up @@ -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![],
};

Expand Down Expand Up @@ -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]]
Expand Down
8 changes: 8 additions & 0 deletions query-translator/src/translate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand Down
7 changes: 7 additions & 0 deletions query-translator/tests/translate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down

0 comments on commit 565a0e9

Please sign in to comment.