1) Broaden FullText to include ranking abilities along with matching, 2) Implement CASE WHEN ELSE END, 3) Let the people do 1 + 2 + 3 rathern 1 + (2 + 3), and so forth.

This commit is contained in:
Par Winzell
2009-03-18 19:01:20 +00:00
parent 830e830100
commit fa7d2c0a7f
8 changed files with 309 additions and 94 deletions
@@ -55,8 +55,9 @@ import com.samskivert.depot.expression.LiteralExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.expression.ValueExp;
import com.samskivert.depot.operator.Conditionals.Case;
import com.samskivert.depot.operator.Conditionals.Exists;
import com.samskivert.depot.operator.Conditionals.FullTextMatch;
import com.samskivert.depot.operator.Conditionals.FullText;
import com.samskivert.depot.operator.Conditionals.In;
import com.samskivert.depot.operator.Conditionals.IsNull;
import com.samskivert.depot.operator.Logic.Not;
@@ -141,11 +142,28 @@ public class BindVisitor implements ExpressionVisitor
}
}
public void visit (FullTextMatch match)
public void visit (FullText.Match match)
{
// we never get here
}
public void visit (FullText.Rank rank)
{
// we never get here
}
public void visit (Case caseExp)
{
for (Tuple<SQLExpression, SQLExpression> tuple : caseExp.getWhenExps()) {
tuple.left.accept(this);
tuple.right.accept(this);
}
SQLExpression elseExp = caseExp.getElseExp();
if (elseExp != null) {
elseExp.accept(this);
}
}
public void visit (ColumnExp columnExp)
{
// no arguments
@@ -50,8 +50,9 @@ import com.samskivert.depot.expression.FunctionExp;
import com.samskivert.depot.expression.LiteralExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.expression.ValueExp;
import com.samskivert.depot.operator.Conditionals.Case;
import com.samskivert.depot.operator.Conditionals.Exists;
import com.samskivert.depot.operator.Conditionals.FullTextMatch;
import com.samskivert.depot.operator.Conditionals.FullText;
import com.samskivert.depot.operator.Conditionals.In;
import com.samskivert.depot.operator.Conditionals.IsNull;
import com.samskivert.depot.operator.Logic.Not;
@@ -216,8 +217,25 @@ public abstract class BuildVisitor implements ExpressionVisitor
public abstract void visit (EpochSeconds seconds);
public abstract void visit (FullTextMatch match);
public abstract void visit (FullText.Match match);
public void visit (Case caseExp)
{
_builder.append("(case ");
for (Tuple<SQLExpression, SQLExpression> tuple : caseExp.getWhenExps()) {
_builder.append(" when ");
tuple.left.accept(this);
_builder.append(" then ");
tuple.right.accept(this);
}
SQLExpression elseExp = caseExp.getElseExp();
if (elseExp != null) {
_builder.append(" else ");
elseExp.accept(this);
}
_builder.append(" end)");
}
public void visit (ColumnExp columnExp)
{
appendRhsColumn(columnExp.getPersistentClass(), columnExp.getField());
@@ -41,10 +41,11 @@ import com.samskivert.depot.expression.FunctionExp;
import com.samskivert.depot.expression.LiteralExp;
import com.samskivert.depot.expression.ValueExp;
import com.samskivert.depot.operator.Conditionals.Case;
import com.samskivert.depot.operator.Conditionals.Exists;
import com.samskivert.depot.operator.Conditionals.FullText;
import com.samskivert.depot.operator.Conditionals.In;
import com.samskivert.depot.operator.Conditionals.IsNull;
import com.samskivert.depot.operator.Conditionals.FullTextMatch;
import com.samskivert.depot.operator.Logic.Not;
import com.samskivert.depot.operator.SQLOperator.BinaryOperator;
import com.samskivert.depot.operator.SQLOperator.MultiOperator;
@@ -67,7 +68,8 @@ public interface ExpressionVisitor
public void visit (BinaryOperator binaryOperator);
public void visit (IsNull isNull);
public void visit (In in);
public void visit (FullTextMatch match);
public void visit (FullText.Match match);
public void visit (FullText.Rank rank);
public void visit (ColumnExp columnExp);
public void visit (Not not);
public void visit (GroupBy groupBy);
@@ -87,4 +89,5 @@ public interface ExpressionVisitor
public void visit (InsertClause insertClause);
public void visit (CreateIndexClause createIndexClause);
public void visit (DropIndexClause<? extends PersistentRecord> dropIndexClause);
public void visit (Case caseExp);
}
@@ -50,10 +50,10 @@ import com.samskivert.depot.expression.FunctionExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.operator.Arithmetic.BitAnd;
import com.samskivert.depot.operator.Arithmetic.BitOr;
import com.samskivert.depot.operator.Conditionals.FullTextMatch;
import com.samskivert.depot.operator.Conditionals.FullText;
import com.samskivert.depot.operator.Conditionals.Like;
import com.samskivert.depot.operator.Logic.Or;
import com.samskivert.depot.operator.SQLOperator.BinaryOperator;
import com.samskivert.depot.operator.SQLOperator.MultiOperator;
import com.samskivert.depot.impl.clause.CreateIndexClause;
@@ -62,20 +62,20 @@ public class HSQLBuilder
{
public class HBuildVisitor extends BuildVisitor
{
@Override public void visit (FullTextMatch match)
@Override public void visit (FullText.Match match)
{
// HSQL doesn't have real full text search, so we fake it by creating a condition like
// (lower(COL1) like '%foo%') OR (lower(COL1) like '%bar%') OR ...
// (lower(COL2) like '%foo%') OR (lower(COL2) like '%bar%') OR ...
// ... and so on. Not efficient, but basically functional.
Class<? extends PersistentRecord> pClass = match.getPersistentClass();
Class<? extends PersistentRecord> pClass = match.getDefinition().getPersistentClass();
// find the fields involved
String[] fields = _types.getMarshaller(pClass).
getFullTextIndex(match.getName()).fields();
getFullTextIndex(match.getDefinition().getName()).fields();
// explode the query into words
String[] ftsWords = match.getQuery().toLowerCase().split("\\W+");
String[] ftsWords = match.getDefinition().getQuery().toLowerCase().split("\\W+");
if (ftsWords.length > 0 && ftsWords[0].length() == 0) {
// if the query led with whitespace, the first 'word' will be empty; strip it
ftsWords = ArrayUtil.splice(ftsWords, 0, 1);
@@ -95,19 +95,38 @@ public class HSQLBuilder
_ftsCondition.accept(this);
}
@Override
public void visit (BinaryOperator binaryOperator)
@Override public void visit (FullText.Rank rank)
{
// HSQL doesn't handle & and | operators
if (binaryOperator instanceof BitAnd) {
_builder.append("BITAND(?, ?)");
// not implemented for HSQL
_builder.append("0");
}
} else if (binaryOperator instanceof BitOr) {
_builder.append("BITOR(?, ?)");
@Override
public void visit (MultiOperator operator)
{
String op;
// HSQL doesn't handle & and | operators
if (operator instanceof BitAnd) {
op = "bitand";
} else if (operator instanceof BitOr) {
op = "bitor";
} else {
super.visit(binaryOperator);
super.visit(operator);
return;
}
_builder.append(op).append("(");
boolean virgin = true;
for (SQLExpression bit: operator.getConditions()) {
if (!virgin) {
_builder.append(", ");
}
_builder.append("?");
virgin = false;
}
_builder.append(")");
}
public void visit (EpochSeconds epochSeconds)
@@ -146,7 +165,7 @@ public class HSQLBuilder
super(types, conn, stmt);
}
@Override public void visit (FullTextMatch match) {
@Override public void visit (FullText.Match match) {
_ftsCondition.accept(this);
_ftsCondition = null;
}
@@ -41,7 +41,7 @@ import com.samskivert.depot.clause.OrderBy.Order;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.EpochSeconds;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.operator.Conditionals.FullTextMatch;
import com.samskivert.depot.operator.Conditionals.FullText;
import com.samskivert.depot.impl.FieldMarshaller.BooleanMarshaller;
import com.samskivert.depot.impl.FieldMarshaller.ByteArrayMarshaller;
@@ -65,19 +65,14 @@ public class MySQLBuilder
{
public class MSBuildVisitor extends BuildVisitor
{
@Override public void visit (FullTextMatch match)
@Override public void visit (FullText.Match match)
{
_builder.append("match(");
Class<? extends PersistentRecord> pClass = match.getPersistentClass();
String[] fields =
_types.getMarshaller(pClass).getFullTextIndex(match.getName()).fields();
for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
new ColumnExp(pClass, fields[ii]).accept(this);
}
_builder.append(") against (? in boolean mode)");
renderMatch(match.getDefinition());
}
@Override public void visit (FullText.Rank rank)
{
renderMatch(rank.getDefinition());
}
@Override public void visit (DeleteClause deleteClause)
@@ -146,6 +141,21 @@ public class MySQLBuilder
{
_builder.append(field);
}
protected void renderMatch (FullText fullText)
{
_builder.append("match(");
Class<? extends PersistentRecord> pClass = fullText.getPersistentClass();
String[] fields = _types.getMarshaller(pClass).getFullTextIndex(
fullText.getName()).fields();
for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
new ColumnExp(pClass, fields[ii]).accept(this);
}
_builder.append(") against (? in boolean mode)");
}
}
public class MSBindVisitor extends BindVisitor
@@ -154,12 +164,22 @@ public class MySQLBuilder
super(types, conn, stmt);
}
@Override public void visit (FullTextMatch match) {
@Override public void visit (FullText.Match match) {
bindMatch(match.getDefinition());
}
@Override public void visit (FullText.Rank rank) {
bindMatch(rank.getDefinition());
}
protected void bindMatch (FullText fullText)
{
try {
_stmt.setString(_argIdx++, match.getQuery());
_stmt.setString(_argIdx++, fullText.getQuery());
} catch (SQLException sqe) {
throw new DatabaseException("Failed to configure full-text match column " +
"[idx=" + (_argIdx-1) + ", match=" + match + "]", sqe);
throw new DatabaseException(
"Failed to configure full-text match column [idx=" + (_argIdx-1) +
", match=" + fullText + "]", sqe);
}
}
}
@@ -41,7 +41,7 @@ import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.FullTextIndex;
import com.samskivert.depot.expression.EpochSeconds;
import com.samskivert.depot.operator.Conditionals.FullTextMatch;
import com.samskivert.depot.operator.Conditionals.FullText;
import static com.samskivert.Log.log;
@@ -50,9 +50,15 @@ public class PostgreSQLBuilder
{
public class PGBuildVisitor extends BuildVisitor
{
@Override public void visit (FullTextMatch match) {
appendIdentifier("ftsCol_" + match.getName());
_builder.append(" @@ TO_TSQUERY('default', ?)");
@Override public void visit (FullText.Match match) {
appendIdentifier("ftsCol_" + match.getDefinition().getName());
_builder.append(" @@ to_tsquery('default', ?)");
}
@Override public void visit (FullText.Rank rank) {
_builder.append("rank(");
appendIdentifier("ftsCol_" + rank.getDefinition().getName());
_builder.append(", to_tsquery('default', ?), 32)");
}
@Override public void visit (EpochSeconds epochSeconds) {
@@ -78,7 +84,28 @@ public class PostgreSQLBuilder
public class PGBindVisitor extends BindVisitor
{
@Override public void visit (FullTextMatch match) {
@Override public void visit (FullText.Rank rank) {
String query = massageQuery(rank.getDefinition());
try {
_stmt.setString(_argIdx++, query);
} catch (SQLException sqe) {
throw new DatabaseException("Failed to configure full-text match column " +
"[idx=" + (_argIdx-1) + ", query=" + query + "]", sqe);
}
}
@Override public void visit (FullText.Match match) {
String query = massageQuery(match.getDefinition());
try {
_stmt.setString(_argIdx++, query);
} catch (SQLException sqe) {
throw new DatabaseException("Failed to configure full-text match column " +
"[idx=" + (_argIdx-1) + ", query=" + query + "]", sqe);
}
}
protected String massageQuery (FullText match)
{
// The tsearch2 engine takes queries on the form
// (foo&bar)|goop
// so in this first simple implementation, we just take the user query, chop it into
@@ -88,13 +115,7 @@ public class PostgreSQLBuilder
if (searchTerms.length > 0 && searchTerms[0].length() == 0) {
searchTerms = ArrayUtil.splice(searchTerms, 0, 1);
}
String query = StringUtil.join(searchTerms, "|");
try {
_stmt.setString(_argIdx++, query);
} catch (SQLException sqe) {
throw new DatabaseException("Failed to configure full-text match column " +
"[idx=" + (_argIdx-1) + ", query=" + query + "]", sqe);
}
return StringUtil.join(searchTerms, "|");
}
// TODO: enable when we can require 1.6 support
@@ -21,7 +21,8 @@
package com.samskivert.depot.operator;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.operator.SQLOperator.BinaryOperator;
import com.samskivert.depot.expression.ValueExp;
import com.samskivert.depot.operator.SQLOperator.MultiOperator;
/**
* A convenient container for implementations of arithmetic operators. Classes that value brevity
@@ -30,16 +31,16 @@ import com.samskivert.depot.operator.SQLOperator.BinaryOperator;
public abstract class Arithmetic
{
/** The SQL '+' operator. */
public static class Add extends BinaryOperator
public static class Add extends MultiOperator
{
public Add (SQLExpression column, Comparable<?> value)
{
super(column, value);
super(column, new ValueExp(value));
}
public Add (SQLExpression column, SQLExpression value)
public Add (SQLExpression... values)
{
super(column, value);
super(values);
}
@Override public String operator()
@@ -49,16 +50,16 @@ public abstract class Arithmetic
}
/** The SQL '-' operator. */
public static class Sub extends BinaryOperator
public static class Sub extends MultiOperator
{
public Sub (SQLExpression column, Comparable<?> value)
{
super(column, value);
super(column, new ValueExp(value));
}
public Sub (SQLExpression column, SQLExpression value)
public Sub (SQLExpression... values)
{
super(column, value);
super(values);
}
@Override public String operator()
@@ -68,16 +69,16 @@ public abstract class Arithmetic
}
/** The SQL '*' operator. */
public static class Mul extends BinaryOperator
public static class Mul extends MultiOperator
{
public Mul (SQLExpression column, Comparable<?> value)
{
super(column, value);
super(column, new ValueExp(value));
}
public Mul (SQLExpression column, SQLExpression value)
public Mul (SQLExpression... values)
{
super(column, value);
super(values);
}
@Override public String operator()
@@ -87,16 +88,16 @@ public abstract class Arithmetic
}
/** The SQL '/' operator. */
public static class Div extends BinaryOperator
public static class Div extends MultiOperator
{
public Div (SQLExpression column, Comparable<?> value)
{
super(column, value);
super(column, new ValueExp(value));
}
public Div (SQLExpression column, SQLExpression value)
public Div (SQLExpression... values)
{
super(column, value);
super(values);
}
@Override public String operator()
@@ -106,16 +107,16 @@ public abstract class Arithmetic
}
/** The SQL '&' operator. */
public static class BitAnd extends BinaryOperator
public static class BitAnd extends MultiOperator
{
public BitAnd (SQLExpression column, Comparable<?> value)
{
super(column, value);
super(column, new ValueExp(value));
}
public BitAnd (SQLExpression column, SQLExpression value)
public BitAnd (SQLExpression... values)
{
super(column, value);
super(values);
}
@Override public String operator()
@@ -125,16 +126,16 @@ public abstract class Arithmetic
}
/** The SQL '|' operator. */
public static class BitOr extends BinaryOperator
public static class BitOr extends MultiOperator
{
public BitOr (SQLExpression column, Comparable<?> value)
{
super(column, value);
super(column, new ValueExp(value));
}
public BitOr (SQLExpression column, SQLExpression value)
public BitOr (SQLExpression... values)
{
super(column, value);
super(values);
}
@Override public String operator()
@@ -21,12 +21,15 @@
package com.samskivert.depot.operator;
import java.util.Collection;
import java.util.List;
import com.google.common.collect.Lists;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.clause.SelectClause;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.ExpressionVisitor;
import com.samskivert.util.Tuple;
/**
* A convenient container for implementations of conditional operators. Classes that value brevity
@@ -67,7 +70,7 @@ public abstract class Conditionals
protected ColumnExp _column;
}
/** The SQL '=' operator. */
public static class Equals extends SQLOperator.BinaryOperator
{
@@ -270,11 +273,13 @@ public abstract class Conditionals
_clause = clause;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_clause.addClasses(classSet);
@@ -293,21 +298,143 @@ public abstract class Conditionals
protected SelectClause<T> _clause;
}
public static class Case implements SQLOperator
{
public Case (SQLExpression... exps)
{
int i = 0;
while (i+1 < exps.length) {
_whenExps.add(Tuple.newTuple(exps[i], exps[i+1]));
i += 2;
}
_elseExp = (i < exps.length) ? exps[i] : null;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
for (Tuple<SQLExpression, SQLExpression> tuple : _whenExps) {
tuple.left.addClasses(classSet);
tuple.right.addClasses(classSet);
}
if (_elseExp != null) {
_elseExp.addClasses(classSet);
}
}
public List<Tuple<SQLExpression, SQLExpression>> getWhenExps ()
{
return _whenExps;
}
public SQLExpression getElseExp ()
{
return _elseExp;
}
@Override // from Object
public String toString ()
{
StringBuilder builder = new StringBuilder();
builder.append("Case(");
for (Tuple<SQLExpression, SQLExpression> tuple : _whenExps) {
builder.append(tuple.left.toString()).append("->");
builder.append(tuple.right.toString()).append(",");
}
if (_elseExp != null) {
builder.append(_elseExp.toString()).append(")");
}
return builder.toString();
}
protected List<Tuple<SQLExpression, SQLExpression>> _whenExps = Lists.newArrayList();
protected SQLExpression _elseExp;
}
/**
* An attempt at a dialect-agnostic full-text search condition, such as MySQL's MATCH() and
* PostgreSQL's @@ TO_TSQUERY(...) abilities.
*/
public static class FullTextMatch
implements SQLOperator
public static class FullText
{
public FullTextMatch (Class<? extends PersistentRecord> pClass, String name, String query)
public class Rank
implements SQLOperator
{
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
@Override // from Object
public String toString ()
{
return FullText.this.toString("Rank");
}
public FullText getDefinition ()
{
return FullText.this;
}
}
public class Match
implements SQLOperator
{
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
public FullText getDefinition ()
{
return FullText.this;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
@Override // from Object
public String toString ()
{
return FullText.this.toString("Match");
}
}
public FullText (Class<? extends PersistentRecord> pClass, String name, String query)
{
_pClass = pClass;
_name = name;
_query = query;
}
public SQLOperator match ()
{
return new Match();
}
public SQLOperator rank ()
{
return new Rank();
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
@@ -323,23 +450,11 @@ public abstract class Conditionals
return _name;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
protected String toString (String subType)
{
builder.visit(this);
return "FullText." + subType + "(" + _name + "=" + _query + ")";
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
@Override // from Object
public String toString ()
{
return "FullText(" + _name + "=" + _query + ")";
}
protected Class<? extends PersistentRecord> _pClass;
protected String _name;
protected String _query;