diff --git a/src/java/com/samskivert/depot/impl/BindVisitor.java b/src/java/com/samskivert/depot/impl/BindVisitor.java index decbd65..7b0460e 100644 --- a/src/java/com/samskivert/depot/impl/BindVisitor.java +++ b/src/java/com/samskivert/depot/impl/BindVisitor.java @@ -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 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 diff --git a/src/java/com/samskivert/depot/impl/BuildVisitor.java b/src/java/com/samskivert/depot/impl/BuildVisitor.java index 0a029ae..1c80625 100644 --- a/src/java/com/samskivert/depot/impl/BuildVisitor.java +++ b/src/java/com/samskivert/depot/impl/BuildVisitor.java @@ -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 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()); diff --git a/src/java/com/samskivert/depot/impl/ExpressionVisitor.java b/src/java/com/samskivert/depot/impl/ExpressionVisitor.java index a8f0547..791b061 100644 --- a/src/java/com/samskivert/depot/impl/ExpressionVisitor.java +++ b/src/java/com/samskivert/depot/impl/ExpressionVisitor.java @@ -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 dropIndexClause); + public void visit (Case caseExp); } diff --git a/src/java/com/samskivert/depot/impl/HSQLBuilder.java b/src/java/com/samskivert/depot/impl/HSQLBuilder.java index e004b20..9e2cefb 100644 --- a/src/java/com/samskivert/depot/impl/HSQLBuilder.java +++ b/src/java/com/samskivert/depot/impl/HSQLBuilder.java @@ -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 pClass = match.getPersistentClass(); + Class 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; } diff --git a/src/java/com/samskivert/depot/impl/MySQLBuilder.java b/src/java/com/samskivert/depot/impl/MySQLBuilder.java index c6a3730..a587078 100644 --- a/src/java/com/samskivert/depot/impl/MySQLBuilder.java +++ b/src/java/com/samskivert/depot/impl/MySQLBuilder.java @@ -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 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 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); } } } diff --git a/src/java/com/samskivert/depot/impl/PostgreSQLBuilder.java b/src/java/com/samskivert/depot/impl/PostgreSQLBuilder.java index 39cd5e3..32a13d2 100644 --- a/src/java/com/samskivert/depot/impl/PostgreSQLBuilder.java +++ b/src/java/com/samskivert/depot/impl/PostgreSQLBuilder.java @@ -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 diff --git a/src/java/com/samskivert/depot/operator/Arithmetic.java b/src/java/com/samskivert/depot/operator/Arithmetic.java index 5011f07..04f70c6 100644 --- a/src/java/com/samskivert/depot/operator/Arithmetic.java +++ b/src/java/com/samskivert/depot/operator/Arithmetic.java @@ -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() diff --git a/src/java/com/samskivert/depot/operator/Conditionals.java b/src/java/com/samskivert/depot/operator/Conditionals.java index 5bab3e9..aec40de 100644 --- a/src/java/com/samskivert/depot/operator/Conditionals.java +++ b/src/java/com/samskivert/depot/operator/Conditionals.java @@ -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> classSet) { _clause.addClasses(classSet); @@ -293,21 +298,143 @@ public abstract class Conditionals protected SelectClause _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> classSet) + { + for (Tuple tuple : _whenExps) { + tuple.left.addClasses(classSet); + tuple.right.addClasses(classSet); + } + if (_elseExp != null) { + _elseExp.addClasses(classSet); + } + } + + public List> getWhenExps () + { + return _whenExps; + } + + public SQLExpression getElseExp () + { + return _elseExp; + } + + @Override // from Object + public String toString () + { + StringBuilder builder = new StringBuilder(); + builder.append("Case("); + for (Tuple 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> _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 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> 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> classSet) + { + } + + @Override // from Object + public String toString () + { + return FullText.this.toString("Match"); + } + } + + public FullText (Class 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 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> classSet) - { - } - - @Override // from Object - public String toString () - { - return "FullText(" + _name + "=" + _query + ")"; - } - + protected Class _pClass; protected String _name; protected String _query;