[CALCITE-1967] Elasticsearch 5 adapter (Christian Beikov) Move code that is independent of Elasticsearch into a org.apache.calcite.adapter.elasticsearch package under core. It provides abstract classes that the elasticsearch2 and elasticsearch5 modules make concrete.
Close apache/calcite#528 Project: http://git-wip-us.apache.org/repos/asf/calcite/repo Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/e1525926 Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/e1525926 Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/e1525926 Branch: refs/heads/branch-1.14 Commit: e1525926790b058ae1845db32dcbf59b80ed270e Parents: 2156d82 Author: Christian Beikov <[email protected]> Authored: Thu Aug 24 16:28:50 2017 +0200 Committer: Julian Hyde <[email protected]> Committed: Tue Aug 29 10:12:29 2017 -0700 ---------------------------------------------------------------------- .../AbstractElasticsearchTable.java | 128 ++++++++ .../elasticsearch/ElasticsearchFilter.java | 297 +++++++++++++++++++ .../elasticsearch/ElasticsearchMethod.java | 50 ++++ .../elasticsearch/ElasticsearchProject.java | 98 ++++++ .../adapter/elasticsearch/ElasticsearchRel.java | 58 ++++ .../elasticsearch/ElasticsearchRules.java | 238 +++++++++++++++ .../elasticsearch/ElasticsearchSchema.java | 33 +++ .../elasticsearch/ElasticsearchSort.java | 104 +++++++ .../elasticsearch/ElasticsearchTableScan.java | 90 ++++++ .../ElasticsearchToEnumerableConverter.java | 124 ++++++++ .../ElasticsearchToEnumerableConverterRule.java | 42 +++ .../MapProjectionFieldVisitor.java | 42 +++ .../adapter/elasticsearch/package-info.java | 26 ++ elasticsearch/pom.xml | 148 --------- .../elasticsearch/ElasticsearchEnumerator.java | 151 ---------- .../elasticsearch/ElasticsearchFilter.java | 287 ------------------ .../elasticsearch/ElasticsearchMethod.java | 50 ---- .../elasticsearch/ElasticsearchProject.java | 95 ------ .../adapter/elasticsearch/ElasticsearchRel.java | 58 ---- .../elasticsearch/ElasticsearchRules.java | 236 --------------- .../elasticsearch/ElasticsearchSchema.java | 127 -------- .../ElasticsearchSchemaFactory.java | 63 ---- .../elasticsearch/ElasticsearchSort.java | 93 ------ .../elasticsearch/ElasticsearchTable.java | 152 ---------- .../elasticsearch/ElasticsearchTableScan.java | 88 ------ .../ElasticsearchToEnumerableConverter.java | 124 -------- .../ElasticsearchToEnumerableConverterRule.java | 42 --- .../adapter/elasticsearch/package-info.java | 26 -- .../calcite/test/ElasticsearchAdapterIT.java | 270 ----------------- .../resources/elasticsearch-zips-model.json | 50 ---- .../src/test/resources/log4j.properties | 24 -- elasticsearch2/pom.xml | 148 +++++++++ .../Elasticsearch2Enumerator.java | 152 ++++++++++ .../elasticsearch2/Elasticsearch2Schema.java | 134 +++++++++ .../Elasticsearch2SchemaFactory.java | 63 ++++ .../elasticsearch2/Elasticsearch2Table.java | 66 +++++ .../adapter/elasticsearch2/package-info.java | 26 ++ .../calcite/test/Elasticsearch2AdapterIT.java | 270 +++++++++++++++++ .../resources/elasticsearch-zips-model.json | 50 ++++ .../src/test/resources/log4j.properties | 24 ++ elasticsearch5/pom.xml | 153 ++++++++++ .../Elasticsearch5Enumerator.java | 154 ++++++++++ .../elasticsearch5/Elasticsearch5Schema.java | 138 +++++++++ .../Elasticsearch5SchemaFactory.java | 63 ++++ .../elasticsearch5/Elasticsearch5Table.java | 86 ++++++ .../adapter/elasticsearch5/package-info.java | 26 ++ .../calcite/test/Elasticsearch5AdapterIT.java | 270 +++++++++++++++++ .../resources/elasticsearch-zips-model.json | 50 ++++ .../src/test/resources/log4j.properties | 24 ++ pom.xml | 4 +- site/_docs/adapter.md | 5 +- site/_docs/elasticsearch_adapter.md | 5 +- sqlline | 2 +- sqlline.bat | 2 +- 54 files changed, 3240 insertions(+), 2089 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/core/src/main/java/org/apache/calcite/adapter/elasticsearch/AbstractElasticsearchTable.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/elasticsearch/AbstractElasticsearchTable.java b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/AbstractElasticsearchTable.java new file mode 100644 index 0000000..0980469 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/AbstractElasticsearchTable.java @@ -0,0 +1,128 @@ +/* + * 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.calcite.adapter.elasticsearch; + +import org.apache.calcite.adapter.java.AbstractQueryableTable; +import org.apache.calcite.linq4j.Enumerable; +import org.apache.calcite.linq4j.Enumerator; +import org.apache.calcite.linq4j.QueryProvider; +import org.apache.calcite.linq4j.Queryable; +import org.apache.calcite.plan.RelOptCluster; +import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.schema.SchemaPlus; +import org.apache.calcite.schema.TranslatableTable; +import org.apache.calcite.schema.impl.AbstractTableQueryable; +import org.apache.calcite.sql.type.SqlTypeName; + +import java.util.List; +import java.util.Map; + +/** + * Table based on an Elasticsearch type. + */ +public abstract class AbstractElasticsearchTable extends AbstractQueryableTable + implements TranslatableTable { + protected final String indexName; + protected final String typeName; + + /** + * Creates an ElasticsearchTable. + */ + public AbstractElasticsearchTable(String indexName, String typeName) { + super(Object[].class); + this.indexName = indexName; + this.typeName = typeName; + } + + @Override public String toString() { + return "ElasticsearchTable{" + typeName + "}"; + } + + public RelDataType getRowType(RelDataTypeFactory relDataTypeFactory) { + final RelDataType mapType = relDataTypeFactory.createMapType( + relDataTypeFactory.createSqlType(SqlTypeName.VARCHAR), + relDataTypeFactory.createTypeWithNullability( + relDataTypeFactory.createSqlType(SqlTypeName.ANY), + true)); + return relDataTypeFactory.builder().add("_MAP", mapType).build(); + } + + public <T> Queryable<T> asQueryable(QueryProvider queryProvider, SchemaPlus schema, + String tableName) { + return new ElasticsearchQueryable<>(queryProvider, schema, this, tableName); + } + + public RelNode toRel(RelOptTable.ToRelContext context, RelOptTable relOptTable) { + final RelOptCluster cluster = context.getCluster(); + return new ElasticsearchTableScan(cluster, cluster.traitSetOf(ElasticsearchRel.CONVENTION), + relOptTable, this, null); + } + + /** Executes a "find" operation on the underlying type. + * + * <p>For example, + * <code>client.prepareSearch(index).setTypes(type) + * .setSource("{\"fields\" : [\"state\"]}")</code></p> + * + * @param index Elasticsearch index + * @param ops List of operations represented as Json strings. + * @param fields List of fields to project; or null to return map + * @return Enumerator of results + */ + protected abstract Enumerable<Object> find(String index, List<String> ops, + List<Map.Entry<String, Class>> fields); + + /** + * Implementation of {@link Queryable} based on + * a {@link AbstractElasticsearchTable}. + * + * @param <T> element type + */ + public static class ElasticsearchQueryable<T> extends AbstractTableQueryable<T> { + public ElasticsearchQueryable(QueryProvider queryProvider, SchemaPlus schema, + AbstractElasticsearchTable table, String tableName) { + super(queryProvider, schema, table, tableName); + } + + public Enumerator<T> enumerator() { + return null; + } + + private String getIndex() { + return schema.unwrap(ElasticsearchSchema.class).getIndex(); + } + + private AbstractElasticsearchTable getTable() { + return (AbstractElasticsearchTable) table; + } + + /** Called via code-generation. + * + * @see ElasticsearchMethod#ELASTICSEARCH_QUERYABLE_FIND + */ + @SuppressWarnings("UnusedDeclaration") + public Enumerable<Object> find(List<String> ops, + List<Map.Entry<String, Class>> fields) { + return getTable().find(getIndex(), ops, fields); + } + } +} + +// End AbstractElasticsearchTable.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchFilter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchFilter.java b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchFilter.java new file mode 100644 index 0000000..c1bc317 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchFilter.java @@ -0,0 +1,297 @@ +/* + * 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.calcite.adapter.elasticsearch; + +import org.apache.calcite.plan.RelOptCluster; +import org.apache.calcite.plan.RelOptCost; +import org.apache.calcite.plan.RelOptPlanner; +import org.apache.calcite.plan.RelOptUtil; +import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.core.Filter; +import org.apache.calcite.rel.core.Project; +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.util.JsonBuilder; +import org.apache.calcite.util.Pair; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + + +/** + * Implementation of a {@link org.apache.calcite.rel.core.Filter} + * relational expression in Elasticsearch. + */ +public class ElasticsearchFilter extends Filter implements ElasticsearchRel { + public ElasticsearchFilter(RelOptCluster cluster, RelTraitSet traitSet, RelNode child, + RexNode condition) { + super(cluster, traitSet, child, condition); + assert getConvention() == ElasticsearchRel.CONVENTION; + assert getConvention() == child.getConvention(); + } + + @Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) { + return super.computeSelfCost(planner, mq).multiplyBy(0.1); + } + + @Override public Filter copy(RelTraitSet relTraitSet, RelNode input, RexNode condition) { + return new ElasticsearchFilter(getCluster(), relTraitSet, input, condition); + } + + @Override public void implement(Implementor implementor) { + implementor.visitChild(0, getInput()); + List<String> fieldNames; + if (input instanceof Project) { + final List<RexNode> projects = ((Project) input).getProjects(); + fieldNames = new ArrayList<>(projects.size()); + for (RexNode project : projects) { + String name = project.accept(MapProjectionFieldVisitor.INSTANCE); + fieldNames.add(name); + } + } else { + fieldNames = ElasticsearchRules.elasticsearchFieldNames(getRowType()); + } + Translator translator = new Translator(fieldNames); + String match = translator.translateMatch(condition); + implementor.add(match); + } + + /** + * Translates {@link RexNode} expressions into Elasticsearch expression strings. + */ + static class Translator { + final JsonBuilder builder = new JsonBuilder(); + final Multimap<String, Pair<String, RexLiteral>> multimap = + HashMultimap.create(); + final Map<String, RexLiteral> eqMap = new LinkedHashMap<>(); + private final List<String> fieldNames; + + Translator(List<String> fieldNames) { + this.fieldNames = fieldNames; + } + + private String translateMatch(RexNode condition) { + // filter node + final Map<String, Object> filterMap = new LinkedHashMap<>(); + filterMap.put("filter", translateOr(condition)); + + // constant_score node + final Map<String, Object> map = builder.map(); + map.put("constant_score", filterMap); + + return "\"query\" : " + builder.toJsonString(map).replaceAll("\\s+", "") + .toLowerCase(Locale.ROOT); + } + + private Object translateOr(RexNode condition) { + final List<Object> list = new ArrayList<>(); + + final List<RexNode> orNodes = RelOptUtil.disjunctions(condition); + for (RexNode node : orNodes) { + List<Map<String, Object>> andNodes = translateAnd(node); + + if (andNodes.size() > 0) { + Map<String, Object> andClause = new HashMap<>(); + andClause.put("must", andNodes); + + // boolean filters + LinkedHashMap<String, Object> filterEvaluator = new LinkedHashMap<>(); + filterEvaluator.put("bool", andClause); + list.add(filterEvaluator); + } else { + list.add(andNodes.get(0)); + } + } + + if (orNodes.size() > 1) { + Map<String, Object> map = builder.map(); + map.put("should", list); + + // boolean filters + LinkedHashMap<String, Object> filterEvaluator = new LinkedHashMap<>(); + filterEvaluator.put("bool", map); + return filterEvaluator; + } else { + return list.get(0); + } + } + + private void addPredicate(Map<String, Object> map, String op, Object v) { + if (map.containsKey(op) && stronger(op, map.get(op), v)) { + return; + } + map.put(op, v); + } + + /** + * Translates a condition that may be an AND of other conditions. Gathers + * together conditions that apply to the same field. + */ + private List<Map<String, Object>> translateAnd(RexNode node0) { + eqMap.clear(); + multimap.clear(); + for (RexNode node : RelOptUtil.conjunctions(node0)) { + translateMatch2(node); + } + List<Map<String, Object>> filters = new ArrayList<>(); + for (Map.Entry<String, RexLiteral> entry : eqMap.entrySet()) { + multimap.removeAll(entry.getKey()); + + Map<String, Object> filter = new HashMap<>(); + filter.put(entry.getKey(), literalValue(entry.getValue())); + + Map<String, Object> map = new HashMap<>(); + map.put("term", filter); + filters.add(map); + } + for (Map.Entry<String, Collection<Pair<String, RexLiteral>>> entry + : multimap.asMap().entrySet()) { + Map<String, Object> map2 = builder.map(); + + Map<String, Object> map = new HashMap<>(); + for (Pair<String, RexLiteral> s : entry.getValue()) { + if (!s.left.equals("not")) { + addPredicate(map2, s.left, literalValue(s.right)); + + Map<String, Object> filter = new HashMap<>(); + filter.put(entry.getKey(), map2); + + map.put("range", filter); + } else { + map2.put(entry.getKey(), literalValue(s.right)); + + Map<String, Object> termMap = new HashMap<>(); + termMap.put("term", map2); + + map.put("not", termMap); + } + } + filters.add(map); + } + return filters; + } + + private boolean stronger(String key, Object v0, Object v1) { + if (key.equals("lt") || key.equals("lte")) { + if (v0 instanceof Number && v1 instanceof Number) { + return ((Number) v0).doubleValue() < ((Number) v1).doubleValue(); + } + if (v0 instanceof String && v1 instanceof String) { + return v0.toString().compareTo(v1.toString()) < 0; + } + } + if (key.equals("gt") || key.equals("gte")) { + return stronger("lt", v1, v0); + } + return false; + } + + private static Object literalValue(RexLiteral literal) { + return literal.getValue2(); + } + + private Void translateMatch2(RexNode node) { + switch (node.getKind()) { + case EQUALS: + return translateBinary(null, null, (RexCall) node); + case LESS_THAN: + return translateBinary("lt", "gt", (RexCall) node); + case LESS_THAN_OR_EQUAL: + return translateBinary("lte", "gte", (RexCall) node); + case NOT_EQUALS: + return translateBinary("not", "not", (RexCall) node); + case GREATER_THAN: + return translateBinary("gt", "lt", (RexCall) node); + case GREATER_THAN_OR_EQUAL: + return translateBinary("gte", "lte", (RexCall) node); + default: + throw new AssertionError("cannot translate " + node); + } + } + + /** + * Translates a call to a binary operator, reversing arguments if + * necessary. + */ + private Void translateBinary(String op, String rop, RexCall call) { + final RexNode left = call.operands.get(0); + final RexNode right = call.operands.get(1); + boolean b = translateBinary2(op, left, right); + if (b) { + return null; + } + b = translateBinary2(rop, right, left); + if (b) { + return null; + } + throw new AssertionError("cannot translate op " + op + " call " + call); + } + + /** + * Translates a call to a binary operator. Returns whether successful. + */ + private boolean translateBinary2(String op, RexNode left, RexNode right) { + switch (right.getKind()) { + case LITERAL: + break; + default: + return false; + } + final RexLiteral rightLiteral = (RexLiteral) right; + switch (left.getKind()) { + case INPUT_REF: + final RexInputRef left1 = (RexInputRef) left; + String name = fieldNames.get(left1.getIndex()); + translateOp2(op, name, rightLiteral); + return true; + case CAST: + return translateBinary2(op, ((RexCall) left).operands.get(0), right); + case OTHER_FUNCTION: + String itemName = ElasticsearchRules.isItem((RexCall) left); + if (itemName != null) { + translateOp2(op, itemName, rightLiteral); + return true; + } + // fall through + default: + return false; + } + } + + private void translateOp2(String op, String name, RexLiteral right) { + if (op == null) { + eqMap.put(name, right); + } else { + multimap.put(name, Pair.of(op, right)); + } + } + } +} + +// End ElasticsearchFilter.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchMethod.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchMethod.java b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchMethod.java new file mode 100644 index 0000000..72753e6 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchMethod.java @@ -0,0 +1,50 @@ +/* + * 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.calcite.adapter.elasticsearch; + +import org.apache.calcite.linq4j.tree.Types; + +import com.google.common.collect.ImmutableMap; + +import java.lang.reflect.Method; +import java.util.List; + +/** + * Builtin methods in the Elasticsearch adapter. + */ +enum ElasticsearchMethod { + ELASTICSEARCH_QUERYABLE_FIND(AbstractElasticsearchTable.ElasticsearchQueryable.class, + "find", List.class, List.class); + + public final Method method; + + public static final ImmutableMap<Method, ElasticsearchMethod> MAP; + + static { + final ImmutableMap.Builder<Method, ElasticsearchMethod> builder = ImmutableMap.builder(); + for (ElasticsearchMethod value: ElasticsearchMethod.values()) { + builder.put(value.method, value); + } + MAP = builder.build(); + } + + ElasticsearchMethod(Class clazz, String methodName, Class... argumentTypes) { + this.method = Types.lookupMethod(clazz, methodName, argumentTypes); + } +} + +// End ElasticsearchMethod.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchProject.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchProject.java b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchProject.java new file mode 100644 index 0000000..b42abd7 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchProject.java @@ -0,0 +1,98 @@ +/* + * 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.calcite.adapter.elasticsearch; + +import org.apache.calcite.adapter.java.JavaTypeFactory; +import org.apache.calcite.plan.RelOptCluster; +import org.apache.calcite.plan.RelOptCost; +import org.apache.calcite.plan.RelOptPlanner; +import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.core.Project; +import org.apache.calcite.rel.metadata.RelMetadataQuery; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.util.Pair; +import org.apache.calcite.util.Util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of {@link org.apache.calcite.rel.core.Project} + * relational expression in Elasticsearch. + */ +public class ElasticsearchProject extends Project implements ElasticsearchRel { + public ElasticsearchProject(RelOptCluster cluster, RelTraitSet traitSet, RelNode input, + List<? extends RexNode> projects, RelDataType rowType) { + super(cluster, traitSet, input, projects, rowType); + assert getConvention() == ElasticsearchRel.CONVENTION; + assert getConvention() == input.getConvention(); + } + + @Override public Project copy(RelTraitSet relTraitSet, RelNode input, List<RexNode> projects, + RelDataType relDataType) { + return new ElasticsearchProject(getCluster(), traitSet, input, projects, relDataType); + } + + @Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) { + return super.computeSelfCost(planner, mq).multiplyBy(0.1); + } + + @Override public void implement(Implementor implementor) { + implementor.visitChild(0, getInput()); + + final List<String> inFields = + ElasticsearchRules.elasticsearchFieldNames(getInput().getRowType()); + final ElasticsearchRules.RexToElasticsearchTranslator translator = + new ElasticsearchRules.RexToElasticsearchTranslator( + (JavaTypeFactory) getCluster().getTypeFactory(), inFields); + + final List<String> findItems = new ArrayList<>(); + final List<String> scriptFieldItems = new ArrayList<>(); + for (Pair<RexNode, String> pair: getNamedProjects()) { + final String name = pair.right; + final String expr = pair.left.accept(translator); + + if (expr.equals("\"" + name + "\"")) { + findItems.add(ElasticsearchRules.quote(name)); + } else if (expr.matches("\"literal\":.+")) { + scriptFieldItems.add(ElasticsearchRules.quote(name) + + ":{\"script\": " + + expr.split(":")[1] + "}"); + } else { + scriptFieldItems.add(ElasticsearchRules.quote(name) + + ":{\"script\":\"params._source." + + expr.replaceAll("\"", "") + "\"}"); + } + } + final String findString = Util.toString(findItems, "", ", ", ""); + final String scriptFieldString = "\"script_fields\": {" + + Util.toString(scriptFieldItems, "", ", ", "") + "}"; + final String fieldString = "\"_source\" : [" + findString + "]" + + ", " + scriptFieldString; + + for (String opfield : implementor.list) { + if (opfield.startsWith("\"_source\"")) { + implementor.list.remove(opfield); + } + } + implementor.add(fieldString); + } +} + +// End ElasticsearchProject.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchRel.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchRel.java b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchRel.java new file mode 100644 index 0000000..436adf9 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchRel.java @@ -0,0 +1,58 @@ +/* + * 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.calcite.adapter.elasticsearch; + +import org.apache.calcite.plan.Convention; +import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.rel.RelNode; + +import java.util.ArrayList; +import java.util.List; + +/** + * Relational expression that uses Elasticsearch calling convention. + */ +public interface ElasticsearchRel extends RelNode { + void implement(Implementor implementor); + + /** + * Calling convention for relational operations that occur in Elasticsearch. + */ + Convention CONVENTION = new Convention.Impl("ELASTICSEARCH", ElasticsearchRel.class); + + /** + * Callback for the implementation process that converts a tree of + * {@link ElasticsearchRel} nodes into an Elasticsearch query. + */ + class Implementor { + final List<String> list = new ArrayList<>(); + + RelOptTable table; + AbstractElasticsearchTable elasticsearchTable; + + public void add(String findOp) { + list.add(findOp); + } + + public void visitChild(int ordinal, RelNode input) { + assert ordinal == 0; + ((ElasticsearchRel) input).implement(this); + } + } +} + +// End ElasticsearchRel.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchRules.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchRules.java b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchRules.java new file mode 100644 index 0000000..c4f6728 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchRules.java @@ -0,0 +1,238 @@ +/* + * 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.calcite.adapter.elasticsearch; + +import org.apache.calcite.adapter.enumerable.RexImpTable; +import org.apache.calcite.adapter.enumerable.RexToLixTranslator; +import org.apache.calcite.adapter.java.JavaTypeFactory; +import org.apache.calcite.plan.Convention; +import org.apache.calcite.plan.RelOptRule; +import org.apache.calcite.plan.RelTrait; +import org.apache.calcite.plan.RelTraitSet; +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.LogicalFilter; +import org.apache.calcite.rel.logical.LogicalProject; +import org.apache.calcite.rel.type.RelDataType; +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.rex.RexVisitorImpl; +import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.sql.validate.SqlValidatorUtil; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.List; + +/** + * Rules and relational operators for + * {@link ElasticsearchRel#CONVENTION ELASTICSEARCH} + * calling convention. + */ +class ElasticsearchRules { + static final RelOptRule[] RULES = { + ElasticsearchSortRule.INSTANCE, + ElasticsearchFilterRule.INSTANCE, + ElasticsearchProjectRule.INSTANCE + }; + + private ElasticsearchRules() {} + + /** + * Returns 'string' if it is a call to item['string'], null otherwise. + */ + static String isItem(RexCall call) { + if (call.getOperator() != SqlStdOperatorTable.ITEM) { + return null; + } + final RexNode op0 = call.getOperands().get(0); + final RexNode op1 = call.getOperands().get(1); + + if (op0 instanceof RexInputRef + && ((RexInputRef) op0).getIndex() == 0 + && op1 instanceof RexLiteral + && ((RexLiteral) op1).getValue2() instanceof String) { + return (String) ((RexLiteral) op1).getValue2(); + } + return null; + } + + static List<String> elasticsearchFieldNames(final RelDataType rowType) { + return SqlValidatorUtil.uniquify( + new AbstractList<String>() { + @Override public String get(int index) { + final String name = rowType.getFieldList().get(index).getName(); + return name.startsWith("$") ? "_" + name.substring(2) : name; + } + + @Override public int size() { + return rowType.getFieldCount(); + } + }, + SqlValidatorUtil.EXPR_SUGGESTER, true); + } + + static String quote(String s) { + return "\"" + s + "\""; + } + + /** + * Translator from {@link RexNode} to strings in Elasticsearch's expression + * language. + */ + static class RexToElasticsearchTranslator extends RexVisitorImpl<String> { + private final JavaTypeFactory typeFactory; + private final List<String> inFields; + + RexToElasticsearchTranslator(JavaTypeFactory typeFactory, List<String> inFields) { + super(true); + this.typeFactory = typeFactory; + this.inFields = inFields; + } + + @Override public String visitLiteral(RexLiteral literal) { + if (literal.getValue() == null) { + return "null"; + } + return "\"literal\":\"" + + RexToLixTranslator.translateLiteral(literal, literal.getType(), + typeFactory, RexImpTable.NullAs.NOT_POSSIBLE) + + "\""; + } + + @Override public String visitInputRef(RexInputRef inputRef) { + return quote(inFields.get(inputRef.getIndex())); + } + + @Override public String visitCall(RexCall call) { + final String name = isItem(call); + if (name != null) { + return "\"" + name + "\""; + } + + final List<String> strings = visitList(call.operands); + if (call.getKind() == SqlKind.CAST) { + return strings.get(0).startsWith("$") ? strings.get(0).substring(1) : strings.get(0); + } + if (call.getOperator() == SqlStdOperatorTable.ITEM) { + final RexNode op1 = call.getOperands().get(1); + if (op1 instanceof RexLiteral && op1.getType().getSqlTypeName() == SqlTypeName.INTEGER) { + return stripQuotes(strings.get(0)) + "[" + ((RexLiteral) op1).getValue2() + "]"; + } + } + throw new IllegalArgumentException("Translation of " + call.toString() + + "is not supported by ElasticsearchProject"); + } + + private String stripQuotes(String s) { + return s.startsWith("'") && s.endsWith("'") ? s.substring(1, s.length() - 1) : s; + } + + 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 + * Elasticsearch calling convention. + */ + abstract static class ElasticsearchConverterRule extends ConverterRule { + final Convention out; + + ElasticsearchConverterRule(Class<? extends RelNode> clazz, RelTrait in, Convention out, + String description) { + super(clazz, in, out, description); + this.out = out; + } + } + + /** + * Rule to convert a {@link org.apache.calcite.rel.core.Sort} to an + * {@link ElasticsearchSort}. + */ + private static class ElasticsearchSortRule extends ElasticsearchConverterRule { + private static final ElasticsearchSortRule INSTANCE = + new ElasticsearchSortRule(); + + private ElasticsearchSortRule() { + super(Sort.class, Convention.NONE, ElasticsearchRel.CONVENTION, + "ElasticsearchSortRule"); + } + + @Override public RelNode convert(RelNode relNode) { + final Sort sort = (Sort) relNode; + final RelTraitSet traitSet = sort.getTraitSet().replace(out).replace(sort.getCollation()); + return new ElasticsearchSort(relNode.getCluster(), traitSet, + convert(sort.getInput(), traitSet.replace(RelCollations.EMPTY)), sort.getCollation(), + sort.offset, sort.fetch); + } + } + + /** + * Rule to convert a {@link org.apache.calcite.rel.logical.LogicalFilter} to an + * {@link ElasticsearchFilter}. + */ + private static class ElasticsearchFilterRule extends ElasticsearchConverterRule { + private static final ElasticsearchFilterRule INSTANCE = new ElasticsearchFilterRule(); + + private ElasticsearchFilterRule() { + super(LogicalFilter.class, Convention.NONE, ElasticsearchRel.CONVENTION, + "ElasticsearchFilterRule"); + } + + @Override public RelNode convert(RelNode relNode) { + final LogicalFilter filter = (LogicalFilter) relNode; + final RelTraitSet traitSet = filter.getTraitSet().replace(out); + return new ElasticsearchFilter(relNode.getCluster(), traitSet, + convert(filter.getInput(), out), + filter.getCondition()); + } + } + + /** + * Rule to convert a {@link org.apache.calcite.rel.logical.LogicalProject} + * to an {@link ElasticsearchProject}. + */ + private static class ElasticsearchProjectRule extends ElasticsearchConverterRule { + private static final ElasticsearchProjectRule INSTANCE = new ElasticsearchProjectRule(); + + private ElasticsearchProjectRule() { + super(LogicalProject.class, Convention.NONE, ElasticsearchRel.CONVENTION, + "ElasticsearchProjectRule"); + } + + @Override public RelNode convert(RelNode relNode) { + final LogicalProject project = (LogicalProject) relNode; + final RelTraitSet traitSet = project.getTraitSet().replace(out); + return new ElasticsearchProject(project.getCluster(), traitSet, + convert(project.getInput(), out), project.getProjects(), project.getRowType()); + } + } +} + +// End ElasticsearchRules.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchSchema.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchSchema.java b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchSchema.java new file mode 100644 index 0000000..7247d19 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchSchema.java @@ -0,0 +1,33 @@ +/* + * 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.calcite.adapter.elasticsearch; + +import org.apache.calcite.schema.Schema; + +/** + * Gives access to some basic information of the Elasticsearch schema. + */ +public interface ElasticsearchSchema extends Schema { + /** + * The name of the Elasticsearch index. + * + * @return The index name + */ + String getIndex(); +} + +// End ElasticsearchSchema.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchSort.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchSort.java b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchSort.java new file mode 100644 index 0000000..851e51b --- /dev/null +++ b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchSort.java @@ -0,0 +1,104 @@ +/* + * 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.calcite.adapter.elasticsearch; + +import org.apache.calcite.plan.RelOptCluster; +import org.apache.calcite.plan.RelOptCost; +import org.apache.calcite.plan.RelOptPlanner; +import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.rel.RelCollation; +import org.apache.calcite.rel.RelFieldCollation; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.core.Project; +import org.apache.calcite.rel.core.Sort; +import org.apache.calcite.rel.metadata.RelMetadataQuery; +import org.apache.calcite.rel.type.RelDataTypeField; +import org.apache.calcite.rex.RexLiteral; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.util.Util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of {@link org.apache.calcite.rel.core.Sort} + * relational expression in Elasticsearch. + */ +public class ElasticsearchSort extends Sort implements ElasticsearchRel { + public ElasticsearchSort(RelOptCluster cluster, RelTraitSet traitSet, RelNode child, + RelCollation collation, RexNode offset, RexNode fetch) { + super(cluster, traitSet, child, collation, offset, fetch); + assert getConvention() == ElasticsearchRel.CONVENTION; + assert getConvention() == child.getConvention(); + } + + @Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) { + return super.computeSelfCost(planner, mq).multiplyBy(0.05); + } + + @Override public Sort copy(RelTraitSet traitSet, RelNode relNode, RelCollation relCollation, + RexNode offset, RexNode fetch) { + return new ElasticsearchSort(getCluster(), traitSet, relNode, collation, offset, fetch); + } + + @Override public void implement(Implementor implementor) { + implementor.visitChild(0, getInput()); + if (!collation.getFieldCollations().isEmpty()) { + final List<String> keys = new ArrayList<>(); + if (input instanceof Project) { + final List<RexNode> projects = ((Project) input).getProjects(); + + for (RelFieldCollation fieldCollation : collation.getFieldCollations()) { + RexNode project = projects.get(fieldCollation.getFieldIndex()); + String name = project.accept(MapProjectionFieldVisitor.INSTANCE); + keys.add(ElasticsearchRules.quote(name) + ": " + direction(fieldCollation)); + } + } else { + final List<RelDataTypeField> fields = getRowType().getFieldList(); + + for (RelFieldCollation fieldCollation : collation.getFieldCollations()) { + final String name = fields.get(fieldCollation.getFieldIndex()).getName(); + keys.add(ElasticsearchRules.quote(name) + ": " + direction(fieldCollation)); + } + } + + implementor.add("\"sort\": [ " + Util.toString(keys, "{", "}, {", "}") + "]"); + } + + if (offset != null) { + implementor.add("\"from\": " + ((RexLiteral) offset).getValue()); + } + + if (fetch != null) { + implementor.add("\"size\": " + ((RexLiteral) fetch).getValue()); + } + } + + private String direction(RelFieldCollation fieldCollation) { + switch (fieldCollation.getDirection()) { + case DESCENDING: + case STRICTLY_DESCENDING: + return "\"desc\""; + case ASCENDING: + case STRICTLY_ASCENDING: + default: + return "\"asc\""; + } + } +} + +// End ElasticsearchSort.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchTableScan.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchTableScan.java b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchTableScan.java new file mode 100644 index 0000000..33ca14f --- /dev/null +++ b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchTableScan.java @@ -0,0 +1,90 @@ +/* + * 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.calcite.adapter.elasticsearch; + +import org.apache.calcite.plan.RelOptCluster; +import org.apache.calcite.plan.RelOptCost; +import org.apache.calcite.plan.RelOptPlanner; +import org.apache.calcite.plan.RelOptRule; +import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.core.TableScan; +import org.apache.calcite.rel.metadata.RelMetadataQuery; +import org.apache.calcite.rel.type.RelDataType; + +import com.google.common.base.Preconditions; + +import java.util.List; + +/** + * Relational expression representing a scan of an Elasticsearch type. + * + * <p> Additional operations might be applied, + * using the "find" method.</p> + */ +public class ElasticsearchTableScan extends TableScan implements ElasticsearchRel { + private final AbstractElasticsearchTable elasticsearchTable; + private final RelDataType projectRowType; + + /** + * Creates an ElasticsearchTableScan. + * + * @param cluster Cluster + * @param traitSet Trait set + * @param table Table + * @param elasticsearchTable Elasticsearch table + * @param projectRowType Fields and types to project; null to project raw row + */ + protected ElasticsearchTableScan(RelOptCluster cluster, RelTraitSet traitSet, + RelOptTable table, AbstractElasticsearchTable elasticsearchTable, + RelDataType projectRowType) { + super(cluster, traitSet, table); + this.elasticsearchTable = Preconditions.checkNotNull(elasticsearchTable); + this.projectRowType = projectRowType; + + assert getConvention() == ElasticsearchRel.CONVENTION; + } + + @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) { + assert inputs.isEmpty(); + return this; + } + + @Override public RelDataType deriveRowType() { + return projectRowType != null ? projectRowType : super.deriveRowType(); + } + + @Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) { + final float f = projectRowType == null ? 1f : (float) projectRowType.getFieldCount() / 100f; + return super.computeSelfCost(planner, mq).multiplyBy(.1 * f); + } + + @Override public void register(RelOptPlanner planner) { + planner.addRule(ElasticsearchToEnumerableConverterRule.INSTANCE); + for (RelOptRule rule: ElasticsearchRules.RULES) { + planner.addRule(rule); + } + } + + @Override public void implement(Implementor implementor) { + implementor.elasticsearchTable = elasticsearchTable; + implementor.table = table; + } +} + +// End ElasticsearchTableScan.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchToEnumerableConverter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchToEnumerableConverter.java b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchToEnumerableConverter.java new file mode 100644 index 0000000..768f4e5 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchToEnumerableConverter.java @@ -0,0 +1,124 @@ +/* + * 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.calcite.adapter.elasticsearch; + +import org.apache.calcite.adapter.enumerable.EnumerableRel; +import org.apache.calcite.adapter.enumerable.EnumerableRelImplementor; +import org.apache.calcite.adapter.enumerable.JavaRowFormat; +import org.apache.calcite.adapter.enumerable.PhysType; +import org.apache.calcite.adapter.enumerable.PhysTypeImpl; + +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.ConventionTraitDef; +import org.apache.calcite.plan.RelOptCluster; +import org.apache.calcite.plan.RelOptCost; +import org.apache.calcite.plan.RelOptPlanner; +import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.prepare.CalcitePrepareImpl; +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 com.google.common.base.Function; +import com.google.common.collect.Lists; + +import java.util.AbstractList; +import java.util.List; +import javax.annotation.Nullable; + +/** + * Relational expression representing a scan of a table in an Elasticsearch data source. + */ +public class ElasticsearchToEnumerableConverter extends ConverterImpl implements EnumerableRel { + protected ElasticsearchToEnumerableConverter(RelOptCluster cluster, RelTraitSet traits, + RelNode input) { + super(cluster, ConventionTraitDef.INSTANCE, traits, input); + } + + @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) { + return new ElasticsearchToEnumerableConverter(getCluster(), traitSet, sole(inputs)); + } + + @Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) { + return super.computeSelfCost(planner, mq).multiplyBy(.1); + } + + @Override public Result implement(EnumerableRelImplementor implementor, Prefer prefer) { + final BlockBuilder list = new BlockBuilder(); + final ElasticsearchRel.Implementor elasticsearchImplementor = + new ElasticsearchRel.Implementor(); + elasticsearchImplementor.visitChild(0, getInput()); + final RelDataType rowType = getRowType(); + final PhysType physType = PhysTypeImpl.of(implementor.getTypeFactory(), rowType, + prefer.prefer(JavaRowFormat.ARRAY)); + final Expression fields = list.append("fields", + constantArrayList( + Pair.zip(ElasticsearchRules.elasticsearchFieldNames(rowType), + new AbstractList<Class>() { + @Override public Class get(int index) { + return physType.fieldClass(index); + } + + @Override public int size() { + return rowType.getFieldCount(); + } + }), + Pair.class)); + final Expression table = list.append("table", + elasticsearchImplementor.table + .getExpression(AbstractElasticsearchTable.ElasticsearchQueryable.class)); + List<String> opList = elasticsearchImplementor.list; + final Expression ops = list.append("ops", constantArrayList(opList, String.class)); + Expression enumerable = list.append("enumerable", + Expressions.call(table, ElasticsearchMethod.ELASTICSEARCH_QUERYABLE_FIND.method, ops, + fields)); + if (CalcitePrepareImpl.DEBUG) { + System.out.println("Elasticsearch: " + opList); + } + Hook.QUERY_PLAN.run(opList); + list.add(Expressions.return_(null, enumerable)); + return implementor.result(physType, list.toBlock()); + } + + /** 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 + * {@code {ConstantExpression("x"), ConstantExpression("y")}}. */ + private static <T> List<Expression> constantList(List<T> values) { + return Lists.transform(values, + new Function<T, Expression>() { + @Nullable + @Override public Expression apply(@Nullable T t) { + return Expressions.constant(t); + } + }); + } +} + +// End ElasticsearchToEnumerableConverter.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchToEnumerableConverterRule.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchToEnumerableConverterRule.java b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchToEnumerableConverterRule.java new file mode 100644 index 0000000..1047757 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchToEnumerableConverterRule.java @@ -0,0 +1,42 @@ +/* + * 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.calcite.adapter.elasticsearch; + +import org.apache.calcite.adapter.enumerable.EnumerableConvention; +import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.convert.ConverterRule; + +/** + * Rule to convert a relational expression from + * {@link ElasticsearchRel#CONVENTION} to {@link EnumerableConvention}. + */ +public class ElasticsearchToEnumerableConverterRule extends ConverterRule { + public static final ConverterRule INSTANCE = new ElasticsearchToEnumerableConverterRule(); + + private ElasticsearchToEnumerableConverterRule() { + super(RelNode.class, ElasticsearchRel.CONVENTION, EnumerableConvention.INSTANCE, + "ElasticsearchToEnumerableConverterRule"); + } + + @Override public RelNode convert(RelNode relNode) { + RelTraitSet newTraitSet = relNode.getTraitSet().replace(getOutConvention()); + return new ElasticsearchToEnumerableConverter(relNode.getCluster(), newTraitSet, relNode); + } +} + +// End ElasticsearchToEnumerableConverterRule.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/core/src/main/java/org/apache/calcite/adapter/elasticsearch/MapProjectionFieldVisitor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/elasticsearch/MapProjectionFieldVisitor.java b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/MapProjectionFieldVisitor.java new file mode 100644 index 0000000..bcb99a7 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/MapProjectionFieldVisitor.java @@ -0,0 +1,42 @@ +/* + * 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.calcite.adapter.elasticsearch; + +import org.apache.calcite.rex.RexCall; +import org.apache.calcite.rex.RexLiteral; +import org.apache.calcite.rex.RexVisitorImpl; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; + +/** + * Visitor that extracts the actual field name from an item expression. + */ +public class MapProjectionFieldVisitor extends RexVisitorImpl<String> { + public static final MapProjectionFieldVisitor INSTANCE = new MapProjectionFieldVisitor(); + + private MapProjectionFieldVisitor() { + super(true); + } + + @Override public String visitCall(RexCall call) { + if (call.op == SqlStdOperatorTable.ITEM) { + return ((RexLiteral) call.getOperands().get(1)).getValueAs(String.class); + } + return super.visitCall(call); + } +} + +// End MapProjectionFieldVisitor.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/core/src/main/java/org/apache/calcite/adapter/elasticsearch/package-info.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/elasticsearch/package-info.java b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/package-info.java new file mode 100644 index 0000000..98e1a92 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/adapter/elasticsearch/package-info.java @@ -0,0 +1,26 @@ +/* + * 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. + */ + +/** + * Base classes for a query provider based on an Elasticsearch DB. + */ +@PackageMarker +package org.apache.calcite.adapter.elasticsearch; + +import org.apache.calcite.avatica.util.PackageMarker; + +// End package-info.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/elasticsearch/pom.xml ---------------------------------------------------------------------- diff --git a/elasticsearch/pom.xml b/elasticsearch/pom.xml deleted file mode 100644 index aee216a..0000000 --- a/elasticsearch/pom.xml +++ /dev/null @@ -1,148 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -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. ---> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - - <parent> - <groupId>org.apache.calcite</groupId> - <artifactId>calcite</artifactId> - <version>1.14.0-SNAPSHOT</version> - </parent> - - <artifactId>calcite-elasticsearch</artifactId> - <packaging>jar</packaging> - <version>1.14.0-SNAPSHOT</version> - <name>Calcite Elasticsearch</name> - <description>Elasticsearch adapter for Calcite</description> - - <properties> - <top.dir>${project.basedir}/..</top.dir> - </properties> - - <dependencies> - <dependency> - <groupId>com.google.guava</groupId> - <artifactId>guava</artifactId> - </dependency> - <dependency> - <groupId>org.apache.calcite</groupId> - <artifactId>calcite-core</artifactId> - <type>jar</type> - </dependency> - <dependency> - <groupId>org.apache.calcite</groupId> - <artifactId>calcite-core</artifactId> - <type>test-jar</type> - <scope>test</scope> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-core</artifactId> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-databind</artifactId> - </dependency> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.apache.calcite</groupId> - <artifactId>calcite-linq4j</artifactId> - </dependency> - <dependency> - <groupId>org.elasticsearch</groupId> - <artifactId>elasticsearch</artifactId> - <version>${elasticsearch-java-driver.version}</version> - </dependency> - <dependency> - <groupId>com.carrotsearch</groupId> - <artifactId>hppc</artifactId> - <version>${hppc.version}</version> - </dependency> - <dependency> - <groupId>com.google.code.findbugs</groupId> - <artifactId>jsr305</artifactId> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-api</artifactId> - </dependency> - </dependencies> - - <build> - <plugins> - <plugin> - <artifactId>maven-dependency-plugin</artifactId> - <version>${maven-dependency-plugin.version}</version> - <executions> - <execution> - <id>analyze</id> - <goals> - <goal>analyze-only</goal> - </goals> - <configuration> - <failOnWarning>true</failOnWarning> - <!-- ignore "unused but declared" warnings --> - <ignoredUnusedDeclaredDependencies> - <ignoredUnusedDeclaredDependency>org.apache.calcite.avatica:avatica</ignoredUnusedDeclaredDependency> - <ignoredUnusedDeclaredDependency>org.slf4j:slf4j-api</ignoredUnusedDeclaredDependency> - <ignoredUnusedDeclaredDependency>org.slf4j:slf4j-log4j12</ignoredUnusedDeclaredDependency> - </ignoredUnusedDeclaredDependencies> - </configuration> - </execution> - </executions> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <executions> - <execution> - <goals> - <goal>test-jar</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-release-plugin</artifactId> - </plugin> - <!-- Parent module has the same plugin and does the work of - generating -sources.jar for each project. But without the - plugin declared here, IDEs don't know the sources are - available. --> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-source-plugin</artifactId> - <executions> - <execution> - <id>attach-sources</id> - <phase>verify</phase> - <goals> - <goal>jar-no-fork</goal> - <goal>test-jar-no-fork</goal> - </goals> - </execution> - </executions> - </plugin> - </plugins> - </build> -</project> http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchEnumerator.java ---------------------------------------------------------------------- diff --git a/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchEnumerator.java b/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchEnumerator.java deleted file mode 100644 index e7478f5..0000000 --- a/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchEnumerator.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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.calcite.adapter.elasticsearch; - -import org.apache.calcite.avatica.util.DateTimeUtils; -import org.apache.calcite.linq4j.Enumerator; -import org.apache.calcite.linq4j.function.Function1; -import org.apache.calcite.linq4j.tree.Primitive; - -import org.elasticsearch.search.SearchHit; - -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * Enumerator that reads from an Elasticsearch type. - */ -public class ElasticsearchEnumerator implements Enumerator<Object> { - private final Iterator<SearchHit> cursor; - private final Function1<SearchHit, Object> getter; - private Object current; - - /** - * Creates an ElasticsearchEnumerator. - * - * @param cursor Iterator over Elasticsearch {@link SearchHit} objects - * @param getter Converts an object into a list of fields - */ - public ElasticsearchEnumerator(Iterator<SearchHit> cursor, Function1<SearchHit, Object> getter) { - this.cursor = cursor; - this.getter = getter; - } - - public Object current() { - return current; - } - - public boolean moveNext() { - if (cursor.hasNext()) { - SearchHit map = cursor.next(); - current = getter.apply(map); - return true; - } else { - current = null; - return false; - } - } - - public void reset() { - throw new UnsupportedOperationException(); - } - - public void close() { - // nothing to do - } - - private static Function1<SearchHit, Map> mapGetter() { - return new Function1<SearchHit, Map>() { - public Map apply(SearchHit searchHitFields) { - return (Map) searchHitFields.fields(); - } - }; - } - - private static Function1<SearchHit, Object> singletonGetter(final String fieldName, - final Class fieldClass) { - return new Function1<SearchHit, Object>() { - public Object apply(SearchHit searchHitFields) { - if (searchHitFields.fields().isEmpty()) { - return convert(searchHitFields.getSource(), fieldClass); - } else { - return convert(searchHitFields.getFields(), fieldClass); - } - } - }; - } - - /** - * Function that extracts a given set of fields from {@link SearchHit} - * objects. - * - * @param fields List of fields to project - */ - private static Function1<SearchHit, Object[]> listGetter( - final List<Map.Entry<String, Class>> fields) { - return new Function1<SearchHit, Object[]>() { - public Object[] apply(SearchHit searchHitFields) { - Object[] objects = new Object[fields.size()]; - for (int i = 0; i < fields.size(); i++) { - final Map.Entry<String, Class> field = fields.get(i); - final String name = field.getKey(); - if (searchHitFields.fields().isEmpty()) { - objects[i] = convert(searchHitFields.getSource().get(name), field.getValue()); - } else { - objects[i] = convert(searchHitFields.field(name).getValue(), field.getValue()); - } - } - return objects; - } - }; - } - - static Function1<SearchHit, Object> getter(List<Map.Entry<String, Class>> fields) { - //noinspection unchecked - return fields == null - ? (Function1) mapGetter() - : fields.size() == 1 - ? singletonGetter(fields.get(0).getKey(), fields.get(0).getValue()) - : (Function1) listGetter(fields); - } - - private static Object convert(Object o, Class clazz) { - if (o == null) { - return null; - } - Primitive primitive = Primitive.of(clazz); - if (primitive != null) { - clazz = primitive.boxClass; - } else { - primitive = Primitive.ofBox(clazz); - } - if (clazz.isInstance(o)) { - return o; - } - if (o instanceof Date && primitive != null) { - o = ((Date) o).getTime() / DateTimeUtils.MILLIS_PER_DAY; - } - if (o instanceof Number && primitive != null) { - return primitive.number((Number) o); - } - return o; - } -} - -// End ElasticsearchEnumerator.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchFilter.java ---------------------------------------------------------------------- diff --git a/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchFilter.java b/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchFilter.java deleted file mode 100644 index 8e923a1..0000000 --- a/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchFilter.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * 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.calcite.adapter.elasticsearch; - -import org.apache.calcite.plan.RelOptCluster; -import org.apache.calcite.plan.RelOptCost; -import org.apache.calcite.plan.RelOptPlanner; -import org.apache.calcite.plan.RelOptUtil; -import org.apache.calcite.plan.RelTraitSet; -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.util.JsonBuilder; -import org.apache.calcite.util.Pair; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - - -/** - * Implementation of a {@link org.apache.calcite.rel.core.Filter} - * relational expression in Elasticsearch. - */ -public class ElasticsearchFilter extends Filter implements ElasticsearchRel { - public ElasticsearchFilter(RelOptCluster cluster, RelTraitSet traitSet, RelNode child, - RexNode condition) { - super(cluster, traitSet, child, condition); - assert getConvention() == ElasticsearchRel.CONVENTION; - assert getConvention() == child.getConvention(); - } - - @Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) { - return super.computeSelfCost(planner, mq).multiplyBy(0.1); - } - - @Override public Filter copy(RelTraitSet relTraitSet, RelNode input, RexNode condition) { - return new ElasticsearchFilter(getCluster(), relTraitSet, input, condition); - } - - @Override public void implement(Implementor implementor) { - implementor.visitChild(0, getInput()); - final List<String> fieldNames = - ElasticsearchRules.elasticsearchFieldNames(getRowType()); - final Translator translator = new Translator(fieldNames); - final String match = translator.translateMatch(condition); - implementor.add(match); - } - - /** - * Translates {@link RexNode} expressions into Elasticsearch expression strings. - */ - static class Translator { - final JsonBuilder builder = new JsonBuilder(); - final Multimap<String, Pair<String, RexLiteral>> multimap = - HashMultimap.create(); - final Map<String, RexLiteral> eqMap = new LinkedHashMap<>(); - private final List<String> fieldNames; - - Translator(List<String> fieldNames) { - this.fieldNames = fieldNames; - } - - private String translateMatch(RexNode condition) { - // filter node - final Map<String, Object> filterMap = new LinkedHashMap<>(); - filterMap.put("filter", translateOr(condition)); - - // constant_score node - final Map<String, Object> map = builder.map(); - map.put("constant_score", filterMap); - - return "\"query\" : " + builder.toJsonString(map).replaceAll("\\s+", "") - .toLowerCase(Locale.ROOT); - } - - private Object translateOr(RexNode condition) { - final List<Object> list = new ArrayList<>(); - - final List<RexNode> orNodes = RelOptUtil.disjunctions(condition); - for (RexNode node : orNodes) { - List<Map<String, Object>> andNodes = translateAnd(node); - - if (andNodes.size() > 0) { - Map<String, Object> andClause = new HashMap<>(); - andClause.put("must", andNodes); - - // boolean filters - LinkedHashMap<String, Object> filterEvaluator = new LinkedHashMap<>(); - filterEvaluator.put("bool", andClause); - list.add(filterEvaluator); - } else { - list.add(andNodes.get(0)); - } - } - - if (orNodes.size() > 1) { - Map<String, Object> map = builder.map(); - map.put("should", list); - - // boolean filters - LinkedHashMap<String, Object> filterEvaluator = new LinkedHashMap<>(); - filterEvaluator.put("bool", map); - return filterEvaluator; - } else { - return list.get(0); - } - } - - private void addPredicate(Map<String, Object> map, String op, Object v) { - if (map.containsKey(op) && stronger(op, map.get(op), v)) { - return; - } - map.put(op, v); - } - - /** - * Translates a condition that may be an AND of other conditions. Gathers - * together conditions that apply to the same field. - */ - private List<Map<String, Object>> translateAnd(RexNode node0) { - eqMap.clear(); - multimap.clear(); - for (RexNode node : RelOptUtil.conjunctions(node0)) { - translateMatch2(node); - } - List<Map<String, Object>> filters = new ArrayList<>(); - for (Map.Entry<String, RexLiteral> entry : eqMap.entrySet()) { - multimap.removeAll(entry.getKey()); - - Map<String, Object> filter = new HashMap<>(); - filter.put(entry.getKey(), literalValue(entry.getValue())); - - Map<String, Object> map = new HashMap<>(); - map.put("term", filter); - filters.add(map); - } - for (Map.Entry<String, Collection<Pair<String, RexLiteral>>> entry - : multimap.asMap().entrySet()) { - Map<String, Object> map2 = builder.map(); - - Map<String, Object> map = new HashMap<>(); - for (Pair<String, RexLiteral> s : entry.getValue()) { - if (!s.left.equals("not")) { - addPredicate(map2, s.left, literalValue(s.right)); - - Map<String, Object> filter = new HashMap<>(); - filter.put(entry.getKey(), map2); - - map.put("range", filter); - } else { - map2.put(entry.getKey(), literalValue(s.right)); - - Map<String, Object> termMap = new HashMap<>(); - termMap.put("term", map2); - - map.put("not", termMap); - } - } - filters.add(map); - } - return filters; - } - - private boolean stronger(String key, Object v0, Object v1) { - if (key.equals("lt") || key.equals("lte")) { - if (v0 instanceof Number && v1 instanceof Number) { - return ((Number) v0).doubleValue() < ((Number) v1).doubleValue(); - } - if (v0 instanceof String && v1 instanceof String) { - return v0.toString().compareTo(v1.toString()) < 0; - } - } - if (key.equals("gt") || key.equals("gte")) { - return stronger("lt", v1, v0); - } - return false; - } - - private static Object literalValue(RexLiteral literal) { - return literal.getValue2(); - } - - private Void translateMatch2(RexNode node) { - switch (node.getKind()) { - case EQUALS: - return translateBinary(null, null, (RexCall) node); - case LESS_THAN: - return translateBinary("lt", "gt", (RexCall) node); - case LESS_THAN_OR_EQUAL: - return translateBinary("lte", "gte", (RexCall) node); - case NOT_EQUALS: - return translateBinary("not", "not", (RexCall) node); - case GREATER_THAN: - return translateBinary("gt", "lt", (RexCall) node); - case GREATER_THAN_OR_EQUAL: - return translateBinary("gte", "lte", (RexCall) node); - default: - throw new AssertionError("cannot translate " + node); - } - } - - /** - * Translates a call to a binary operator, reversing arguments if - * necessary. - */ - private Void translateBinary(String op, String rop, RexCall call) { - final RexNode left = call.operands.get(0); - final RexNode right = call.operands.get(1); - boolean b = translateBinary2(op, left, right); - if (b) { - return null; - } - b = translateBinary2(rop, right, left); - if (b) { - return null; - } - throw new AssertionError("cannot translate op " + op + " call " + call); - } - - /** - * Translates a call to a binary operator. Returns whether successful. - */ - private boolean translateBinary2(String op, RexNode left, RexNode right) { - switch (right.getKind()) { - case LITERAL: - break; - default: - return false; - } - final RexLiteral rightLiteral = (RexLiteral) right; - switch (left.getKind()) { - case INPUT_REF: - final RexInputRef left1 = (RexInputRef) left; - String name = fieldNames.get(left1.getIndex()); - translateOp2(op, name, rightLiteral); - return true; - case CAST: - return translateBinary2(op, ((RexCall) left).operands.get(0), right); - case OTHER_FUNCTION: - String itemName = ElasticsearchRules.isItem((RexCall) left); - if (itemName != null) { - translateOp2(op, itemName, rightLiteral); - return true; - } - // fall through - default: - return false; - } - } - - private void translateOp2(String op, String name, RexLiteral right) { - if (op == null) { - eqMap.put(name, right); - } else { - multimap.put(name, Pair.of(op, right)); - } - } - } -} - -// End ElasticsearchFilter.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e1525926/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchMethod.java ---------------------------------------------------------------------- diff --git a/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchMethod.java b/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchMethod.java deleted file mode 100644 index a0b3af6..0000000 --- a/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchMethod.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.calcite.adapter.elasticsearch; - -import org.apache.calcite.linq4j.tree.Types; - -import com.google.common.collect.ImmutableMap; - -import java.lang.reflect.Method; -import java.util.List; - -/** - * Builtin methods in the Elasticsearch adapter. - */ -enum ElasticsearchMethod { - ELASTICSEARCH_QUERYABLE_FIND(ElasticsearchTable.ElasticsearchQueryable.class, "find", - List.class, List.class); - - public final Method method; - - public static final ImmutableMap<Method, ElasticsearchMethod> MAP; - - static { - final ImmutableMap.Builder<Method, ElasticsearchMethod> builder = ImmutableMap.builder(); - for (ElasticsearchMethod value: ElasticsearchMethod.values()) { - builder.put(value.method, value); - } - MAP = builder.build(); - } - - ElasticsearchMethod(Class clazz, String methodName, Class... argumentTypes) { - this.method = Types.lookupMethod(clazz, methodName, argumentTypes); - } -} - -// End ElasticsearchMethod.java
