| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.solr.handler.sql; |
| |
| import org.apache.calcite.adapter.java.JavaTypeFactory; |
| import org.apache.calcite.plan.*; |
| import org.apache.calcite.rel.RelCollations; |
| import org.apache.calcite.rel.RelNode; |
| import org.apache.calcite.rel.convert.ConverterRule; |
| import org.apache.calcite.rel.core.Sort; |
| import org.apache.calcite.rel.logical.LogicalAggregate; |
| import org.apache.calcite.rel.logical.LogicalFilter; |
| import org.apache.calcite.rel.logical.LogicalProject; |
| import org.apache.calcite.rel.logical.LogicalSort; |
| import org.apache.calcite.rel.rules.AggregateValuesRule; |
| import org.apache.calcite.rel.rules.ReduceExpressionsRule; |
| import org.apache.calcite.rel.rules.ValuesReduceRule; |
| import org.apache.calcite.rel.type.RelDataType; |
| import org.apache.calcite.rex.RexCall; |
| import org.apache.calcite.rex.RexInputRef; |
| import org.apache.calcite.rex.RexNode; |
| import org.apache.calcite.rex.RexVisitorImpl; |
| import org.apache.calcite.sql.SqlKind; |
| import org.apache.calcite.sql.validate.SqlValidatorUtil; |
| |
| import java.util.AbstractList; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.function.Predicate; |
| |
| /** |
| * Rules and relational operators for |
| * {@link SolrRel#CONVENTION} |
| * calling convention. |
| */ |
| class SolrRules { |
| static final RelOptRule[] RULES = { |
| SolrSortRule.SORT_RULE, |
| SolrFilterRule.FILTER_RULE, |
| SolrProjectRule.PROJECT_RULE, |
| SolrAggregateRule.AGGREGATE_RULE, |
| }; |
| |
| static final RelOptRule[] CONSTANT_REDUCTION_RULES = { |
| ReduceExpressionsRule.PROJECT_INSTANCE, |
| ReduceExpressionsRule.FILTER_INSTANCE, |
| ReduceExpressionsRule.CALC_INSTANCE, |
| ReduceExpressionsRule.JOIN_INSTANCE, |
| ValuesReduceRule.FILTER_INSTANCE, |
| ValuesReduceRule.PROJECT_FILTER_INSTANCE, |
| ValuesReduceRule.PROJECT_INSTANCE, |
| AggregateValuesRule.INSTANCE |
| }; |
| |
| static List<String> solrFieldNames(final RelDataType rowType) { |
| return SqlValidatorUtil.uniquify( |
| new AbstractList<String>() { |
| @Override |
| public String get(int index) { |
| return rowType.getFieldList().get(index).getName(); |
| } |
| |
| @Override |
| public int size() { |
| return rowType.getFieldCount(); |
| } |
| }, true); |
| } |
| |
| /** Translator from {@link RexNode} to strings in Solr's expression language. */ |
| static class RexToSolrTranslator extends RexVisitorImpl<String> { |
| private final JavaTypeFactory typeFactory; |
| private final List<String> inFields; |
| |
| RexToSolrTranslator(JavaTypeFactory typeFactory, List<String> inFields) { |
| super(true); |
| this.typeFactory = typeFactory; |
| this.inFields = inFields; |
| } |
| |
| @Override |
| public String visitInputRef(RexInputRef inputRef) { |
| return inFields.get(inputRef.getIndex()); |
| } |
| |
| @Override |
| public String visitCall(RexCall call) { |
| final List<String> strings = visitList(call.operands); |
| if (call.getKind() == SqlKind.CAST) { |
| return strings.get(0); |
| } |
| |
| return super.visitCall(call); |
| } |
| |
| private List<String> visitList(List<RexNode> list) { |
| final List<String> strings = new ArrayList<>(); |
| for (RexNode node : list) { |
| strings.add(node.accept(this)); |
| } |
| return strings; |
| } |
| } |
| |
| /** Base class for planner rules that convert a relational expression to Solr calling convention. */ |
| abstract static class SolrConverterRule extends ConverterRule { |
| final Convention out = SolrRel.CONVENTION; |
| |
| SolrConverterRule(Class<? extends RelNode> clazz, String description) { |
| this(clazz, relNode -> true, description); |
| } |
| |
| <R extends RelNode> SolrConverterRule(Class<R> clazz, Predicate<RelNode> predicate, String description) { |
| super(clazz, Convention.NONE, SolrRel.CONVENTION, description); |
| } |
| } |
| |
| /** |
| * Rule to convert a {@link LogicalFilter} to a {@link SolrFilter}. |
| */ |
| private static class SolrFilterRule extends SolrConverterRule { |
| private static boolean isNotFilterByExpr(List<RexNode> rexNodes, List<String> 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); |
| } else if (rexNode instanceof RexInputRef) { |
| result = result && !fieldNames.get(((RexInputRef) rexNode).getIndex()).startsWith("EXPR$"); |
| } |
| } |
| return result; |
| } |
| |
| private static final Predicate<RelNode> FILTER_PREDICATE = relNode -> { |
| List<RexNode> filterOperands = ((RexCall) ((LogicalFilter) relNode).getCondition()).getOperands(); |
| return isNotFilterByExpr(filterOperands, SolrRules.solrFieldNames(relNode.getRowType())); |
| }; |
| |
| private static final SolrFilterRule FILTER_RULE = new SolrFilterRule(); |
| |
| private SolrFilterRule() { |
| super(LogicalFilter.class, FILTER_PREDICATE, "SolrFilterRule"); |
| } |
| |
| public RelNode convert(RelNode rel) { |
| final LogicalFilter filter = (LogicalFilter) rel; |
| final RelTraitSet traitSet = filter.getTraitSet().replace(out); |
| return new SolrFilter( |
| rel.getCluster(), |
| traitSet, |
| convert(filter.getInput(), out), |
| filter.getCondition()); |
| } |
| } |
| |
| /** |
| * Rule to convert a {@link LogicalProject} to a {@link SolrProject}. |
| */ |
| private static class SolrProjectRule extends SolrConverterRule { |
| private static final SolrProjectRule PROJECT_RULE = new SolrProjectRule(); |
| |
| private SolrProjectRule() { |
| super(LogicalProject.class, "SolrProjectRule"); |
| } |
| |
| public RelNode convert(RelNode rel) { |
| final LogicalProject project = (LogicalProject) rel; |
| final RelNode converted = convert(project.getInput(), out); |
| final RelTraitSet traitSet = project.getTraitSet().replace(out); |
| return new SolrProject( |
| rel.getCluster(), |
| traitSet, |
| converted, |
| project.getProjects(), |
| project.getRowType()); |
| } |
| } |
| |
| /** |
| * Rule to convert a {@link LogicalSort} to a {@link SolrSort}. |
| */ |
| private static class SolrSortRule extends SolrConverterRule { |
| static final SolrSortRule SORT_RULE = new SolrSortRule(LogicalSort.class, "SolrSortRule"); |
| |
| SolrSortRule(Class<? extends RelNode> clazz, String description) { |
| super(clazz, description); |
| } |
| |
| @Override |
| public RelNode convert(RelNode rel) { |
| final Sort sort = (Sort) rel; |
| final RelTraitSet traitSet = sort.getTraitSet().replace(out).replace(sort.getCollation()); |
| return new SolrSort( |
| rel.getCluster(), |
| traitSet, |
| convert(sort.getInput(), traitSet.replace(RelCollations.EMPTY)), |
| sort.getCollation(), |
| sort.offset, |
| sort.fetch); |
| } |
| } |
| |
| /** |
| * Rule to convert an {@link LogicalAggregate} to an {@link SolrAggregate}. |
| */ |
| private static class SolrAggregateRule extends SolrConverterRule { |
| // private static final Predicate<RelNode> AGGREGATE_PREDICTE = relNode -> |
| // Aggregate.IS_SIMPLE.apply(((LogicalAggregate)relNode));// && |
| // !((LogicalAggregate)relNode).containsDistinctCall(); |
| |
| private static final RelOptRule AGGREGATE_RULE = new SolrAggregateRule(); |
| |
| private SolrAggregateRule() { |
| super(LogicalAggregate.class, "SolrAggregateRule"); |
| } |
| |
| @Override |
| public RelNode convert(RelNode rel) { |
| final LogicalAggregate agg = (LogicalAggregate) rel; |
| final RelTraitSet traitSet = agg.getTraitSet().replace(out); |
| return new SolrAggregate( |
| rel.getCluster(), |
| traitSet, |
| convert(agg.getInput(), traitSet.simplify()), |
| agg.indicator, |
| agg.getGroupSet(), |
| agg.getGroupSets(), |
| agg.getAggCallList()); |
| } |
| } |
| } |