/* * 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.plan.*; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.core.Filter; import org.apache.calcite.rel.metadata.RelMetadataQuery; import org.apache.calcite.rex.RexCall; import org.apache.calcite.rex.RexInputRef; import org.apache.calcite.rex.RexLiteral; import org.apache.calcite.rex.RexNode; import org.apache.calcite.sql.SqlKind; 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. */ class SolrFilter extends Filter implements SolrRel { SolrFilter( RelOptCluster cluster, RelTraitSet traitSet, RelNode child, RexNode condition) { super(cluster, traitSet, child, condition); assert getConvention() == SolrRel.CONVENTION; assert getConvention() == child.getConvention(); } @Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) { return super.computeSelfCost(planner, mq).multiplyBy(0.1); } public SolrFilter copy(RelTraitSet traitSet, RelNode input, RexNode condition) { return new SolrFilter(getCluster(), traitSet, input, condition); } public void implement(Implementor implementor) { implementor.visitChild(0, getInput()); 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); } } private static class Translator { private final List<String> fieldNames; public boolean negativeQuery = true; Translator(List<String> fieldNames) { this.fieldNames = fieldNames; } 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<String> ors = new ArrayList<>(); for (RexNode node : RelOptUtil.disjunctions(condition)) { ors.add(translateMatch(node)); } return String.join(" OR ", ors); } private String translateAnd(RexNode node0) { List<String> andStrings = new ArrayList(); List<String> notStrings = new ArrayList(); List<RexNode> ands = new ArrayList(); List<RexNode> nots = new ArrayList(); RelOptUtil.decomposeConjunction(node0, ands, nots); for (RexNode node : ands) { andStrings.add(translateMatch(node)); } String andString = String.join(" AND ", andStrings); if (nots.size() > 0) { for (RexNode node : nots) { notStrings.add(translateMatch(node)); } String notString = String.join(" NOT ", notStrings); return "(" + andString + ") NOT (" + notString + ")"; } else { return andString; } } private String translateComparison(RexNode node) { Pair<String, RexLiteral> binaryTranslated = null; if (((RexCall) node).getOperands().size() == 2) { binaryTranslated = translateBinary((RexCall) node); } switch (node.getKind()) { case NOT: return "-" + translateComparison(((RexCall) node).getOperands().get(0)); case EQUALS: String terms = binaryTranslated.getValue().toString().trim(); terms = terms.replace("'",""); 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() + ")"; case LESS_THAN: this.negativeQuery = false; return "(" + binaryTranslated.getKey() + ": [ * TO " + binaryTranslated.getValue() + " })"; case LESS_THAN_OR_EQUAL: this.negativeQuery = false; return "(" + binaryTranslated.getKey() + ": [ * TO " + binaryTranslated.getValue() + " ])"; case GREATER_THAN: this.negativeQuery = false; return "(" + binaryTranslated.getKey() + ": { " + binaryTranslated.getValue() + " TO * ])"; case GREATER_THAN_OR_EQUAL: this.negativeQuery = false; return "(" + binaryTranslated.getKey() + ": [ " + binaryTranslated.getValue() + " TO * ])"; default: throw new AssertionError("cannot translate " + node); } } /** * Translates a call to a binary operator, reversing arguments if necessary. */ private Pair<String, RexLiteral> translateBinary(RexCall call) { List<RexNode> 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<String, RexLiteral> a = translateBinary2(left, right); if (a != null) { return a; } final Pair<String, RexLiteral> 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<String, RexLiteral> 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; } } } private static class HavingTranslator { private final List<String> fieldNames; private Map<String,String> reverseAggMappings; HavingTranslator(List<String> fieldNames, Map<String, String> 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<String> 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<String> andStrings = new ArrayList(); List<String> notStrings = new ArrayList(); List<RexNode> ands = new ArrayList(); List<RexNode> 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<String, RexLiteral> binaryTranslated = null; if (((RexCall) node).getOperands().size() == 2) { binaryTranslated = translateBinary((RexCall) node); } switch (node.getKind()) { case EQUALS: String terms = binaryTranslated.getValue().toString().trim(); String clause = "eq(" + binaryTranslated.getKey() + "," + terms + ")"; return clause; case NOT_EQUALS: return "not(eq(" + binaryTranslated.getKey() + "," + binaryTranslated.getValue() + "))"; case LESS_THAN: return "lt(" + binaryTranslated.getKey() + "," + binaryTranslated.getValue() + ")"; case LESS_THAN_OR_EQUAL: return "lteq(" + binaryTranslated.getKey() + "," + binaryTranslated.getValue() + ")"; case GREATER_THAN: return "gt(" + binaryTranslated.getKey() + "," + binaryTranslated.getValue() + ")"; case GREATER_THAN_OR_EQUAL: return "gteq(" + binaryTranslated.getKey() + "," + binaryTranslated.getValue() + ")"; default: throw new AssertionError("cannot translate " + node); } } /** * Translates a call to a binary operator, reversing arguments if necessary. */ private Pair<String, RexLiteral> translateBinary(RexCall call) { List<RexNode> 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<String, RexLiteral> a = translateBinary2(left, right); if (a != null) { if(reverseAggMappings.containsKey(a.getKey())) { return new Pair<String, RexLiteral>(reverseAggMappings.get(a.getKey()),a.getValue()); } return a; } final Pair<String, RexLiteral> 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<String, RexLiteral> 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; } } } }