/* * 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 com.google.common.collect.Lists; import org.apache.calcite.adapter.enumerable.*; import org.apache.calcite.linq4j.tree.BlockBuilder; import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.linq4j.tree.Expressions; import org.apache.calcite.linq4j.tree.MethodCallExpression; import org.apache.calcite.plan.*; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.convert.ConverterImpl; import org.apache.calcite.rel.metadata.RelMetadataQuery; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.runtime.Hook; import org.apache.calcite.util.BuiltInMethod; import org.apache.calcite.util.Pair; import java.util.AbstractList; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Relational expression representing a scan of a table in Solr */ class SolrToEnumerableConverter extends ConverterImpl implements EnumerableRel { SolrToEnumerableConverter(RelOptCluster cluster, RelTraitSet traits, RelNode input) { super(cluster, ConventionTraitDef.INSTANCE, traits, input); } @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) { return new SolrToEnumerableConverter(getCluster(), traitSet, sole(inputs)); } @Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) { return super.computeSelfCost(planner, mq).multiplyBy(.1); } public Result implement(EnumerableRelImplementor implementor, Prefer pref) { // Generates a call to "query" with the appropriate fields final BlockBuilder list = new BlockBuilder(); final SolrRel.Implementor solrImplementor = new SolrRel.Implementor(); solrImplementor.visitChild(0, getInput()); final RelDataType rowType = getRowType(); final PhysType physType = PhysTypeImpl.of(implementor.getTypeFactory(), rowType, pref.prefer(JavaRowFormat.ARRAY)); final Expression table = list.append("table", solrImplementor.table.getExpression(SolrTable.SolrQueryable.class)); final Expression fields = list.append("fields", constantArrayList( Pair.zip(generateFields(SolrRules.solrFieldNames(rowType), solrImplementor.fieldMappings), new AbstractList<Class>() { @Override public Class get(int index) { return physType.fieldClass(index); } @Override public int size() { return rowType.getFieldCount(); } }), Pair.class)); final Expression query = list.append("query", Expressions.constant(solrImplementor.query, String.class)); final Expression orders = list.append("orders", constantArrayList(solrImplementor.orders, Pair.class)); final Expression buckets = list.append("buckets", constantArrayList(solrImplementor.buckets, String.class)); 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, havingPredicate)); Hook.QUERY_PLAN.run(query); list.add(Expressions.return_(null, enumerable)); return implementor.result(physType, list.toBlock()); } private List<String> generateFields(List<String> queryFields, Map<String, String> fieldMappings) { if(fieldMappings.isEmpty()) { return queryFields; } else { List<String> fields = new ArrayList<>(); for(String field : queryFields) { fields.add(getField(fieldMappings, field)); } return fields; } } private String getField(Map<String, String> fieldMappings, String field) { String retField = field; while(fieldMappings.containsKey(field)) { field = fieldMappings.getOrDefault(field, retField); if(retField.equals(field)) { break; } else { retField = field; } } return retField; } /** * E.g. {@code constantArrayList("x", "y")} returns * "Arrays.asList('x', 'y')". */ private static <T> MethodCallExpression constantArrayList(List<T> values, Class clazz) { return Expressions.call(BuiltInMethod.ARRAYS_AS_LIST.method, Expressions.newArrayInit(clazz, constantList(values))); } /** * E.g. {@code constantList("x", "y")} returns "{ConstantExpression("x"), ConstantExpression("y")}". */ private static <T> List<Expression> constantList(List<T> values) { return Lists.transform(values, Expressions::constant); } }