From de512d7402024acb61917cacfd98e9aaaed4a456 Mon Sep 17 00:00:00 2001 From: Joel Bernstein Date: Wed, 8 Feb 2017 12:55:18 -0500 Subject: [PATCH] SOLR-8593: Push down the HAVING clause --- .../solr/handler/sql/SolrAggregate.java | 3 + .../apache/solr/handler/sql/SolrFilter.java | 231 ++++++++++++++++-- .../apache/solr/handler/sql/SolrMethod.java | 1 + .../org/apache/solr/handler/sql/SolrRel.java | 14 ++ .../apache/solr/handler/sql/SolrRules.java | 4 +- .../apache/solr/handler/sql/SolrTable.java | 90 +++++-- .../sql/SolrToEnumerableConverter.java | 3 +- .../client/solrj/io/ops/AndOperation.java | 59 ++--- .../solr/client/solrj/io/ops/OrOperation.java | 47 ++-- 9 files changed, 354 insertions(+), 98 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/handler/sql/SolrAggregate.java b/solr/core/src/java/org/apache/solr/handler/sql/SolrAggregate.java index 5068b2ee620..983ab7691be 100644 --- a/solr/core/src/java/org/apache/solr/handler/sql/SolrAggregate.java +++ b/solr/core/src/java/org/apache/solr/handler/sql/SolrAggregate.java @@ -69,8 +69,11 @@ class SolrAggregate extends Aggregate implements SolrRel { for(Pair namedAggCall : getNamedAggCalls()) { + AggregateCall aggCall = namedAggCall.getKey(); + Pair metric = toSolrMetric(implementor, aggCall, inNames); + implementor.addReverseAggMapping(namedAggCall.getValue(), metric.getKey().toLowerCase()+"("+metric.getValue()+")"); implementor.addMetricPair(namedAggCall.getValue(), metric.getKey(), metric.getValue()); if(aggCall.getName() == null) { implementor.addFieldMapping(namedAggCall.getValue(), diff --git a/solr/core/src/java/org/apache/solr/handler/sql/SolrFilter.java b/solr/core/src/java/org/apache/solr/handler/sql/SolrFilter.java index 01d3346f333..50102b1fefc 100644 --- a/solr/core/src/java/org/apache/solr/handler/sql/SolrFilter.java +++ b/solr/core/src/java/org/apache/solr/handler/sql/SolrFilter.java @@ -29,6 +29,7 @@ import org.apache.calcite.util.Pair; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * Implementation of a {@link org.apache.calcite.rel.core.Filter} relational expression in Solr. @@ -54,13 +55,18 @@ class SolrFilter extends Filter implements SolrRel { public void implement(Implementor implementor) { implementor.visitChild(0, getInput()); - Translator translator = new Translator(SolrRules.solrFieldNames(getRowType())); - String query = translator.translateMatch(condition); - implementor.addQuery(query); - implementor.setNegativeQuery(translator.negativeQuery); + if(getInput() instanceof SolrAggregate) { + HavingTranslator translator = new HavingTranslator(SolrRules.solrFieldNames(getRowType()), implementor.reverseAggMappings); + String havingPredicate = translator.translateMatch(condition); + implementor.setHavingPredicate(havingPredicate); + } else { + Translator translator = new Translator(SolrRules.solrFieldNames(getRowType())); + String query = translator.translateMatch(condition); + implementor.addQuery(query); + implementor.setNegativeQuery(translator.negativeQuery); + } } - /** Translates {@link RexNode} expressions into Solr query strings. */ private static class Translator { private final List fieldNames; @@ -71,11 +77,11 @@ class SolrFilter extends Filter implements SolrRel { } private String translateMatch(RexNode condition) { - if(condition.getKind().belongsTo(SqlKind.COMPARISON)) { + if (condition.getKind().belongsTo(SqlKind.COMPARISON)) { return translateComparison(condition); - } else if(condition.isA(SqlKind.AND)) { - return "("+translateAnd(condition)+")"; - } else if(condition.isA(SqlKind.OR)) { + } else if (condition.isA(SqlKind.AND)) { + return "(" + translateAnd(condition) + ")"; + } else if (condition.isA(SqlKind.OR)) { return "(" + translateOr(condition) + ")"; } else { return null; @@ -90,8 +96,6 @@ class SolrFilter extends Filter implements SolrRel { return String.join(" OR ", ors); } - - private String translateAnd(RexNode node0) { List andStrings = new ArrayList(); List notStrings = new ArrayList(); @@ -101,18 +105,18 @@ class SolrFilter extends Filter implements SolrRel { RelOptUtil.decomposeConjunction(node0, ands, nots); - for(RexNode node: ands) { + for (RexNode node : ands) { andStrings.add(translateMatch(node)); } String andString = String.join(" AND ", andStrings); - if(nots.size() > 0) { - for(RexNode node: nots) { + if (nots.size() > 0) { + for (RexNode node : nots) { notStrings.add(translateMatch(node)); } String notString = String.join(" NOT ", notStrings); - return "("+ andString +") NOT ("+notString+")"; + return "(" + andString + ") NOT (" + notString + ")"; } else { return andString; } @@ -126,39 +130,41 @@ class SolrFilter extends Filter implements SolrRel { switch (node.getKind()) { case NOT: - return "-"+translateComparison(((RexCall) node).getOperands().get(0)); + return "-" + translateComparison(((RexCall) node).getOperands().get(0)); case EQUALS: String terms = binaryTranslated.getValue().getValue2().toString().trim(); - if(!terms.startsWith("(") && !terms.startsWith("[") && !terms.startsWith("{")){ - terms = "\""+terms+"\""; + if (!terms.startsWith("(") && !terms.startsWith("[") && !terms.startsWith("{")) { + terms = "\"" + terms + "\""; } String clause = binaryTranslated.getKey() + ":" + terms; this.negativeQuery = false; return clause; case NOT_EQUALS: - return "-(" + binaryTranslated.getKey() + ":" + binaryTranslated.getValue().getValue2()+")"; + return "-(" + binaryTranslated.getKey() + ":" + binaryTranslated.getValue().getValue2() + ")"; case LESS_THAN: this.negativeQuery = false; - return "("+binaryTranslated.getKey() + ": [ * TO " + binaryTranslated.getValue().getValue2() + " })"; + return "(" + binaryTranslated.getKey() + ": [ * TO " + binaryTranslated.getValue().getValue2() + " })"; case LESS_THAN_OR_EQUAL: this.negativeQuery = false; - return "("+binaryTranslated.getKey() + ": [ * TO " + binaryTranslated.getValue().getValue2() + " ])"; + return "(" + binaryTranslated.getKey() + ": [ * TO " + binaryTranslated.getValue().getValue2() + " ])"; case GREATER_THAN: this.negativeQuery = false; - return "("+binaryTranslated.getKey() + ": { " + binaryTranslated.getValue().getValue2() + " TO * ])"; + return "(" + binaryTranslated.getKey() + ": { " + binaryTranslated.getValue().getValue2() + " TO * ])"; case GREATER_THAN_OR_EQUAL: this.negativeQuery = false; - return "("+binaryTranslated.getKey() + ": [ " + binaryTranslated.getValue().getValue2() + " TO * ])"; + return "(" + binaryTranslated.getKey() + ": [ " + binaryTranslated.getValue().getValue2() + " TO * ])"; default: throw new AssertionError("cannot translate " + node); } } - /** Translates a call to a binary operator, reversing arguments if necessary. */ + /** + * Translates a call to a binary operator, reversing arguments if necessary. + */ private Pair translateBinary(RexCall call) { List operands = call.getOperands(); - if(operands.size() != 2) { + if (operands.size() != 2) { throw new AssertionError("Invalid number of arguments - " + operands.size()); } final RexNode left = operands.get(0); @@ -174,7 +180,9 @@ class SolrFilter extends Filter implements SolrRel { throw new AssertionError("cannot translate call " + call); } - /** Translates a call to a binary operator. Returns whether successful. */ + /** + * Translates a call to a binary operator. Returns whether successful. + */ private Pair translateBinary2(RexNode left, RexNode right) { switch (right.getKind()) { case LITERAL: @@ -194,6 +202,177 @@ class SolrFilter extends Filter implements SolrRel { // String itemName = SolrRules.isItem((RexCall) left); // if (itemName != null) { // return translateOp2(op, itemName, rightLiteral); +// } + default: + return null; + } + } + } + + private static class HavingTranslator { + + private final List fieldNames; + private Map reverseAggMappings; + + HavingTranslator(List fieldNames, Map reverseAggMappings) { + this.fieldNames = fieldNames; + this.reverseAggMappings = reverseAggMappings; + } + + private String translateMatch(RexNode condition) { + if (condition.getKind().belongsTo(SqlKind.COMPARISON)) { + return translateComparison(condition); + } else if (condition.isA(SqlKind.AND)) { + return translateAnd(condition); + } else if (condition.isA(SqlKind.OR)) { + return translateOr(condition); + } else { + return null; + } + } + + private String translateOr(RexNode condition) { + List ors = new ArrayList<>(); + for (RexNode node : RelOptUtil.disjunctions(condition)) { + ors.add(translateMatch(node)); + } + StringBuilder builder = new StringBuilder(); + + builder.append("or("); + int i = 0; + for (i = 0; i < ors.size(); i++) { + if (i > 0) { + builder.append(","); + } + + builder.append(ors.get(i)); + } + builder.append(")"); + return builder.toString(); + } + + private String translateAnd(RexNode node0) { + List andStrings = new ArrayList(); + List notStrings = new ArrayList(); + + List ands = new ArrayList(); + List nots = new ArrayList(); + + RelOptUtil.decomposeConjunction(node0, ands, nots); + + for (RexNode node : ands) { + andStrings.add(translateMatch(node)); + } + + StringBuilder builder = new StringBuilder(); + + builder.append("and("); + for (int i = 0; i < andStrings.size(); i++) { + if (i > 0) { + builder.append(","); + } + + builder.append(andStrings.get(i)); + } + builder.append(")"); + + + if (nots.size() > 0) { + for (RexNode node : nots) { + notStrings.add(translateMatch(node)); + } + + StringBuilder notBuilder = new StringBuilder(); + for(int i=0; i< notStrings.size(); i++) { + if(i > 0) { + notBuilder.append(","); + } + notBuilder.append("not("); + notBuilder.append(notStrings.get(i)); + notBuilder.append(")"); + } + + return "and(" + builder.toString() + ","+ notBuilder.toString()+")"; + } else { + return builder.toString(); + } + } + + private String translateComparison(RexNode node) { + Pair binaryTranslated = null; + if (((RexCall) node).getOperands().size() == 2) { + binaryTranslated = translateBinary((RexCall) node); + } + + switch (node.getKind()) { + + case EQUALS: + String terms = binaryTranslated.getValue().getValue2().toString().trim(); + String clause = "eq(" + binaryTranslated.getKey() + "," + terms + ")"; + return clause; + case NOT_EQUALS: + return "not(eq(" + binaryTranslated.getKey() + "," + binaryTranslated.getValue().getValue2() + "))"; + case LESS_THAN: + return "lt(" + binaryTranslated.getKey() + "," + binaryTranslated.getValue().getValue2() + ")"; + case LESS_THAN_OR_EQUAL: + return "lteq(" + binaryTranslated.getKey() + "," + binaryTranslated.getValue().getValue2() + ")"; + case GREATER_THAN: + return "gt(" + binaryTranslated.getKey() + "," + binaryTranslated.getValue().getValue2() + ")"; + case GREATER_THAN_OR_EQUAL: + return "gteq(" + binaryTranslated.getKey() + "," + binaryTranslated.getValue().getValue2() + ")"; + default: + throw new AssertionError("cannot translate " + node); + } + } + + /** + * Translates a call to a binary operator, reversing arguments if necessary. + */ + private Pair translateBinary(RexCall call) { + List operands = call.getOperands(); + if (operands.size() != 2) { + throw new AssertionError("Invalid number of arguments - " + operands.size()); + } + final RexNode left = operands.get(0); + final RexNode right = operands.get(1); + final Pair a = translateBinary2(left, right); + + if (a != null) { + if(reverseAggMappings.containsKey(a.getKey())) { + return new Pair(reverseAggMappings.get(a.getKey()),a.getValue()); + } + return a; + } + final Pair b = translateBinary2(right, left); + if (b != null) { + return b; + } + throw new AssertionError("cannot translate call " + call); + } + + /** + * Translates a call to a binary operator. Returns whether successful. + */ + private Pair translateBinary2(RexNode left, RexNode right) { + switch (right.getKind()) { + case LITERAL: + break; + default: + return null; + } + + final RexLiteral rightLiteral = (RexLiteral) right; + switch (left.getKind()) { + case INPUT_REF: + final RexInputRef left1 = (RexInputRef) left; + String name = fieldNames.get(left1.getIndex()); + return new Pair<>(name, rightLiteral); + case CAST: + return translateBinary2(((RexCall) left).operands.get(0), right); +// case OTHER_FUNCTION: +// String itemName = SolrRules.isItem((RexCall) left); +// if (itemName != null) { +// return translateOp2(op, itemName, rightLiteral); // } default: return null; diff --git a/solr/core/src/java/org/apache/solr/handler/sql/SolrMethod.java b/solr/core/src/java/org/apache/solr/handler/sql/SolrMethod.java index 4ec3fdb4b39..b0bf80140b3 100644 --- a/solr/core/src/java/org/apache/solr/handler/sql/SolrMethod.java +++ b/solr/core/src/java/org/apache/solr/handler/sql/SolrMethod.java @@ -33,6 +33,7 @@ enum SolrMethod { List.class, List.class, String.class, + String.class, String.class); public final Method method; diff --git a/solr/core/src/java/org/apache/solr/handler/sql/SolrRel.java b/solr/core/src/java/org/apache/solr/handler/sql/SolrRel.java index b7843d7d267..557cfe063d6 100644 --- a/solr/core/src/java/org/apache/solr/handler/sql/SolrRel.java +++ b/solr/core/src/java/org/apache/solr/handler/sql/SolrRel.java @@ -20,6 +20,7 @@ import org.apache.calcite.plan.Convention; import org.apache.calcite.plan.RelOptTable; import org.apache.calcite.rel.RelNode; import org.apache.calcite.util.Pair; +import org.apache.solr.client.solrj.io.ops.BooleanOperation; import java.util.*; @@ -35,7 +36,9 @@ interface SolrRel extends RelNode { /** Callback for the implementation process that converts a tree of {@link SolrRel} nodes into a Solr query. */ class Implementor { final Map fieldMappings = new HashMap<>(); + final Map reverseAggMappings = new HashMap<>(); String query = null; + String havingPredicate; boolean negativeQuery; String limitValue = null; final List> orders = new ArrayList<>(); @@ -51,6 +54,12 @@ interface SolrRel extends RelNode { } } + void addReverseAggMapping(String key, String val) { + if(key != null && !reverseAggMappings.containsKey(key)) { + this.reverseAggMappings.put(key, val); + } + } + void addQuery(String query) { this.query = query; } @@ -79,6 +88,11 @@ interface SolrRel extends RelNode { } } + void setHavingPredicate(String havingPredicate) { + this.havingPredicate = havingPredicate; + } + + void setLimit(String limit) { limitValue = limit; } diff --git a/solr/core/src/java/org/apache/solr/handler/sql/SolrRules.java b/solr/core/src/java/org/apache/solr/handler/sql/SolrRules.java index 118ec1a5a46..4cbadda5494 100644 --- a/solr/core/src/java/org/apache/solr/handler/sql/SolrRules.java +++ b/solr/core/src/java/org/apache/solr/handler/sql/SolrRules.java @@ -111,7 +111,7 @@ class SolrRules { } SolrConverterRule(Class clazz, Predicate predicate, String description) { - super(clazz, predicate::test, Convention.NONE, SolrRel.CONVENTION, description); + super(clazz, Convention.NONE, SolrRel.CONVENTION, description); } } @@ -120,8 +120,10 @@ class SolrRules { */ private static class SolrFilterRule extends SolrConverterRule { private static boolean isNotFilterByExpr(List rexNodes, List fieldNames) { + // We dont have a way to filter by result of aggregator now boolean result = true; + for (RexNode rexNode : rexNodes) { if (rexNode instanceof RexCall) { result = result && isNotFilterByExpr(((RexCall) rexNode).getOperands(), fieldNames); diff --git a/solr/core/src/java/org/apache/solr/handler/sql/SolrTable.java b/solr/core/src/java/org/apache/solr/handler/sql/SolrTable.java index fff646887c7..5f64231c8e3 100644 --- a/solr/core/src/java/org/apache/solr/handler/sql/SolrTable.java +++ b/solr/core/src/java/org/apache/solr/handler/sql/SolrTable.java @@ -32,7 +32,17 @@ import org.apache.solr.client.solrj.io.comp.ComparatorOrder; import org.apache.solr.client.solrj.io.comp.FieldComparator; import org.apache.solr.client.solrj.io.comp.MultipleFieldComparator; import org.apache.solr.client.solrj.io.comp.StreamComparator; +import org.apache.solr.client.solrj.io.ops.AndOperation; +import org.apache.solr.client.solrj.io.ops.BooleanOperation; +import org.apache.solr.client.solrj.io.ops.EqualsOperation; +import org.apache.solr.client.solrj.io.ops.GreaterThanEqualToOperation; +import org.apache.solr.client.solrj.io.ops.GreaterThanOperation; +import org.apache.solr.client.solrj.io.ops.LessThanEqualToOperation; +import org.apache.solr.client.solrj.io.ops.LessThanOperation; +import org.apache.solr.client.solrj.io.ops.NotOperation; +import org.apache.solr.client.solrj.io.ops.OrOperation; import org.apache.solr.client.solrj.io.stream.*; +import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParser; import org.apache.solr.client.solrj.io.stream.expr.StreamFactory; import org.apache.solr.client.solrj.io.stream.metrics.*; import org.apache.solr.common.params.CommonParams; @@ -72,7 +82,7 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable { private Enumerable query(final Properties properties) { return query(properties, Collections.emptyList(), null, Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), null, null); + Collections.emptyList(), null, null, null); } /** Executes a Solr query on the underlying table. @@ -89,7 +99,8 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable { final List buckets, final List> metricPairs, final String limit, - final String negativeQuery) { + final String negativeQuery, + final String havingPredicate) { // SolrParams should be a ModifiableParams instead of a map boolean mapReduce = "map_reduce".equals(properties.getProperty("aggregationMode")); boolean negative = Boolean.parseBoolean(negativeQuery); @@ -106,8 +117,6 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable { } } - System.out.println("####### Limit:"+limit); - TupleStream tupleStream; String zk = properties.getProperty("zk"); try { @@ -126,7 +135,8 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable { orders, buckets, metricPairs, - limit); + limit, + havingPredicate); } else { tupleStream = handleGroupByFacet(zk, collection, @@ -135,7 +145,8 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable { orders, buckets, metricPairs, - limit); + limit, + havingPredicate); } } } @@ -403,7 +414,8 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable { final List> orders, final List _buckets, final List> metricPairs, - final String limit) throws IOException { + final String limit, + final String havingPredicate) throws IOException { int numWorkers = Integer.parseInt(properties.getProperty("numWorkers", "1")); @@ -438,21 +450,36 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable { CloudSolrStream cstream = new CloudSolrStream(zk, collection, params); tupleStream = new RollupStream(cstream, buckets, metrics); + StreamFactory factory = new StreamFactory() + .withFunctionName("search", CloudSolrStream.class) + .withFunctionName("parallel", ParallelStream.class) + .withFunctionName("rollup", RollupStream.class) + .withFunctionName("sum", SumMetric.class) + .withFunctionName("min", MinMetric.class) + .withFunctionName("max", MaxMetric.class) + .withFunctionName("avg", MeanMetric.class) + .withFunctionName("count", CountMetric.class) + .withFunctionName("and", AndOperation.class) + .withFunctionName("or", OrOperation.class) + .withFunctionName("not", NotOperation.class) + .withFunctionName("eq", EqualsOperation.class) + .withFunctionName("gt", GreaterThanOperation.class) + .withFunctionName("lt", LessThanOperation.class) + .withFunctionName("lteq", LessThanEqualToOperation.class) + .withFunctionName("having", HavingStream.class) + .withFunctionName("gteq", GreaterThanEqualToOperation.class); + + if(havingPredicate != null) { + BooleanOperation booleanOperation = (BooleanOperation)factory.constructOperation(StreamExpressionParser.parse(havingPredicate)); + tupleStream = new HavingStream(tupleStream, booleanOperation); + } + if(numWorkers > 1) { // Do the rollups in parallel // Maintain the sort of the Tuples coming from the workers. StreamComparator comp = bucketSortComp(buckets, sortDirection); ParallelStream parallelStream = new ParallelStream(zk, collection, tupleStream, numWorkers, comp); - StreamFactory factory = new StreamFactory() - .withFunctionName("search", CloudSolrStream.class) - .withFunctionName("parallel", ParallelStream.class) - .withFunctionName("rollup", RollupStream.class) - .withFunctionName("sum", SumMetric.class) - .withFunctionName("min", MinMetric.class) - .withFunctionName("max", MaxMetric.class) - .withFunctionName("avg", MeanMetric.class) - .withFunctionName("count", CountMetric.class); parallelStream.setStreamFactory(factory); tupleStream = parallelStream; @@ -508,7 +535,8 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable { final List> orders, final List bucketFields, final List> metricPairs, - final String lim) throws IOException { + final String lim, + final String havingPredicate) throws IOException { ModifiableSolrParams solrParams = new ModifiableSolrParams(); solrParams.add(CommonParams.Q, query); @@ -542,6 +570,30 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable { limit); + + StreamFactory factory = new StreamFactory() + .withFunctionName("search", CloudSolrStream.class) + .withFunctionName("parallel", ParallelStream.class) + .withFunctionName("rollup", RollupStream.class) + .withFunctionName("sum", SumMetric.class) + .withFunctionName("min", MinMetric.class) + .withFunctionName("max", MaxMetric.class) + .withFunctionName("avg", MeanMetric.class) + .withFunctionName("count", CountMetric.class) + .withFunctionName("and", AndOperation.class) + .withFunctionName("or", OrOperation.class) + .withFunctionName("not", NotOperation.class) + .withFunctionName("eq", EqualsOperation.class) + .withFunctionName("gt", GreaterThanOperation.class) + .withFunctionName("lt", LessThanOperation.class) + .withFunctionName("lteq", LessThanEqualToOperation.class) + .withFunctionName("gteq", GreaterThanEqualToOperation.class); + + if(havingPredicate != null) { + BooleanOperation booleanOperation = (BooleanOperation)factory.constructOperation(StreamExpressionParser.parse(havingPredicate)); + tupleStream = new HavingStream(tupleStream, booleanOperation); + } + if(lim != null) { tupleStream = new LimitStream(tupleStream, limit); @@ -623,8 +675,8 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable { */ @SuppressWarnings("UnusedDeclaration") public Enumerable query(List> fields, String query, List> order, - List buckets, List> metricPairs, String limit, String negativeQuery) { - return getTable().query(getProperties(), fields, query, order, buckets, metricPairs, limit, negativeQuery); + List buckets, List> metricPairs, String limit, String negativeQuery, String havingPredicate) { + return getTable().query(getProperties(), fields, query, order, buckets, metricPairs, limit, negativeQuery, havingPredicate); } } diff --git a/solr/core/src/java/org/apache/solr/handler/sql/SolrToEnumerableConverter.java b/solr/core/src/java/org/apache/solr/handler/sql/SolrToEnumerableConverter.java index 9b18891b1a7..10d4d4c9688 100644 --- a/solr/core/src/java/org/apache/solr/handler/sql/SolrToEnumerableConverter.java +++ b/solr/core/src/java/org/apache/solr/handler/sql/SolrToEnumerableConverter.java @@ -84,8 +84,9 @@ class SolrToEnumerableConverter extends ConverterImpl implements EnumerableRel { final Expression metricPairs = list.append("metricPairs", constantArrayList(solrImplementor.metricPairs, Pair.class)); final Expression limit = list.append("limit", Expressions.constant(solrImplementor.limitValue)); final Expression negativeQuery = list.append("negativeQuery", Expressions.constant(Boolean.toString(solrImplementor.negativeQuery), String.class)); + final Expression havingPredicate = list.append("havingTest", Expressions.constant(solrImplementor.havingPredicate, String.class)); Expression enumerable = list.append("enumerable", Expressions.call(table, SolrMethod.SOLR_QUERYABLE_QUERY.method, - fields, query, orders, buckets, metricPairs, limit, negativeQuery)); + fields, query, orders, buckets, metricPairs, limit, negativeQuery, havingPredicate)); Hook.QUERY_PLAN.run(query); list.add(Expressions.return_(null, enumerable)); return implementor.result(physType, list.toBlock()); diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/AndOperation.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/AndOperation.java index f095f631aaa..bebc7777d26 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/AndOperation.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/AndOperation.java @@ -18,12 +18,12 @@ package org.apache.solr.client.solrj.io.ops; import java.io.IOException; import java.util.List; +import java.util.ArrayList; import java.util.UUID; import org.apache.solr.client.solrj.io.Tuple; import org.apache.solr.client.solrj.io.stream.expr.Explanation; import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType; -import org.apache.solr.client.solrj.io.stream.expr.Expressible; import org.apache.solr.client.solrj.io.stream.expr.StreamExpression; import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter; import org.apache.solr.client.solrj.io.stream.expr.StreamFactory; @@ -33,60 +33,47 @@ public class AndOperation implements BooleanOperation { private static final long serialVersionUID = 1; private UUID operationNodeId = UUID.randomUUID(); - protected BooleanOperation leftOperand; - protected BooleanOperation rightOperand; + private List booleanOperations = new ArrayList(); public void operate(Tuple tuple) { - leftOperand.operate(tuple); - rightOperand.operate(tuple); + for(BooleanOperation booleanOperation : booleanOperations) { + booleanOperation.operate(tuple); + } } - public AndOperation(BooleanOperation leftOperand, BooleanOperation rightOperand) { - this.leftOperand = leftOperand; - this.rightOperand = rightOperand; + public AndOperation(List booleanOperations) { + this.booleanOperations = booleanOperations; } public AndOperation(StreamExpression expression, StreamFactory factory) throws IOException { - List operationExpressions = factory.getExpressionOperandsRepresentingTypes(expression, BooleanOperation.class); - if(operationExpressions != null && operationExpressions.size() == 2) { - StreamExpression left = operationExpressions.get(0); - StreamOperation leftOp = factory.constructOperation(left); - if(leftOp instanceof BooleanOperation) { - leftOperand = (BooleanOperation) leftOp; - } else { - throw new IOException("The And/Or Operation requires a BooleanOperation."); - } - - StreamExpression right = operationExpressions.get(1); - StreamOperation rightOp = factory.constructOperation(right); - if(rightOp instanceof BooleanOperation) { - rightOperand = (BooleanOperation) rightOp; - } else { - throw new IOException("The And/Or Operation requires a BooleanOperation."); - } + List operationExpressions = factory.getExpressionOperandsRepresentingTypes(expression, BooleanOperation.class); + for(StreamExpression se : operationExpressions) { + StreamOperation op = factory.constructOperation(se); + if(op instanceof BooleanOperation) { + booleanOperations.add((BooleanOperation)op); } else { - throw new IOException("The And/Or Operation requires a BooleanOperations."); + throw new IOException("AndOperation requires BooleanOperation parameters"); } + } } public boolean evaluate() { - return leftOperand.evaluate() && rightOperand.evaluate(); + for(BooleanOperation booleanOperation : booleanOperations) { + if(!booleanOperation.evaluate()) { + return false; + } + } + return true; } @Override public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException { StreamExpression expression = new StreamExpression(factory.getFunctionName(this.getClass())); - if(leftOperand instanceof Expressible) { - expression.addParameter(leftOperand.toExpression(factory)); - } else { - throw new IOException("This left operand of the AndOperation contains a non-expressible operation - it cannot be converted to an expression"); + + for(BooleanOperation booleanOperation : booleanOperations) { + expression.addParameter(booleanOperation.toExpression(factory)); } - if(rightOperand instanceof Expressible) { - expression.addParameter(rightOperand.toExpression(factory)); - } else { - throw new IOException("This the right operand of the AndOperation contains a non-expressible operation - it cannot be converted to an expression"); - } return expression; } diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/OrOperation.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/OrOperation.java index faac5cd54ce..2325a589452 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/OrOperation.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/OrOperation.java @@ -17,46 +17,63 @@ package org.apache.solr.client.solrj.io.ops; import java.io.IOException; +import java.util.List; +import java.util.ArrayList; import java.util.UUID; +import org.apache.solr.client.solrj.io.Tuple; import org.apache.solr.client.solrj.io.stream.expr.Explanation; import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType; -import org.apache.solr.client.solrj.io.stream.expr.Expressible; import org.apache.solr.client.solrj.io.stream.expr.StreamExpression; import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter; import org.apache.solr.client.solrj.io.stream.expr.StreamFactory; -public class OrOperation extends AndOperation { +public class OrOperation implements BooleanOperation { private static final long serialVersionUID = 1; private UUID operationNodeId = UUID.randomUUID(); - public OrOperation(BooleanOperation leftOperand, BooleanOperation rightOperand) { - super(leftOperand, rightOperand); + private List booleanOperations = new ArrayList(); + + public void operate(Tuple tuple) { + for(BooleanOperation booleanOperation : booleanOperations) { + booleanOperation.operate(tuple); + } + } + + public OrOperation(List booleanOperations) { + this.booleanOperations = booleanOperations; } public OrOperation(StreamExpression expression, StreamFactory factory) throws IOException { - super(expression, factory); + List operationExpressions = factory.getExpressionOperandsRepresentingTypes(expression, BooleanOperation.class); + for(StreamExpression se : operationExpressions) { + StreamOperation op = factory.constructOperation(se); + if(op instanceof BooleanOperation) { + booleanOperations.add((BooleanOperation)op); + } else { + throw new IOException("AndOperation requires BooleanOperation parameters"); + } + } } public boolean evaluate() { - return leftOperand.evaluate() || rightOperand.evaluate(); + for(BooleanOperation booleanOperation : booleanOperations) { + if(booleanOperation.evaluate()) { + return true; + } + } + return false; } @Override public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException { StreamExpression expression = new StreamExpression(factory.getFunctionName(this.getClass())); - if(leftOperand instanceof Expressible) { - expression.addParameter(leftOperand.toExpression(factory)); - } else { - throw new IOException("This left operand of the OrOperation contains a non-expressible operation - it cannot be converted to an expression"); + + for(BooleanOperation booleanOperation : booleanOperations) { + expression.addParameter(booleanOperation.toExpression(factory)); } - if(rightOperand instanceof Expressible) { - expression.addParameter(rightOperand.toExpression(factory)); - } else { - throw new IOException("This the right operand of the OrOperation contains a non-expressible operation - it cannot be converted to an expression"); - } return expression; }