diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/AbstractColQuery.java b/querydsl-collections/src/main/java/com/mysema/query/collections/AbstractColQuery.java index c1ec16321..63bb3e30b 100644 --- a/querydsl-collections/src/main/java/com/mysema/query/collections/AbstractColQuery.java +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/AbstractColQuery.java @@ -93,6 +93,7 @@ public class AbstractColQuery> { @SuppressWarnings("unchecked") public Iterable iterate(Expr e1, Expr e2, Expr... rest) { + // TODO : move this code to querydsl-core final Expr[] full = asArray(new Expr[rest.length + 2], e1, e2, rest); boolean oneType = true; if (e1.getType().isAssignableFrom((e2.getType()))){ @@ -243,7 +244,7 @@ public class AbstractColQuery> { case DEFAULT : // do nothing } } - indexSupport.init(sources, where.create()); + indexSupport.init(ops, sources, where.create()); multiIt.init(indexSupport); if (!wrapIterators && (where.create() != null)){ @@ -256,7 +257,7 @@ public class AbstractColQuery> { protected Iterator handleFromWhereSingleSource(List> sources) throws Exception{ JoinExpression join = joins.get(0); sources.add(join.getTarget()); - indexSupport.init(sources, where.create()); + indexSupport.init(ops, sources, where.create()); // create a simple projecting iterator for Object -> Object[] Iterator it = QueryIteratorUtils.toArrayIterator(indexSupport.getIterator(join.getTarget())); @@ -289,7 +290,7 @@ public class AbstractColQuery> { } protected Iterator handleSelect(Iterator it, List> sources, Expr projection) throws Exception { - return QueryIteratorUtils.project(ops, it, sources, projection); + return QueryIteratorUtils.transform(ops, it, sources, projection); } public Iterable iterate(final Expr projection) { diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/IndexSupport.java b/querydsl-collections/src/main/java/com/mysema/query/collections/IndexSupport.java index ab993dcf2..08386ea2a 100644 --- a/querydsl-collections/src/main/java/com/mysema/query/collections/IndexSupport.java +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/IndexSupport.java @@ -8,6 +8,8 @@ package com.mysema.query.collections; import java.util.Iterator; import java.util.List; +import com.mysema.query.collections.support.DefaultIndexSupport; +import com.mysema.query.grammar.JavaOps; import com.mysema.query.grammar.types.Expr; import com.mysema.query.grammar.types.Expr.EBoolean; @@ -22,7 +24,15 @@ import com.mysema.query.grammar.types.Expr.EBoolean; */ public interface IndexSupport { - void init(List> orderedSources, EBoolean condition); + /** + * init the IndexSupport instance + * NOTE : this IndexSupport instance is stateful and needs to be query specific, not global + * + * @param ops + * @param orderedSources + * @param condition + */ + void init(JavaOps ops, List> orderedSources, EBoolean condition); Iterator getIterator(Expr expr); diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/SourceSortingSupport.java b/querydsl-collections/src/main/java/com/mysema/query/collections/SourceSortingSupport.java index 879e63bc4..a0165760f 100644 --- a/querydsl-collections/src/main/java/com/mysema/query/collections/SourceSortingSupport.java +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/SourceSortingSupport.java @@ -22,6 +22,12 @@ import com.mysema.query.grammar.types.Expr.EBoolean; */ public interface SourceSortingSupport { + /** + * sort the given join sources using some optimization heuristics based on the given match condition + * + * @param joins + * @param condition + */ void sortSources(List> joins, EBoolean condition); } diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/iterators/FilteringMultiIterator.java b/querydsl-collections/src/main/java/com/mysema/query/collections/iterators/FilteringMultiIterator.java index 2d0b28a1f..579aac3f5 100644 --- a/querydsl-collections/src/main/java/com/mysema/query/collections/iterators/FilteringMultiIterator.java +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/iterators/FilteringMultiIterator.java @@ -116,8 +116,8 @@ public class FilteringMultiIterator extends MultiIterator implements IndexSuppor return this; } - public void init(List> orderedSources, EBoolean condition) { - indexSupport.init(orderedSources, condition); + public void init(JavaOps ops, List> orderedSources, EBoolean condition) { + indexSupport.init(ops, orderedSources, condition); } } diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/support/DefaultIndexSupport.java b/querydsl-collections/src/main/java/com/mysema/query/collections/support/DefaultIndexSupport.java index 172553af7..164f127a5 100644 --- a/querydsl-collections/src/main/java/com/mysema/query/collections/support/DefaultIndexSupport.java +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/support/DefaultIndexSupport.java @@ -1,32 +1,45 @@ -/* - * Copyright (c) 2009 Mysema Ltd. - * All rights reserved. - * - */ package com.mysema.query.collections.support; +import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import org.codehaus.janino.ExpressionEvaluator; + import com.mysema.query.collections.IndexSupport; +import com.mysema.query.collections.utils.EvaluatorUtils; +import com.mysema.query.collections.utils.QueryIteratorUtils; +import com.mysema.query.grammar.JavaOps; +import com.mysema.query.grammar.Ops; import com.mysema.query.grammar.types.Expr; +import com.mysema.query.grammar.types.Operation; +import com.mysema.query.grammar.types.Path; import com.mysema.query.grammar.types.Expr.EBoolean; +import com.mysema.query.util.Assert; /** - * DefaultIndexSupport is the default implementation of the IndexSupport interface - * - * @see IndexSupport + * ExtendedIndexSupport provides * * @author tiwe * @version $Id$ */ public class DefaultIndexSupport implements IndexSupport{ + private JavaOps ops; + + private List> sources; + +// private EBoolean condition; + private final Map,Iterable> exprToIt; + final Map,Map>> pathEqPathIndex; + public DefaultIndexSupport(Map,Iterable> exprToIt){ - this.exprToIt = exprToIt; + this.exprToIt = Assert.notNull(exprToIt); + this.pathEqPathIndex = new HashMap,Map>>(); } @SuppressWarnings("unchecked") @@ -36,11 +49,47 @@ public class DefaultIndexSupport implements IndexSupport{ @SuppressWarnings("unchecked") public Iterator getIterator(Expr expr, Object[] bindings) { + // TODO : make use of the index when appropriate return (Iterator)exprToIt.get(expr).iterator(); } - public void init(List> sources, EBoolean condition) { - // do nothing - } + public void init(JavaOps ops, List> sources, EBoolean condition) { + this.ops = Assert.notNull(ops); + this.sources = Assert.notNull(sources); +// this.condition = Assert.notNull(condition); + // populate the "path eq path" index + // TODO : make a decision when index usage is appropriate + if (condition instanceof Operation){ + visitOperation((Operation) condition); + } + + // TODO : filter the sources, based on non-contextual parts of the condition + } + + private void visitOperation(Operation op) { + if (op.getOperator() == Ops.EQ_OBJECT || op.getOperator() == Ops.EQ_PRIMITIVE){ + if (op.getArgs()[0] instanceof Path && op.getArgs()[1] instanceof Path){ + Path p1 = (Path) op.getArgs()[0], p2 = (Path) op.getArgs()[1]; + int i1 = sources.indexOf(p1.getRoot()); + int i2 = sources.indexOf(p2.getRoot()); + if (i1 < i2){ + indexPath(p2); + }else if (i1 > i2){ + indexPath(p1); + } + } + }else{ + for (Expr e : op.getArgs()){ + if (e instanceof Operation) visitOperation((Operation) e); + } + } + } + + private void indexPath(Path path) { + ExpressionEvaluator ev = EvaluatorUtils.create(ops, Collections.>singletonList((Expr)path.getRoot()), (Expr)path); + Map> map = QueryIteratorUtils.projectToMap(exprToIt.get(path.getRoot()).iterator(), ev); + pathEqPathIndex.put(path, map); + } + } diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/support/SimpleIndexSupport.java b/querydsl-collections/src/main/java/com/mysema/query/collections/support/SimpleIndexSupport.java new file mode 100644 index 000000000..1fb01fb95 --- /dev/null +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/support/SimpleIndexSupport.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2009 Mysema Ltd. + * All rights reserved. + * + */ +package com.mysema.query.collections.support; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import com.mysema.query.collections.IndexSupport; +import com.mysema.query.grammar.JavaOps; +import com.mysema.query.grammar.types.Expr; +import com.mysema.query.grammar.types.Expr.EBoolean; + +/** + * DefaultIndexSupport is the default implementation of the IndexSupport interface + * + * @see IndexSupport + * + * @author tiwe + * @version $Id$ + */ +public class SimpleIndexSupport implements IndexSupport{ + + private final Map,Iterable> exprToIt; + + public SimpleIndexSupport(Map,Iterable> exprToIt){ + this.exprToIt = exprToIt; + } + + @SuppressWarnings("unchecked") + public Iterator getIterator(Expr expr) { + return (Iterator)exprToIt.get(expr).iterator(); + } + + @SuppressWarnings("unchecked") + public Iterator getIterator(Expr expr, Object[] bindings) { + return (Iterator)exprToIt.get(expr).iterator(); + } + + public void init(JavaOps ops, List> sources, EBoolean condition) { + // do nothing + } + +} diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/utils/QueryIteratorUtils.java b/querydsl-collections/src/main/java/com/mysema/query/collections/utils/QueryIteratorUtils.java index d7182af7d..270cf78fb 100644 --- a/querydsl-collections/src/main/java/com/mysema/query/collections/utils/QueryIteratorUtils.java +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/utils/QueryIteratorUtils.java @@ -6,8 +6,7 @@ package com.mysema.query.collections.utils; import java.lang.reflect.InvocationTargetException; -import java.util.Iterator; -import java.util.List; +import java.util.*; import org.apache.commons.collections15.IteratorUtils; import org.apache.commons.collections15.Predicate; @@ -34,6 +33,16 @@ public class QueryIteratorUtils { } } + /** + * filter the given iterator using the given condition + * + * @param + * @param ops + * @param source + * @param sources + * @param condition + * @return + */ public static Iterator multiArgFilter(JavaOps ops, Iterator source, List> sources, EBoolean condition){ ExpressionEvaluator ev = EvaluatorUtils.create(ops, sources, condition); return multiArgFilter(source, ev); @@ -47,19 +56,63 @@ public class QueryIteratorUtils { }); } - public static Iterator project(JavaOps ops, Iterator source, List> sources, Expr projection){ + /** + * transform the given source iterator using the given projection expression + * + * @param + * @param + * @param ops + * @param source + * @param sources + * @param projection + * @return + */ + public static Iterator transform(JavaOps ops, Iterator source, List> sources, Expr projection){ ExpressionEvaluator ev = EvaluatorUtils.create(ops, sources, projection); - return project(source, ev); + return transform(source, ev); } - private static Iterator project(Iterator source, final ExpressionEvaluator ev){ + private static Iterator transform(Iterator source, final ExpressionEvaluator ev){ return IteratorUtils.transformedIterator(source, new Transformer(){ public T transform(S input) { return QueryIteratorUtils.evaluate(ev, (Object[])input); } }); } + + /** + * project the given source iterator to a map by treating the iterator values + * as map values and the projections as map keys + * + * @param + * @param + * @param source + * @param ev + * @return + */ + public static Map> projectToMap(Iterator source, ExpressionEvaluator ev){ + Map> map = new HashMap>(); + while (source.hasNext()){ + S key = source.next(); + T value = evaluate(ev, key); + Collection col = map.get(key); + if (col == null){ + col = new ArrayList(); + map.put(key, col); + } + col.add(value); + } + return map; + } + /** + * filter the given iterator using the given expressionevaluator that evaluates to true / false + * + * @param + * @param source + * @param ev + * @return + */ public static Iterator singleArgFilter(Iterator source, final ExpressionEvaluator ev){ return IteratorUtils.filteredIterator(source, new Predicate(){ public boolean evaluate(S object) { diff --git a/querydsl-collections/src/main/java/com/mysema/query/grammar/JavaSerializer.java b/querydsl-collections/src/main/java/com/mysema/query/grammar/JavaSerializer.java index 9e3e4e5a1..11727b21d 100644 --- a/querydsl-collections/src/main/java/com/mysema/query/grammar/JavaSerializer.java +++ b/querydsl-collections/src/main/java/com/mysema/query/grammar/JavaSerializer.java @@ -208,6 +208,7 @@ public class JavaSerializer extends BaseSerializer{ @Override protected void visitOperation(Class type, Op operator, Expr... args) { if (operator.equals(Ops.LIKE)){ + // optimize like matches to startsWith and endsWith, when possible String right = args[1].toString(); if (!right.contains("_")){ int lastIndex = right.lastIndexOf('%'); diff --git a/querydsl-collections/src/test/java/com/mysema/query/collections/iterators/MultiIteratorTest.java b/querydsl-collections/src/test/java/com/mysema/query/collections/iterators/MultiIteratorTest.java index 934cf9ee3..c6f57b6f8 100644 --- a/querydsl-collections/src/test/java/com/mysema/query/collections/iterators/MultiIteratorTest.java +++ b/querydsl-collections/src/test/java/com/mysema/query/collections/iterators/MultiIteratorTest.java @@ -14,6 +14,7 @@ import org.junit.Test; import com.mysema.query.JoinExpression; import com.mysema.query.collections.IndexSupport; import com.mysema.query.collections.MiniApi; +import com.mysema.query.grammar.JavaOps; import com.mysema.query.grammar.types.Expr; import com.mysema.query.grammar.types.Expr.EBoolean; import com.mysema.query.grammar.types.Expr.ENumber; @@ -60,7 +61,7 @@ public class MultiIteratorTest extends AbstractIteratorTest { return getIterator(expr); } - public void init(List> sources, EBoolean where) { + public void init(JavaOps ops, List> sources, EBoolean where) { // TODO Auto-generated method stub } diff --git a/querydsl-collections/src/test/java/com/mysema/query/collections/support/DefaultIndexSupportTest.java b/querydsl-collections/src/test/java/com/mysema/query/collections/support/DefaultIndexSupportTest.java new file mode 100644 index 000000000..e52b7f7e9 --- /dev/null +++ b/querydsl-collections/src/test/java/com/mysema/query/collections/support/DefaultIndexSupportTest.java @@ -0,0 +1,59 @@ +package com.mysema.query.collections.support; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +import com.mysema.query.collections.AbstractQueryTest; +import com.mysema.query.grammar.JavaOps; +import com.mysema.query.grammar.types.Expr; + + +/** + * DefaultIndexSupportTest provides + * + * @author tiwe + * @version $Id$ + */ +public class DefaultIndexSupportTest extends AbstractQueryTest{ + + private JavaOps ops = new JavaOps(); + + private Map,Iterable> exprToIt = new HashMap,Iterable>(); + + private DefaultIndexSupport indexSupport; + + @Before + public void before(){ + exprToIt.put(cat, cats); + exprToIt.put(otherCat, cats); + indexSupport = new DefaultIndexSupport(exprToIt); + } + + @Test + public void test1(){ + indexSupport.init(ops, Arrays.asList(cat,otherCat), cat.name.eq(otherCat.name)); + Map> map = indexSupport.pathEqPathIndex.get(otherCat.name); + assertTrue("map was null or empty", map != null && !map.isEmpty()); + assertEquals(4, map.size()); + assertTrue(indexSupport.pathEqPathIndex.get(cat.name) == null); + + } + + @Test + public void test2(){ + indexSupport.init(ops, Arrays.asList(otherCat,cat), cat.name.eq(otherCat.name)); + Map> map = indexSupport.pathEqPathIndex.get(cat.name); + assertTrue("map was null or empty", map != null && !map.isEmpty()); + assertEquals(4, map.size()); + assertTrue(indexSupport.pathEqPathIndex.get(otherCat.name) == null); + + } + +}