Fix and test GroupBy SQL query creation

Signed-off-by: Ian Southam <isoutham@schubergphilis.com>
Signed-off-by: Hugo Trippaers <htrippaers@schubergphilis.com>
This commit is contained in:
Antonio Fornie 2014-02-11 15:10:23 +01:00 committed by Hugo Trippaers
parent 8649fa005a
commit cbea6d26e8
3 changed files with 125 additions and 55 deletions

View File

@ -28,31 +28,35 @@ public class GroupBy<J extends SearchBase<?, T, R>, T, R> {
List<Pair<Func, Attribute>> _groupBys;
Having _having;
public GroupBy(J builder) {
public GroupBy(final J builder) {
init(builder);
}
protected void init(final J builder) {
_builder = builder;
_groupBys = new ArrayList<Pair<Func, Attribute>>();
_having = null;
for (Attribute attr : _builder.getSpecifiedAttributes()) {
for (final Attribute attr : _builder.getSpecifiedAttributes()) {
_groupBys.add(new Pair<Func, Attribute>(null, attr));
}
_builder.getSpecifiedAttributes().clear();
}
public GroupBy<J, T, R> group(Object useless) {
public GroupBy<J, T, R> group(final Object useless) {
_groupBys.add(new Pair<Func, Attribute>(null, _builder.getSpecifiedAttributes().get(0)));
_builder.getSpecifiedAttributes().clear();
return this;
}
public GroupBy<J, T, R> group(Func func, Object useless) {
public GroupBy<J, T, R> group(final Func func, final Object useless) {
_groupBys.add(new Pair<Func, Attribute>(func, _builder.getSpecifiedAttributes().get(0)));
_builder.getSpecifiedAttributes().clear();
return this;
}
public J having(Func func, Object obj, Op op, Object value) {
public J having(final Func func, final Object obj, final Op op, final Object value) {
assert (_having == null) : "You can only specify one having in a group by";
List<Attribute> attrs = _builder.getSpecifiedAttributes();
final List<Attribute> attrs = _builder.getSpecifiedAttributes();
assert attrs.size() == 1 : "You didn't specified an attribute";
_having = new Having(func, attrs.get(0), op, value);
@ -60,12 +64,12 @@ public class GroupBy<J extends SearchBase<?, T, R>, T, R> {
return _builder;
}
public void toSql(StringBuilder builder) {
public void toSql(final StringBuilder builder) {
builder.append(" GROUP BY ");
for (Pair<Func, Attribute> groupBy : _groupBys) {
for (final Pair<Func, Attribute> groupBy : _groupBys) {
if (groupBy.first() != null) {
String func = groupBy.first().toString();
func.replaceFirst("@", groupBy.second().table + "." + groupBy.second().columnName);
func = func.replaceFirst("@", groupBy.second().table + "." + groupBy.second().columnName);
builder.append(func);
} else {
builder.append(groupBy.second().table + "." + groupBy.second().columnName);
@ -86,18 +90,19 @@ public class GroupBy<J extends SearchBase<?, T, R>, T, R> {
public Op op;
public Object value;
public Having(Func func, Attribute attr, Op op, Object value) {
public Having(final Func func, final Attribute attr, final Op op, final Object value) {
this.func = func;
this.attr = attr;
this.op = op;
this.value = value;
}
public void toSql(StringBuilder builder) {
public void toSql(final StringBuilder builder) {
builder.append(" HAVING ");
if (func != null) {
String f = func.toString();
f.replaceFirst("@", attr.toString());
builder.append(func);
f = f.replaceFirst("@", attr.toString());
builder.append(f);
} else {
builder.append(attr.toString());
}

View File

@ -52,13 +52,13 @@ import com.cloud.utils.exception.CloudRuntimeException;
*/
public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
final Map<String, Attribute> _attrs;
final Class<T> _entityBeanType;
final Class<K> _resultType;
final GenericDaoBase<? extends T, ? extends Serializable> _dao;
Map<String, Attribute> _attrs;
Class<T> _entityBeanType;
Class<K> _resultType;
GenericDaoBase<? extends T, ? extends Serializable> _dao;
final ArrayList<Condition> _conditions;
final ArrayList<Attribute> _specifiedAttrs;
ArrayList<Condition> _conditions;
ArrayList<Attribute> _specifiedAttrs;
protected HashMap<String, JoinBuilder<SearchBase<?, ?, ?>>> _joins;
protected ArrayList<Select> _selects;
@ -66,7 +66,11 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
protected SelectType _selectType;
T _entity;
SearchBase(Class<T> entityType, Class<K> resultType) {
SearchBase(final Class<T> entityType, final Class<K> resultType) {
init(entityType, resultType);
}
protected void init(final Class<T> entityType, final Class<K> resultType) {
_dao = (GenericDaoBase<? extends T, ? extends Serializable>)GenericDaoBase.getDao(entityType);
if (_dao == null) {
throw new CloudRuntimeException("Unable to find DAO for " + entityType);
@ -81,7 +85,6 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
_joins = null;
_specifiedAttrs = new ArrayList<Attribute>();
}
/**
* Specifies how the search query should be grouped
*
@ -90,7 +93,7 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
* @see GroupBy
*/
@SuppressWarnings("unchecked")
public GroupBy<J, T, K> groupBy(Object... fields) {
public GroupBy<J, T, K> groupBy(final Object... fields) {
assert _groupBy == null : "Can't do more than one group bys";
_groupBy = new GroupBy<J, T, K>((J)this);
return _groupBy;
@ -106,7 +109,7 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
* @return itself to build more search parts.
*/
@SuppressWarnings("unchecked")
public J select(String fieldName, Func func, Object field, Object... params) {
public J select(final String fieldName, final Func func, final Object field, final Object... params) {
if (_entity == null) {
throw new RuntimeException("SearchBuilder cannot be modified once it has been setup");
}
@ -126,9 +129,9 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
try {
declaredField = _resultType.getDeclaredField(fieldName);
declaredField.setAccessible(true);
} catch (SecurityException e) {
} catch (final SecurityException e) {
throw new RuntimeException("Unable to find " + fieldName, e);
} catch (NoSuchFieldException e) {
} catch (final NoSuchFieldException e) {
throw new RuntimeException("Unable to find " + fieldName, e);
}
} else {
@ -138,7 +141,7 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
}
}
Select select = new Select(func, _specifiedAttrs.size() == 0 ? null : _specifiedAttrs.get(0), declaredField, params);
final Select select = new Select(func, _specifiedAttrs.size() == 0 ? null : _specifiedAttrs.get(0), declaredField, params);
_selects.add(select);
_specifiedAttrs.clear();
@ -153,7 +156,7 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
* @return itself
*/
@SuppressWarnings("unchecked")
public J selectFields(Object... fields) {
public J selectFields(final Object... fields) {
if (_entity == null) {
throw new RuntimeException("SearchBuilder cannot be modified once it has been setup");
}
@ -165,13 +168,13 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
_selects = new ArrayList<Select>();
}
for (Attribute attr : _specifiedAttrs) {
for (final Attribute attr : _specifiedAttrs) {
Field field = null;
try {
field = _resultType.getDeclaredField(attr.field.getName());
field.setAccessible(true);
} catch (SecurityException e) {
} catch (NoSuchFieldException e) {
} catch (final SecurityException e) {
} catch (final NoSuchFieldException e) {
}
_selects.add(new Select(Func.NATIVE, attr, field, null));
}
@ -192,14 +195,14 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
* @return itself
*/
@SuppressWarnings("unchecked")
public J join(String name, SearchBase<?, ?, ?> builder, Object joinField1, Object joinField2, JoinBuilder.JoinType joinType) {
public J join(final String name, final SearchBase<?, ?, ?> builder, final Object joinField1, final Object joinField2, final JoinBuilder.JoinType joinType) {
assert _entity != null : "SearchBuilder cannot be modified once it has been setup";
assert _specifiedAttrs.size() == 1 : "You didn't select the attribute.";
assert builder._entity != null : "SearchBuilder cannot be modified once it has been setup";
assert builder._specifiedAttrs.size() == 1 : "You didn't select the attribute.";
assert builder != this : "You can't add yourself, can you? Really think about it!";
JoinBuilder<SearchBase<?, ?, ?>> t = new JoinBuilder<SearchBase<?, ?, ?>>(builder, _specifiedAttrs.get(0), builder._specifiedAttrs.get(0), joinType);
final JoinBuilder<SearchBase<?, ?, ?>> t = new JoinBuilder<SearchBase<?, ?, ?>>(builder, _specifiedAttrs.get(0), builder._specifiedAttrs.get(0), joinType);
if (_joins == null) {
_joins = new HashMap<String, JoinBuilder<SearchBase<?, ?, ?>>>();
}
@ -214,8 +217,8 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
return _selectType;
}
protected void set(String name) {
Attribute attr = _attrs.get(name);
protected void set(final String name) {
final Attribute attr = _attrs.get(name);
assert (attr != null) : "Searching for a field that's not there: " + name;
_specifiedAttrs.add(attr);
}
@ -239,12 +242,12 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
return _specifiedAttrs;
}
protected Condition constructCondition(String conditionName, String cond, Attribute attr, Op op) {
protected Condition constructCondition(final String conditionName, final String cond, final Attribute attr, final Op op) {
assert _entity != null : "SearchBuilder cannot be modified once it has been setup";
assert op == null || _specifiedAttrs.size() == 1 : "You didn't select the attribute.";
assert op != Op.SC : "Call join";
Condition condition = new Condition(conditionName, cond, attr, op);
final Condition condition = new Condition(conditionName, cond, attr, op);
_conditions.add(condition);
_specifiedAttrs.clear();
return condition;
@ -304,7 +307,7 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
*/
@SuppressWarnings("unchecked")
public J cp() {
Condition condition = new Condition(null, " ) ", null, Op.RP);
final Condition condition = new Condition(null, " ) ", null, Op.RP);
_conditions.add(condition);
return (J)this;
}
@ -315,7 +318,7 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
*/
@SuppressWarnings("unchecked")
public J op() {
Condition condition = new Condition(null, " ( ", null, Op.RP);
final Condition condition = new Condition(null, " ( ", null, Op.RP);
_conditions.add(condition);
return (J)this;
}
@ -326,13 +329,13 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
@Override
protected synchronized void finalize() {
if (_entity != null) {
Factory factory = (Factory)_entity;
final Factory factory = (Factory)_entity;
factory.setCallback(0, null);
_entity = null;
}
if (_joins != null) {
for (JoinBuilder<SearchBase<?, ?, ?>> join : _joins.values()) {
for (final JoinBuilder<SearchBase<?, ?, ?>> join : _joins.values()) {
join.getT().finalize();
}
}
@ -343,7 +346,7 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
return;
}
for (Select select : _selects) {
for (final Select select : _selects) {
if (select.field == null) {
assert (_selects.size() == 1) : "You didn't specify any fields to put the result in but you're specifying more than one select so where should I put the selects?";
_selectType = SelectType.Single;
@ -365,11 +368,11 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
protected final Attribute attr;
protected Object[] presets;
protected Condition(String name) {
protected Condition(final String name) {
this(name, null, null, null);
}
public Condition(String name, String cond, Attribute attr, Op op) {
public Condition(final String name, final String cond, final Attribute attr, final Op op) {
this.name = name;
this.attr = attr;
this.cond = cond;
@ -381,7 +384,7 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
return presets != null;
}
public void setPresets(Object... presets) {
public void setPresets(final Object... presets) {
this.presets = presets;
}
@ -389,7 +392,7 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
return presets;
}
public void toSql(StringBuilder sql, Object[] params, int count) {
public void toSql(final StringBuilder sql, final Object[] params, final int count) {
if (count > 0) {
sql.append(cond);
}
@ -438,12 +441,12 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
}
@Override
public boolean equals(Object obj) {
public boolean equals(final Object obj) {
if (!(obj instanceof Condition)) {
return false;
}
Condition condition = (Condition)obj;
final Condition condition = (Condition)obj;
return name.equals(condition.name);
}
}
@ -457,7 +460,7 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
protected Select() {
}
public Select(Func func, Attribute attr, Field field, Object[] params) {
public Select(final Func func, final Attribute attr, final Field field, final Object[] params) {
this.func = func;
this.attr = attr;
this.params = params;
@ -467,22 +470,22 @@ public abstract class SearchBase<J extends SearchBase<?, T, K>, T, K> {
protected class Interceptor implements MethodInterceptor {
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
String name = method.getName();
public Object intercept(final Object object, final Method method, final Object[] args, final MethodProxy methodProxy) throws Throwable {
final String name = method.getName();
if (method.getAnnotation(Transient.class) == null) {
if (name.startsWith("get")) {
String fieldName = Character.toLowerCase(name.charAt(3)) + name.substring(4);
final String fieldName = Character.toLowerCase(name.charAt(3)) + name.substring(4);
set(fieldName);
return null;
} else if (name.startsWith("is")) {
String fieldName = Character.toLowerCase(name.charAt(2)) + name.substring(3);
final String fieldName = Character.toLowerCase(name.charAt(2)) + name.substring(3);
set(fieldName);
return null;
} else {
Column ann = method.getAnnotation(Column.class);
final Column ann = method.getAnnotation(Column.class);
if (ann != null) {
String colName = ann.name();
for (Map.Entry<String, Attribute> attr : _attrs.entrySet()) {
final String colName = ann.name();
for (final Map.Entry<String, Attribute> attr : _attrs.entrySet()) {
if (colName.equals(attr.getValue().columnName)) {
set(attr.getKey());
return null;

View File

@ -0,0 +1,62 @@
package com.cloud.utils.db;
import java.util.ArrayList;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
import com.cloud.utils.Pair;
import com.cloud.utils.db.SearchCriteria.Func;
import com.cloud.utils.db.SearchCriteria.Op;
public class GroupByTest {
protected static final String EXPECTED_QUERY = "BASE GROUP BY FIRST(TEST_TABLE.TEST_COLUMN), MAX(TEST_TABLE.TEST_COLUMN) HAVING COUNT(TEST_TABLE2.TEST_COLUMN2) > ? ";
@Test
public void testToSql() {
// Prepare
final StringBuilder sb = new StringBuilder("BASE");
final GroupByExtension groupBy = new GroupByExtension(new SearchBaseExtension(String.class, String.class));
final Attribute att = new Attribute("TEST_TABLE", "TEST_COLUMN");
final Pair<Func, Attribute> pair1 = new Pair<SearchCriteria.Func, Attribute>(SearchCriteria.Func.FIRST, att);
final Pair<Func, Attribute> pair2 = new Pair<SearchCriteria.Func, Attribute>(SearchCriteria.Func.MAX, att);
groupBy._groupBys = new ArrayList<Pair<Func, Attribute>>();
groupBy._groupBys.add(pair1);
groupBy._groupBys.add(pair2);
groupBy.having(SearchCriteria.Func.COUNT, att, Op.GT, "SOME_VALUE");
// Execute
groupBy.toSql(sb);
// Assert
assertTrue("It didn't create the expected SQL query.", sb.toString().equals(EXPECTED_QUERY));
}
}
class GroupByExtension extends GroupBy<SearchBaseExtension, String, String> {
public GroupByExtension(final SearchBaseExtension builder) {
super(builder);
_builder = builder;
}
@Override
protected void init(final SearchBaseExtension builder) {
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
class SearchBaseExtension extends SearchBase<SearchBaseExtension, String, String>{
SearchBaseExtension(final Class entityType, final Class resultType) {
super(entityType, resultType);
_specifiedAttrs = new ArrayList<Attribute>();
_specifiedAttrs.add(new Attribute("TEST_TABLE2", "TEST_COLUMN2"));
}
@Override
protected void init(final Class<String> entityType, final Class<String> resultType) {
}
}