diff --git a/src/main/java/com/samskivert/depot/Query.java b/src/main/java/com/samskivert/depot/Query.java index 5970f33..1e6fc63 100644 --- a/src/main/java/com/samskivert/depot/Query.java +++ b/src/main/java/com/samskivert/depot/Query.java @@ -178,6 +178,25 @@ public class Query return query; } + /** + * Configures the query to return only distinct rows. + */ + public Query distinct () + { + return distinct(null); + } + + /** + * Configures the query to return rows for which the given expression evaluates distinctly. + */ + public Query distinct (SQLExpression distinctOn) + { + checkState(_distinct == null, "Distinct clause is already configured."); + Query query = clone(); + query._distinct = new Distinct(distinctOn); + return query; + } + /** * Configures a {@link GroupBy} clause on the supplied group expressions. */ @@ -610,6 +629,7 @@ public class Query addIfNotNull(clauses, _where); addAll(clauses, _joins); addIfNotNull(clauses, _orderBy); + addIfNotNull(clauses, _distinct); addIfNotNull(clauses, _groupBy); addIfNotNull(clauses, _limit); addIfNotNull(clauses, _fromOverride); @@ -636,6 +656,7 @@ public class Query checkState(_where != null, "Where clause must be specified for delete."); checkState(_joins == null, "Join clauses not supported by delete."); checkState(_orderBy == null, "OrderBy clause not applicable for delete."); + checkState(_distinct == null, "Distinct clause not applicable for delete."); checkState(_groupBy == null, "GroupBy clause not applicable for delete."); checkState(_fromOverride == null, "FromOverride clause not applicable for delete."); checkState(_fieldDefs == null, "FieldDefinition clauses not applicable for delete."); @@ -678,6 +699,7 @@ public class Query protected WhereClause _where; protected OrderBy _orderBy; + protected Distinct _distinct; protected GroupBy _groupBy; protected Limit _limit; protected FromOverride _fromOverride; diff --git a/src/main/java/com/samskivert/depot/clause/SelectClause.java b/src/main/java/com/samskivert/depot/clause/SelectClause.java index 0056c54..7ecabcd 100644 --- a/src/main/java/com/samskivert/depot/clause/SelectClause.java +++ b/src/main/java/com/samskivert/depot/clause/SelectClause.java @@ -64,6 +64,10 @@ public class SelectClause } else if (clause instanceof FieldDefinition) { _disMap.put(((FieldDefinition) clause).getField(), ((FieldDefinition) clause)); + } else if (clause instanceof Distinct) { + checkArgument(_orderBy == null, "Query can't contain multiple Distinct clauses."); + _distinct = (Distinct) clause; + } else if (clause instanceof OrderBy) { checkArgument(_orderBy == null, "Query can't contain multiple OrderBy clauses."); _orderBy = (OrderBy) clause; @@ -128,6 +132,11 @@ public class SelectClause return _orderBy; } + public Distinct getDistinct () + { + return _distinct; + } + public GroupBy getGroupBy () { return _groupBy; @@ -182,6 +191,9 @@ public class SelectClause if (_orderBy != null) { builder.append(", orderBy=").append(_orderBy); } + if (_distinct != null) { + builder.append(", distinct=").append(_distinct); + } if (_groupBy != null) { builder.append(", groupBy=").append(_groupBy); } @@ -215,6 +227,9 @@ public class SelectClause /** The order by clause, if any. */ protected OrderBy _orderBy; + /** The distinct clause, if any. */ + protected Distinct _distinct; + /** The group by clause, if any. */ protected GroupBy _groupBy; diff --git a/src/main/java/com/samskivert/depot/impl/BuildVisitor.java b/src/main/java/com/samskivert/depot/impl/BuildVisitor.java index 1923d8f..4f1dcd8 100644 --- a/src/main/java/com/samskivert/depot/impl/BuildVisitor.java +++ b/src/main/java/com/samskivert/depot/impl/BuildVisitor.java @@ -14,6 +14,7 @@ import java.util.Set; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.samskivert.depot.clause.Distinct; import com.samskivert.util.ByteEnum; import com.samskivert.util.Tuple; @@ -278,6 +279,17 @@ public abstract class BuildVisitor implements FragmentVisitor return null; } + public Void visit (Distinct distinct) + { + _builder.append("distinct "); + if (distinct.getDistinctOn() != null) { + _builder.append("on "); + distinct.getDistinctOn().accept(this); + _builder.append(" "); + } + return null; + } + public Void visit (Join join) { switch (join.getType()) { @@ -345,6 +357,10 @@ public abstract class BuildVisitor implements FragmentVisitor checkArgument(!_definitions.containsKey(pClass), "Can not yet nest SELECTs on the same persistent record."); + if (selectClause.getDistinct() != null) { + selectClause.getDistinct().accept(this); + } + Map definitionMap = Maps.newHashMap(); for (FieldDefinition definition : selectClause.getFieldDefinitions()) { definitionMap.put(definition.getField(), definition); diff --git a/src/main/java/com/samskivert/depot/impl/ExpressionEvaluator.java b/src/main/java/com/samskivert/depot/impl/ExpressionEvaluator.java index 1d1333c..d6f174a 100644 --- a/src/main/java/com/samskivert/depot/impl/ExpressionEvaluator.java +++ b/src/main/java/com/samskivert/depot/impl/ExpressionEvaluator.java @@ -9,6 +9,7 @@ import java.lang.reflect.Field; import com.samskivert.depot.Key; import com.samskivert.depot.PersistentRecord; +import com.samskivert.depot.clause.Distinct; import com.samskivert.depot.clause.FieldDefinition; import com.samskivert.depot.clause.ForUpdate; import com.samskivert.depot.clause.FromOverride; @@ -252,6 +253,11 @@ public class ExpressionEvaluator throw new IllegalArgumentException("Can't evaluate expression: " + exists); } + public Object visit (Distinct distinct) + { + throw new IllegalArgumentException("Can't evaluate expression: " + distinct); + } + public Object visit (GroupBy groupBy) { throw new IllegalArgumentException("Can't evaluate expression: " + groupBy); diff --git a/src/main/java/com/samskivert/depot/impl/FragmentVisitor.java b/src/main/java/com/samskivert/depot/impl/FragmentVisitor.java index a137bc0..5d38de7 100644 --- a/src/main/java/com/samskivert/depot/impl/FragmentVisitor.java +++ b/src/main/java/com/samskivert/depot/impl/FragmentVisitor.java @@ -6,6 +6,7 @@ package com.samskivert.depot.impl; import com.samskivert.depot.Key; +import com.samskivert.depot.clause.Distinct; import com.samskivert.depot.clause.FieldDefinition; import com.samskivert.depot.clause.ForUpdate; import com.samskivert.depot.clause.FromOverride; @@ -81,6 +82,7 @@ public interface FragmentVisitor public T visit (FullText.Rank match); public T visit (ColumnExp columnExp); public T visit (Not not); + public T visit (Distinct distinct); public T visit (GroupBy groupBy); public T visit (ForUpdate forUpdate); public T visit (OrderBy orderBy); diff --git a/src/main/java/com/samskivert/depot/impl/HSQLBuilder.java b/src/main/java/com/samskivert/depot/impl/HSQLBuilder.java index 05fba87..4ed2d1c 100644 --- a/src/main/java/com/samskivert/depot/impl/HSQLBuilder.java +++ b/src/main/java/com/samskivert/depot/impl/HSQLBuilder.java @@ -18,6 +18,7 @@ import com.samskivert.depot.Ops; import com.samskivert.depot.PersistentRecord; import com.samskivert.depot.annotation.FullTextIndex; import com.samskivert.depot.annotation.GeneratedValue; +import com.samskivert.depot.clause.Distinct; import com.samskivert.depot.clause.OrderBy; import com.samskivert.depot.expression.ColumnExp; import com.samskivert.depot.expression.SQLExpression; @@ -132,6 +133,14 @@ public class HSQLBuilder return super.visit(operator); } + public Void visit (Distinct distinct) + { + if (distinct.getDistinctOn() != null) { + throw new IllegalArgumentException("MySQL does not support DISTINCT ON"); + } + return super.visit(distinct); + } + @Override public Void visit (DatePart exp) { if (exp.getPart() == Part.EPOCH) { diff --git a/src/main/java/com/samskivert/depot/impl/MySQLBuilder.java b/src/main/java/com/samskivert/depot/impl/MySQLBuilder.java index 707a7f5..19079ef 100644 --- a/src/main/java/com/samskivert/depot/impl/MySQLBuilder.java +++ b/src/main/java/com/samskivert/depot/impl/MySQLBuilder.java @@ -10,6 +10,7 @@ import java.sql.SQLException; import java.util.Map; import java.util.Set; +import com.samskivert.depot.clause.Distinct; import com.samskivert.util.StringUtil; import com.samskivert.depot.PersistentRecord; @@ -69,6 +70,14 @@ public class MySQLBuilder return appendFunctionCall("truncate", exp.getArg()); } + public Void visit (Distinct distinct) + { + if (distinct.getDistinctOn() != null) { + throw new IllegalArgumentException("MySQL does not support DISTINCT ON"); + } + return super.visit(distinct); + } + @Override public Void visit (DatePart exp) { return appendFunctionCall(getDateFunction(exp.getPart()), exp.getArg()); }