This is an automated email from the ASF dual-hosted git repository. kangkaisen pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-doris.git
The following commit(s) were added to refs/heads/master by this push: new acd7a58 [Doris On ES] [1/3] Add ES QueryBuilders for debug mode (#3774) acd7a58 is described below commit acd7a5887588a7b438dbb478ececad99a08682b0 Author: Yunfeng,Wu <wuyunfen...@baidu.com> AuthorDate: Tue Jun 9 16:45:16 2020 +0800 [Doris On ES] [1/3] Add ES QueryBuilders for debug mode (#3774) --- .../org/apache/doris/analysis/CreateTableStmt.java | 2 +- .../java/org/apache/doris/catalog/Catalog.java | 2 +- .../java/org/apache/doris/catalog/EsTable.java | 7 +- .../doris/common/proc/EsPartitionsProcDir.java | 2 +- .../apache/doris/common/proc/EsShardProcDir.java | 4 +- .../external/{ => elasticsearch}/EsIndexState.java | 2 +- .../{ => elasticsearch}/EsMajorVersion.java | 2 +- .../external/{ => elasticsearch}/EsNodeInfo.java | 2 +- .../external/{ => elasticsearch}/EsRestClient.java | 18 +- .../{ => elasticsearch}/EsShardRouting.java | 2 +- .../external/{ => elasticsearch}/EsStateStore.java | 10 +- .../external/{ => elasticsearch}/EsTableState.java | 2 +- .../doris/external/{ => elasticsearch}/EsUtil.java | 2 +- .../ExternalDataSourceException.java | 2 +- .../external/elasticsearch/QueryBuilders.java | 459 +++++++++++++++++++++ .../java/org/apache/doris/planner/EsScanNode.java | 6 +- .../java/org/apache/doris/es/EsStateStoreTest.java | 254 ------------ .../{es => external/elasticsearch}/EsUtilTest.java | 4 +- .../external/elasticsearch/QueryBuildersTest.java | 173 ++++++++ 19 files changed, 670 insertions(+), 285 deletions(-) diff --git a/fe/src/main/java/org/apache/doris/analysis/CreateTableStmt.java b/fe/src/main/java/org/apache/doris/analysis/CreateTableStmt.java index 7a87055..27d10e1 100644 --- a/fe/src/main/java/org/apache/doris/analysis/CreateTableStmt.java +++ b/fe/src/main/java/org/apache/doris/analysis/CreateTableStmt.java @@ -33,7 +33,7 @@ import org.apache.doris.common.FeConstants; import org.apache.doris.common.FeNameFormat; import org.apache.doris.common.UserException; import org.apache.doris.common.util.PrintableMap; -import org.apache.doris.external.EsUtil; +import org.apache.doris.external.elasticsearch.EsUtil; import org.apache.doris.mysql.privilege.PrivPredicate; import org.apache.doris.qe.ConnectContext; diff --git a/fe/src/main/java/org/apache/doris/catalog/Catalog.java b/fe/src/main/java/org/apache/doris/catalog/Catalog.java index 43fc47f..37b70c5 100755 --- a/fe/src/main/java/org/apache/doris/catalog/Catalog.java +++ b/fe/src/main/java/org/apache/doris/catalog/Catalog.java @@ -127,7 +127,7 @@ import org.apache.doris.deploy.DeployManager; import org.apache.doris.deploy.impl.AmbariDeployManager; import org.apache.doris.deploy.impl.K8sDeployManager; import org.apache.doris.deploy.impl.LocalFileDeployManager; -import org.apache.doris.external.EsStateStore; +import org.apache.doris.external.elasticsearch.EsStateStore; import org.apache.doris.ha.BDBHA; import org.apache.doris.ha.FrontendNodeType; import org.apache.doris.ha.HAProtocol; diff --git a/fe/src/main/java/org/apache/doris/catalog/EsTable.java b/fe/src/main/java/org/apache/doris/catalog/EsTable.java index a8baaa0..c7d5e57 100644 --- a/fe/src/main/java/org/apache/doris/catalog/EsTable.java +++ b/fe/src/main/java/org/apache/doris/catalog/EsTable.java @@ -20,14 +20,17 @@ package org.apache.doris.catalog; import org.apache.doris.common.DdlException; import org.apache.doris.common.FeMetaVersion; import org.apache.doris.common.io.Text; -import org.apache.doris.external.EsMajorVersion; -import org.apache.doris.external.EsTableState; +import org.apache.doris.external.elasticsearch.EsMajorVersion; +import org.apache.doris.external.elasticsearch.EsTableState; import org.apache.doris.thrift.TEsTable; import org.apache.doris.thrift.TTableDescriptor; import org.apache.doris.thrift.TTableType; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; + import com.google.common.base.Strings; + import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; diff --git a/fe/src/main/java/org/apache/doris/common/proc/EsPartitionsProcDir.java b/fe/src/main/java/org/apache/doris/common/proc/EsPartitionsProcDir.java index 7d9359f..896201e 100644 --- a/fe/src/main/java/org/apache/doris/common/proc/EsPartitionsProcDir.java +++ b/fe/src/main/java/org/apache/doris/common/proc/EsPartitionsProcDir.java @@ -24,7 +24,7 @@ import org.apache.doris.catalog.PartitionType; import org.apache.doris.catalog.RangePartitionInfo; import org.apache.doris.catalog.Table.TableType; import org.apache.doris.common.AnalysisException; -import org.apache.doris.external.EsIndexState; +import org.apache.doris.external.elasticsearch.EsIndexState; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; diff --git a/fe/src/main/java/org/apache/doris/common/proc/EsShardProcDir.java b/fe/src/main/java/org/apache/doris/common/proc/EsShardProcDir.java index 51574a2..4cba64e 100644 --- a/fe/src/main/java/org/apache/doris/common/proc/EsShardProcDir.java +++ b/fe/src/main/java/org/apache/doris/common/proc/EsShardProcDir.java @@ -25,8 +25,8 @@ import org.apache.doris.catalog.Database; import org.apache.doris.catalog.EsTable; import org.apache.doris.common.AnalysisException; import org.apache.doris.common.util.ListComparator; -import org.apache.doris.external.EsIndexState; -import org.apache.doris.external.EsShardRouting; +import org.apache.doris.external.elasticsearch.EsIndexState; +import org.apache.doris.external.elasticsearch.EsShardRouting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; diff --git a/fe/src/main/java/org/apache/doris/external/EsIndexState.java b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsIndexState.java similarity index 99% rename from fe/src/main/java/org/apache/doris/external/EsIndexState.java rename to fe/src/main/java/org/apache/doris/external/elasticsearch/EsIndexState.java index 5dca5db..d2d0e90 100644 --- a/fe/src/main/java/org/apache/doris/external/EsIndexState.java +++ b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsIndexState.java @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.external; +package org.apache.doris.external.elasticsearch; import org.apache.doris.analysis.PartitionKeyDesc; import org.apache.doris.analysis.PartitionValue; diff --git a/fe/src/main/java/org/apache/doris/external/EsMajorVersion.java b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsMajorVersion.java similarity index 98% rename from fe/src/main/java/org/apache/doris/external/EsMajorVersion.java rename to fe/src/main/java/org/apache/doris/external/elasticsearch/EsMajorVersion.java index b71db8a..2319f58 100644 --- a/fe/src/main/java/org/apache/doris/external/EsMajorVersion.java +++ b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsMajorVersion.java @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.external; +package org.apache.doris.external.elasticsearch; /** diff --git a/fe/src/main/java/org/apache/doris/external/EsNodeInfo.java b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsNodeInfo.java similarity index 99% rename from fe/src/main/java/org/apache/doris/external/EsNodeInfo.java rename to fe/src/main/java/org/apache/doris/external/elasticsearch/EsNodeInfo.java index 61b513b..4e11f9d 100644 --- a/fe/src/main/java/org/apache/doris/external/EsNodeInfo.java +++ b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsNodeInfo.java @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.external; +package org.apache.doris.external.elasticsearch; import org.apache.doris.thrift.TNetworkAddress; diff --git a/fe/src/main/java/org/apache/doris/external/EsRestClient.java b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsRestClient.java similarity index 95% rename from fe/src/main/java/org/apache/doris/external/EsRestClient.java rename to fe/src/main/java/org/apache/doris/external/elasticsearch/EsRestClient.java index 2b93ea9..83cbc0c 100644 --- a/fe/src/main/java/org/apache/doris/external/EsRestClient.java +++ b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsRestClient.java @@ -15,12 +15,8 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.external; +package org.apache.doris.external.elasticsearch; -import okhttp3.Credentials; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.util.Strings; @@ -35,6 +31,11 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; +import okhttp3.Credentials; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + public class EsRestClient { private static final Logger LOG = LogManager.getLogger(EsRestClient.class); private ObjectMapper mapper; @@ -142,14 +143,19 @@ public class EsRestClient { Request request = builder.get() .url(currentNode + "/" + path) .build(); + Response response = null; try { - Response response = networkClient.newCall(request).execute(); + response = networkClient.newCall(request).execute(); if (response.isSuccessful()) { return response.body().string(); } } catch (IOException e) { LOG.warn("request node [{}] [{}] failures {}, try next nodes", currentNode, path, e); scratchExceptionForThrow = e; + } finally { + if (response != null) { + response.close(); + } } nextNode = selectNextNode(); if (!nextNode) { diff --git a/fe/src/main/java/org/apache/doris/external/EsShardRouting.java b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsShardRouting.java similarity index 98% rename from fe/src/main/java/org/apache/doris/external/EsShardRouting.java rename to fe/src/main/java/org/apache/doris/external/elasticsearch/EsShardRouting.java index 12afa5f..c4474ae 100644 --- a/fe/src/main/java/org/apache/doris/external/EsShardRouting.java +++ b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsShardRouting.java @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.external; +package org.apache.doris.external.elasticsearch; import org.apache.commons.lang.StringUtils; diff --git a/fe/src/main/java/org/apache/doris/external/EsStateStore.java b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsStateStore.java similarity index 99% rename from fe/src/main/java/org/apache/doris/external/EsStateStore.java rename to fe/src/main/java/org/apache/doris/external/elasticsearch/EsStateStore.java index 460cc70..d22bb70 100644 --- a/fe/src/main/java/org/apache/doris/external/EsStateStore.java +++ b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsStateStore.java @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.external; +package org.apache.doris.external.elasticsearch; import org.apache.doris.catalog.Catalog; import org.apache.doris.catalog.Column; @@ -32,14 +32,14 @@ import org.apache.doris.common.Config; import org.apache.doris.common.DdlException; import org.apache.doris.common.util.MasterDaemon; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Maps; -import com.google.common.collect.Range; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.json.JSONObject; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Maps; +import com.google.common.collect.Range; + import java.util.Collections; import java.util.Comparator; import java.util.List; diff --git a/fe/src/main/java/org/apache/doris/external/EsTableState.java b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsTableState.java similarity index 98% rename from fe/src/main/java/org/apache/doris/external/EsTableState.java rename to fe/src/main/java/org/apache/doris/external/elasticsearch/EsTableState.java index 59b69aa..2b546a6 100644 --- a/fe/src/main/java/org/apache/doris/external/EsTableState.java +++ b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsTableState.java @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.external; +package org.apache.doris.external.elasticsearch; import java.util.Map; import java.util.Random; diff --git a/fe/src/main/java/org/apache/doris/external/EsUtil.java b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsUtil.java similarity index 98% rename from fe/src/main/java/org/apache/doris/external/EsUtil.java rename to fe/src/main/java/org/apache/doris/external/elasticsearch/EsUtil.java index 4e05d0e..754f1ee 100644 --- a/fe/src/main/java/org/apache/doris/external/EsUtil.java +++ b/fe/src/main/java/org/apache/doris/external/elasticsearch/EsUtil.java @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.external; +package org.apache.doris.external.elasticsearch; import org.json.JSONObject; diff --git a/fe/src/main/java/org/apache/doris/external/ExternalDataSourceException.java b/fe/src/main/java/org/apache/doris/external/elasticsearch/ExternalDataSourceException.java similarity index 95% rename from fe/src/main/java/org/apache/doris/external/ExternalDataSourceException.java rename to fe/src/main/java/org/apache/doris/external/elasticsearch/ExternalDataSourceException.java index aee34a6..abb6744 100644 --- a/fe/src/main/java/org/apache/doris/external/ExternalDataSourceException.java +++ b/fe/src/main/java/org/apache/doris/external/elasticsearch/ExternalDataSourceException.java @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.external; +package org.apache.doris.external.elasticsearch; import org.apache.doris.common.UserException; diff --git a/fe/src/main/java/org/apache/doris/external/elasticsearch/QueryBuilders.java b/fe/src/main/java/org/apache/doris/external/elasticsearch/QueryBuilders.java new file mode 100644 index 0000000..cf51470 --- /dev/null +++ b/fe/src/main/java/org/apache/doris/external/elasticsearch/QueryBuilders.java @@ -0,0 +1,459 @@ +// 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.doris.external.elasticsearch; + +import com.fasterxml.jackson.core.JsonGenerator; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + + +/** + * Utility class to generate elastic search queries. + * Some query builders and static helper method have been copied from Elasticsearch + */ +public final class QueryBuilders { + + /** + * A query that matches on all documents. + */ + public static MatchAllQueryBuilder matchAllQuery() { + return new MatchAllQueryBuilder(); + } + + /** + * A Query that matches documents containing a term. + * + * @param name The name of the field + * @param value The value of the term + */ + public static TermQueryBuilder termQuery(String name, String value) { + return new TermQueryBuilder(name, value); + } + + /** + * A Query that matches documents containing a term. + * + * @param name The name of the field + * @param value The value of the term + */ + public static TermQueryBuilder termQuery(String name, int value) { + return new TermQueryBuilder(name, value); + } + + /** + * A Query that matches documents containing a term. + * + * @param name The name of the field + * @param value The value of the term + */ + public static TermQueryBuilder termQuery(String name, long value) { + return new TermQueryBuilder(name, value); + } + + /** + * A Query that matches documents containing a term. + * + * @param name The name of the field + * @param value The value of the term + */ + public static TermQueryBuilder termQuery(String name, float value) { + return new TermQueryBuilder(name, value); + } + + /** + * A Query that matches documents containing a term. + * + * @param name The name of the field + * @param value The value of the term + */ + public static TermQueryBuilder termQuery(String name, double value) { + return new TermQueryBuilder(name, value); + } + + /** + * A Query that matches documents containing a term. + * + * @param name The name of the field + * @param value The value of the term + */ + public static TermQueryBuilder termQuery(String name, boolean value) { + return new TermQueryBuilder(name, value); + } + + /** + * A Query that matches documents containing a term. + * + * @param name The name of the field + * @param value The value of the term + */ + public static TermQueryBuilder termQuery(String name, Object value) { + return new TermQueryBuilder(name, value); + } + + /** + * Implements the wildcard search query. Supported wildcards are {@code *}, which + * matches any character sequence (including the empty one), and {@code ?}, + * which matches any single character. Note this query can be slow, as it + * needs to iterate over many terms. In order to prevent extremely slow WildcardQueries, + * a Wildcard term should not start with one of the wildcards {@code *} or + * {@code ?}. + * + * @param name The field name + * @param query The wildcard query string + */ + public static WildcardQueryBuilder wildcardQuery(String name, String query) { + return new WildcardQueryBuilder(name, query); + } + + /** + * A Query that matches documents matching boolean combinations of other queries. + */ + public static BoolQueryBuilder boolQuery() { + return new BoolQueryBuilder(); + } + + + /** + * A filter for a field based on several terms matching on any of them. + * + * @param name The field name + * @param values The terms + */ + public static TermsQueryBuilder termsQuery(String name, Iterable<?> values) { + return new TermsQueryBuilder(name, values); + } + + /** + * A filter to filter only documents where a field exists in them. + * + * @param name The name of the field + */ + public static ExistsQueryBuilder existsQuery(String name) { + return new ExistsQueryBuilder(name); + } + + /** + * A Query that matches documents within an range of terms. + * + * @param name The field name + */ + public static RangeQueryBuilder rangeQuery(String name) { + return new RangeQueryBuilder(name); + } + + + /** + * Base class to build various ES queries + */ + abstract static class QueryBuilder { + + /** + * Convert query to JSON format + * + * @param out used to generate JSON elements + * @throws IOException if IO error occurred + */ + abstract void toJson(JsonGenerator out) throws IOException; + } + + /** + * A Query that matches documents matching boolean combinations of other queries. + */ + static class BoolQueryBuilder extends QueryBuilder { + + private final List<QueryBuilder> mustClauses = new ArrayList<>(); + private final List<QueryBuilder> mustNotClauses = new ArrayList<>(); + private final List<QueryBuilder> filterClauses = new ArrayList<>(); + private final List<QueryBuilder> shouldClauses = new ArrayList<>(); + + BoolQueryBuilder must(QueryBuilder queryBuilder) { + Objects.requireNonNull(queryBuilder); + mustClauses.add(queryBuilder); + return this; + } + + BoolQueryBuilder filter(QueryBuilder queryBuilder) { + Objects.requireNonNull(queryBuilder); + filterClauses.add(queryBuilder); + return this; + } + + BoolQueryBuilder mustNot(QueryBuilder queryBuilder) { + Objects.requireNonNull(queryBuilder); + mustNotClauses.add(queryBuilder); + return this; + } + + BoolQueryBuilder should(QueryBuilder queryBuilder) { + Objects.requireNonNull(queryBuilder); + shouldClauses.add(queryBuilder); + return this; + } + + @Override + protected void toJson(JsonGenerator out) throws IOException { + out.writeStartObject(); + out.writeFieldName("bool"); + out.writeStartObject(); + writeJsonArray("must", mustClauses, out); + writeJsonArray("filter", filterClauses, out); + writeJsonArray("must_not", mustNotClauses, out); + writeJsonArray("should", shouldClauses, out); + out.writeEndObject(); + out.writeEndObject(); + } + + private void writeJsonArray(String field, List<QueryBuilder> clauses, JsonGenerator out) + throws IOException { + if (clauses.isEmpty()) { + return; + } + + if (clauses.size() == 1) { + out.writeFieldName(field); + clauses.get(0).toJson(out); + } else { + out.writeArrayFieldStart(field); + for (QueryBuilder clause : clauses) { + clause.toJson(out); + } + out.writeEndArray(); + } + } + } + + /** + * A Query that matches documents containing a term + */ + static class TermQueryBuilder extends QueryBuilder { + private final String fieldName; + private final Object value; + + private TermQueryBuilder(final String fieldName, final Object value) { + this.fieldName = Objects.requireNonNull(fieldName, "fieldName"); + this.value = Objects.requireNonNull(value, "value"); + } + + @Override + void toJson(final JsonGenerator out) throws IOException { + out.writeStartObject(); + out.writeFieldName("term"); + out.writeStartObject(); + out.writeFieldName(fieldName); + writeObject(out, value); + out.writeEndObject(); + out.writeEndObject(); + } + } + + /** + * A filter for a field based on several terms matching on any of them. + */ + static class TermsQueryBuilder extends QueryBuilder { + private final String fieldName; + private final Iterable<?> values; + + private TermsQueryBuilder(final String fieldName, final Iterable<?> values) { + this.fieldName = Objects.requireNonNull(fieldName, "fieldName"); + this.values = Objects.requireNonNull(values, "values"); + } + + @Override + void toJson(final JsonGenerator out) throws IOException { + out.writeStartObject(); + out.writeFieldName("terms"); + out.writeStartObject(); + out.writeFieldName(fieldName); + out.writeStartArray(); + for (Object value : values) { + writeObject(out, value); + } + out.writeEndArray(); + out.writeEndObject(); + out.writeEndObject(); + } + } + + /** + * A Query that matches documents within an range of terms + */ + static class RangeQueryBuilder extends QueryBuilder { + + private final String field; + + private Object lt; + private boolean lte; + private Object gt; + private boolean gte; + + private String format; + + private RangeQueryBuilder(final String field) { + this.field = Objects.requireNonNull(field, "fieldName"); + } + + private RangeQueryBuilder to(Object value, boolean lte) { + this.lt = Objects.requireNonNull(value, "value"); + this.lte = lte; + return this; + } + + private RangeQueryBuilder from(Object value, boolean gte) { + this.gt = Objects.requireNonNull(value, "value"); + this.gte = gte; + return this; + } + + RangeQueryBuilder lt(Object value) { + return to(value, false); + } + + RangeQueryBuilder lte(Object value) { + return to(value, true); + } + + RangeQueryBuilder gt(Object value) { + return from(value, false); + } + + RangeQueryBuilder gte(Object value) { + return from(value, true); + } + + RangeQueryBuilder format(String format) { + this.format = format; + return this; + } + + @Override + void toJson(final JsonGenerator out) throws IOException { + if (lt == null && gt == null) { + throw new IllegalStateException("Either lower or upper bound should be provided"); + } + + out.writeStartObject(); + out.writeFieldName("range"); + out.writeStartObject(); + out.writeFieldName(field); + out.writeStartObject(); + + if (gt != null) { + final String op = gte ? "gte" : "gt"; + out.writeFieldName(op); + writeObject(out, gt); + } + + if (lt != null) { + final String op = lte ? "lte" : "lt"; + out.writeFieldName(op); + writeObject(out, lt); + } + + if (format != null) { + out.writeStringField("format", format); + } + + out.writeEndObject(); + out.writeEndObject(); + out.writeEndObject(); + } + } + + + /** + * Supported wildcards are {@code *}, which + * matches any character sequence (including the empty one), and {@code ?}, + * which matches any single character + */ + static class WildcardQueryBuilder extends QueryBuilder { + + private final String fieldName; + private final String value; + + + public WildcardQueryBuilder(String fieldName, String value) { + this.fieldName = Objects.requireNonNull(fieldName, "fieldName"); + this.value = Objects.requireNonNull(value, "value"); + } + + @Override + void toJson(JsonGenerator out) throws IOException { + out.writeStartObject(); + out.writeFieldName("wildcard"); + out.writeStartObject(); + out.writeFieldName(fieldName); + out.writeString(value); + out.writeEndObject(); + out.writeEndObject(); + } + } + + /** + * Query that only match on documents that the fieldName has a value in them + */ + static class ExistsQueryBuilder extends QueryBuilder { + + private final String fieldName; + + ExistsQueryBuilder(final String fieldName) { + this.fieldName = Objects.requireNonNull(fieldName, "fieldName"); + } + + @Override + void toJson(JsonGenerator out) throws IOException { + out.writeStartObject(); + out.writeFieldName("exists"); + out.writeStartObject(); + out.writeStringField("field", fieldName); + out.writeEndObject(); + out.writeEndObject(); + + } + } + + /** + * A query that matches on all documents + */ + static class MatchAllQueryBuilder extends QueryBuilder { + + private MatchAllQueryBuilder() { + } + + @Override + void toJson(final JsonGenerator out) throws IOException { + out.writeStartObject(); + out.writeFieldName("match_all"); + out.writeStartObject(); + out.writeEndObject(); + out.writeEndObject(); + } + } + + /** + * Write (scalar) value (string, number, boolean or null) to json format + * + * @param out source target + * @param value value to write + * @throws IOException if error + */ + private static void writeObject(JsonGenerator out, Object value) throws IOException { + out.writeObject(value); + } +} diff --git a/fe/src/main/java/org/apache/doris/planner/EsScanNode.java b/fe/src/main/java/org/apache/doris/planner/EsScanNode.java index 65fd3f1..497fa63 100644 --- a/fe/src/main/java/org/apache/doris/planner/EsScanNode.java +++ b/fe/src/main/java/org/apache/doris/planner/EsScanNode.java @@ -26,9 +26,9 @@ import org.apache.doris.catalog.PartitionKey; import org.apache.doris.catalog.RangePartitionInfo; import org.apache.doris.common.AnalysisException; import org.apache.doris.common.UserException; -import org.apache.doris.external.EsIndexState; -import org.apache.doris.external.EsShardRouting; -import org.apache.doris.external.EsTableState; +import org.apache.doris.external.elasticsearch.EsIndexState; +import org.apache.doris.external.elasticsearch.EsShardRouting; +import org.apache.doris.external.elasticsearch.EsTableState; import org.apache.doris.system.Backend; import org.apache.doris.thrift.TEsScanNode; import org.apache.doris.thrift.TEsScanRange; diff --git a/fe/src/test/java/org/apache/doris/es/EsStateStoreTest.java b/fe/src/test/java/org/apache/doris/es/EsStateStoreTest.java deleted file mode 100644 index c41c2c4..0000000 --- a/fe/src/test/java/org/apache/doris/es/EsStateStoreTest.java +++ /dev/null @@ -1,254 +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.doris.es; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import org.apache.doris.analysis.PartitionValue; -import org.apache.doris.catalog.Catalog; -import org.apache.doris.catalog.CatalogTestUtil; -import org.apache.doris.catalog.EsTable; -import org.apache.doris.catalog.FakeCatalog; -import org.apache.doris.catalog.FakeEditLog; -import org.apache.doris.catalog.PartitionKey; -import org.apache.doris.catalog.RangePartitionInfo; -import org.apache.doris.common.AnalysisException; -import org.apache.doris.common.FeMetaVersion; -import org.apache.doris.external.EsIndexState; -import org.apache.doris.external.EsStateStore; -import org.apache.doris.external.EsTableState; -import org.apache.doris.meta.MetaContext; - -import com.google.common.collect.Lists; -import com.google.common.collect.Range; - -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.lang.reflect.InvocationTargetException; -import java.net.URISyntaxException; -import java.util.Map; - -public class EsStateStoreTest { - - private static FakeEditLog fakeEditLog; - private static FakeCatalog fakeCatalog; - private static Catalog masterCatalog; - private static String clusterStateStr1 = ""; - private static String clusterStateStr2 = ""; - private static String clusterStateStr3 = ""; - private static String clusterStateStr4 = ""; - private static String clusterStateStr5 = ""; - private EsStateStore esStateStore; - - @BeforeClass - public static void init() throws IOException, InstantiationException, IllegalAccessException, - IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, - URISyntaxException { - fakeEditLog = new FakeEditLog(); - fakeCatalog = new FakeCatalog(); - masterCatalog = CatalogTestUtil.createTestCatalog(); - MetaContext metaContext = new MetaContext(); - metaContext.setMetaVersion(FeMetaVersion.VERSION_40); - metaContext.setThreadLocalInfo(); - // masterCatalog.setJournalVersion(FeMetaVersion.VERSION_40); - FakeCatalog.setCatalog(masterCatalog); - clusterStateStr1 = loadJsonFromFile("data/es/clusterstate1.json"); - clusterStateStr2 = loadJsonFromFile("data/es/clusterstate2.json"); - clusterStateStr3 = loadJsonFromFile("data/es/clusterstate3.json"); - clusterStateStr4 = loadJsonFromFile("data/es/clusterstate4.json"); - clusterStateStr5 = loadJsonFromFile("data/es/clusterstate5.json"); - } - - @Before - public void setUp() { - esStateStore = new EsStateStore(); - } - - /** - * partitioned es table schema: k1(date), k2(int), v(double) - * @throws AnalysisException - */ - @Test - public void testParsePartitionedClusterState() throws AnalysisException { - EsTable esTable = (EsTable) Catalog.getCurrentCatalog() - .getDb(CatalogTestUtil.testDb1) - .getTable(CatalogTestUtil.testPartitionedEsTable1); - boolean hasException = false; - EsTableState esTableState = null; - try { - esTableState = esStateStore.getTableState(clusterStateStr1, esTable); - } catch (Exception e) { - e.printStackTrace(); - hasException = true; - } - assertFalse(hasException); - assertNotNull(esTableState); - assertEquals(2, esTableState.getPartitionedIndexStates().size()); - RangePartitionInfo definedPartInfo = (RangePartitionInfo) esTable.getPartitionInfo(); - RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) esTableState.getPartitionInfo(); - Map<Long, Range<PartitionKey>> rangeMap = rangePartitionInfo.getIdToRange(false); - assertEquals(2, rangeMap.size()); - Range<PartitionKey> part0 = rangeMap.get(new Long(0)); - EsIndexState esIndexState1 = esTableState.getIndexState(0); - assertEquals(5, esIndexState1.getShardRoutings().size()); - assertEquals("index1", esIndexState1.getIndexName()); - PartitionKey lowKey = PartitionKey.createInfinityPartitionKey(definedPartInfo.getPartitionColumns(), false); - PartitionKey upperKey = PartitionKey.createPartitionKey(Lists.newArrayList(new PartitionValue("2018-10-01")), - definedPartInfo.getPartitionColumns()); - Range<PartitionKey> newRange = Range.closedOpen(lowKey, upperKey); - assertEquals(newRange, part0); - Range<PartitionKey> part1 = rangeMap.get(new Long(1)); - EsIndexState esIndexState2 = esTableState.getIndexState(1); - assertEquals("index2", esIndexState2.getIndexName()); - lowKey = PartitionKey.createPartitionKey(Lists.newArrayList(new PartitionValue("2018-10-01")), - definedPartInfo.getPartitionColumns()); - upperKey = PartitionKey.createPartitionKey(Lists.newArrayList(new PartitionValue("2018-10-02")), - definedPartInfo.getPartitionColumns()); - newRange = Range.closedOpen(lowKey, upperKey); - assertEquals(newRange, part1); - assertEquals(6, esIndexState2.getShardRoutings().size()); - } - - /** - * partitioned es table schema: k1(date), k2(int), v(double) - * scenario desc: - * 2 indices, one with partition desc, the other does not contains partition desc - * @throws AnalysisException - */ - @Test - public void testParsePartitionedClusterStateTwoIndices() throws AnalysisException { - EsTable esTable = (EsTable) Catalog.getCurrentCatalog() - .getDb(CatalogTestUtil.testDb1) - .getTable(CatalogTestUtil.testPartitionedEsTable1); - boolean hasException = false; - EsTableState esTableState = null; - try { - esTableState = esStateStore.getTableState(clusterStateStr3, esTable); - } catch (Exception e) { - e.printStackTrace(); - hasException = true; - } - assertFalse(hasException); - assertNotNull(esTableState); - - // check - assertEquals(1, esTableState.getPartitionedIndexStates().size()); - assertEquals(1, esTableState.getUnPartitionedIndexStates().size()); - - // check partition info - RangePartitionInfo definedPartInfo = (RangePartitionInfo) esTable.getPartitionInfo(); - RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) esTableState.getPartitionInfo(); - Map<Long, Range<PartitionKey>> rangeMap = rangePartitionInfo.getIdToRange(false); - assertEquals(1, rangeMap.size()); - Range<PartitionKey> part0 = rangeMap.get(new Long(0)); - EsIndexState esIndexState1 = esTableState.getIndexState(0); - assertEquals(5, esIndexState1.getShardRoutings().size()); - assertEquals("index1", esIndexState1.getIndexName()); - PartitionKey lowKey = PartitionKey.createInfinityPartitionKey(definedPartInfo.getPartitionColumns(), false); - PartitionKey upperKey = PartitionKey.createPartitionKey(Lists.newArrayList(new PartitionValue("2018-10-01")), - definedPartInfo.getPartitionColumns()); - Range<PartitionKey> newRange = Range.closedOpen(lowKey, upperKey); - assertEquals(newRange, part0); - - // check index with no partition desc - EsIndexState esIndexState2 = esTableState.getUnPartitionedIndexStates().get("index2"); - assertEquals("index2", esIndexState2.getIndexName()); - assertEquals(6, esIndexState2.getShardRoutings().size()); - } - - /** - * partitioned es table schema: k1(date), k2(int), v(double) - * scenario desc: - * 2 indices, both of them does not contains partition desc and es table does not have partition info - * but cluster state have partition info - * @throws AnalysisException - */ - @Test - public void testParseUnPartitionedClusterStateTwoIndices() throws AnalysisException { - EsTable esTable = (EsTable) Catalog.getCurrentCatalog() - .getDb(CatalogTestUtil.testDb1) - .getTable(CatalogTestUtil.testUnPartitionedEsTableId1); - boolean hasException = false; - EsTableState esTableState = null; - try { - esTableState = esStateStore.getTableState(clusterStateStr4, esTable); - } catch (Exception e) { - e.printStackTrace(); - hasException = true; - } - assertFalse(hasException); - assertNotNull(esTableState); - - // check - assertEquals(0, esTableState.getPartitionedIndexStates().size()); - assertEquals(2, esTableState.getUnPartitionedIndexStates().size()); - - // check index with no partition desc - EsIndexState esIndexState1 = esTableState.getUnPartitionedIndexStates().get("index1"); - assertEquals("index1", esIndexState1.getIndexName()); - EsIndexState esIndexState2 = esTableState.getUnPartitionedIndexStates().get("index2"); - assertEquals("index2", esIndexState2.getIndexName()); - assertEquals(6, esIndexState2.getShardRoutings().size()); - } - - - /** - * partitioned es table schema: k1(date), k2(int), v(double) - * "upperbound": "2018" is not a valid date value, so parsing procedure will fail - */ - @Test - public void testParseInvalidUpperbound() { - EsTable esTable = (EsTable) Catalog.getCurrentCatalog() - .getDb(CatalogTestUtil.testDb1) - .getTable(CatalogTestUtil.testPartitionedEsTable1); - boolean hasException = false; - EsTableState esTableState = null; - try { - esTableState = esStateStore.getTableState(clusterStateStr2, esTable); - } catch (Exception e) { - hasException = true; - } - assertTrue(hasException); - assertTrue(esTableState == null); - } - - private static String loadJsonFromFile(String fileName) throws IOException, URISyntaxException { - File file = new File(EsStateStoreTest.class.getClassLoader().getResource(fileName).toURI()); - InputStream is = new FileInputStream(file); - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - StringBuilder jsonStr = new StringBuilder(); - String line = ""; - while ((line = br.readLine()) != null) { - jsonStr.append(line); - } - br.close(); - is.close(); - return jsonStr.toString(); - } -} diff --git a/fe/src/test/java/org/apache/doris/es/EsUtilTest.java b/fe/src/test/java/org/apache/doris/external/elasticsearch/EsUtilTest.java similarity index 97% rename from fe/src/test/java/org/apache/doris/es/EsUtilTest.java rename to fe/src/test/java/org/apache/doris/external/elasticsearch/EsUtilTest.java index 876a8ea..b611ae4 100644 --- a/fe/src/test/java/org/apache/doris/es/EsUtilTest.java +++ b/fe/src/test/java/org/apache/doris/external/elasticsearch/EsUtilTest.java @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.es; +package org.apache.doris.external.elasticsearch; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -25,8 +25,6 @@ import org.json.JSONException; import org.json.JSONObject; import org.junit.Test; -import org.apache.doris.external.EsUtil; - public class EsUtilTest { private String jsonStr = "{\"settings\": {\n" diff --git a/fe/src/test/java/org/apache/doris/external/elasticsearch/QueryBuildersTest.java b/fe/src/test/java/org/apache/doris/external/elasticsearch/QueryBuildersTest.java new file mode 100644 index 0000000..ebd0243 --- /dev/null +++ b/fe/src/test/java/org/apache/doris/external/elasticsearch/QueryBuildersTest.java @@ -0,0 +1,173 @@ +// 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.doris.external.elasticsearch; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.StringWriter; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.doris.external.elasticsearch.QueryBuilders; +import org.junit.Test; + + +import static org.junit.Assert.assertEquals; + +/** + * Check that internal queries are correctly converted to ES search query (as JSON) + */ +public class QueryBuildersTest { + + private final ObjectMapper mapper = new ObjectMapper(); + + + @Test + public void testTermQuery() throws Exception { + assertEquals("{\"term\":{\"k\":\"aaaa\"}}", + toJson(QueryBuilders.termQuery("k", "aaaa"))); + assertEquals("{\"term\":{\"aaaa\":\"k\"}}", + toJson(QueryBuilders.termQuery("aaaa", "k"))); + assertEquals("{\"term\":{\"k\":0}}", + toJson(QueryBuilders.termQuery("k", (byte) 0))); + assertEquals("{\"term\":{\"k\":123}}", + toJson(QueryBuilders.termQuery("k", (long) 123))); + assertEquals("{\"term\":{\"k\":41}}", + toJson(QueryBuilders.termQuery("k", (short) 41))); + assertEquals("{\"term\":{\"k\":128}}", + toJson(QueryBuilders.termQuery("k", 128))); + assertEquals("{\"term\":{\"k\":42.42}}", + toJson(QueryBuilders.termQuery("k", 42.42D))); + assertEquals("{\"term\":{\"k\":1.1}}", + toJson(QueryBuilders.termQuery("k", 1.1F))); + assertEquals("{\"term\":{\"k\":1}}", + toJson(QueryBuilders.termQuery("k", new BigDecimal(1)))); + assertEquals("{\"term\":{\"k\":121}}", + toJson(QueryBuilders.termQuery("k", new BigInteger("121")))); + assertEquals("{\"term\":{\"k\":true}}", + toJson(QueryBuilders.termQuery("k", new AtomicBoolean(true)))); + } + + @Test + public void testTermsQuery() throws Exception { + + assertEquals("{\"terms\":{\"k\":[]}}", + toJson(QueryBuilders.termsQuery("k", Collections.emptySet()))); + + assertEquals("{\"terms\":{\"k\":[0]}}", + toJson(QueryBuilders.termsQuery("k", Collections.singleton(0)))); + + assertEquals("{\"terms\":{\"k\":[\"aaa\"]}}", + toJson(QueryBuilders.termsQuery("k", Collections.singleton("aaa")))); + + assertEquals("{\"terms\":{\"k\":[\"aaa\",\"bbb\",\"ccc\"]}}", + toJson(QueryBuilders.termsQuery("k", Arrays.asList("aaa", "bbb", "ccc")))); + + assertEquals("{\"terms\":{\"k\":[1,2,3]}}", + toJson(QueryBuilders.termsQuery("k", Arrays.asList(1, 2, 3)))); + + assertEquals("{\"terms\":{\"k\":[1.1,2.2,3.3]}}", + toJson(QueryBuilders.termsQuery("k", Arrays.asList(1.1f, 2.2f, 3.3f)))); + + assertEquals("{\"terms\":{\"k\":[1.1,2.2,3.3]}}", + toJson(QueryBuilders.termsQuery("k", Arrays.asList(1.1d, 2.2d, 3.3d)))); + } + + @Test + public void testBoolQuery() throws Exception { + QueryBuilders.QueryBuilder q1 = QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery("k", "aaa")); + + assertEquals("{\"bool\":{\"must\":{\"term\":{\"k\":\"aaa\"}}}}", + toJson(q1)); + + QueryBuilders.QueryBuilder q2 = QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery("k1", "aaa")).must(QueryBuilders.termQuery("k2", "bbb")); + + assertEquals("{\"bool\":{\"must\":[{\"term\":{\"k1\":\"aaa\"}},{\"term\":{\"k2\":\"bbb\"}}]}}", + toJson(q2)); + + QueryBuilders.QueryBuilder q3 = QueryBuilders.boolQuery() + .mustNot(QueryBuilders.termQuery("k", "fff")); + + assertEquals("{\"bool\":{\"must_not\":{\"term\":{\"k\":\"fff\"}}}}", + toJson(q3)); + + QueryBuilders.QueryBuilder q4 = QueryBuilders.rangeQuery("k1").lt(200).gt(-200); + QueryBuilders.QueryBuilder q5 = QueryBuilders.termsQuery("k2", Arrays.asList("aaa", "bbb", "ccc")); + QueryBuilders.QueryBuilder q6 = QueryBuilders.boolQuery().must(q4).should(q5); + assertEquals("{\"bool\":{\"must\":{\"range\":{\"k1\":{\"gt\":-200,\"lt\":200}}},\"should\":{\"terms\":{\"k2\":[\"aaa\",\"bbb\",\"ccc\"]}}}}", toJson(q6)); + assertEquals("{\"bool\":{\"filter\":[{\"range\":{\"k1\":{\"gt\":-200,\"lt\":200}}},{\"terms\":{\"k2\":[\"aaa\",\"bbb\",\"ccc\"]}}]}}", toJson(QueryBuilders.boolQuery().filter(q4).filter(q5))); + assertEquals("{\"bool\":{\"filter\":{\"range\":{\"k1\":{\"gt\":-200,\"lt\":200}}},\"must_not\":{\"terms\":{\"k2\":[\"aaa\",\"bbb\",\"ccc\"]}}}}", toJson(QueryBuilders.boolQuery().filter(q4).mustNot(q5))); + + } + + @Test + public void testExistsQuery() throws Exception { + assertEquals("{\"exists\":{\"field\":\"k\"}}", + toJson(QueryBuilders.existsQuery("k"))); + } + + @Test + public void testRangeQuery() throws Exception { + assertEquals("{\"range\":{\"k\":{\"lt\":123}}}", + toJson(QueryBuilders.rangeQuery("k").lt(123))); + assertEquals("{\"range\":{\"k\":{\"gt\":123}}}", + toJson(QueryBuilders.rangeQuery("k").gt(123))); + assertEquals("{\"range\":{\"k\":{\"gte\":12345678}}}", + toJson(QueryBuilders.rangeQuery("k").gte(12345678))); + assertEquals("{\"range\":{\"k\":{\"lte\":12345678}}}", + toJson(QueryBuilders.rangeQuery("k").lte(12345678))); + assertEquals("{\"range\":{\"k\":{\"gt\":123,\"lt\":345}}}", + toJson(QueryBuilders.rangeQuery("k").gt(123).lt(345))); + assertEquals("{\"range\":{\"k\":{\"gt\":-456.6,\"lt\":12.3}}}", + toJson(QueryBuilders.rangeQuery("k").lt(12.3f).gt(-456.6f))); + assertEquals("{\"range\":{\"k\":{\"gt\":6789.33,\"lte\":9999.99}}}", + toJson(QueryBuilders.rangeQuery("k").gt(6789.33f).lte(9999.99f))); + assertEquals("{\"range\":{\"k\":{\"gte\":1,\"lte\":\"zzz\"}}}", + toJson(QueryBuilders.rangeQuery("k").gte(1).lte("zzz"))); + assertEquals("{\"range\":{\"k\":{\"gte\":\"zzz\"}}}", + toJson(QueryBuilders.rangeQuery("k").gte("zzz"))); + assertEquals("{\"range\":{\"k\":{\"gt\":\"aaa\",\"lt\":\"zzz\"}}}", + toJson(QueryBuilders.rangeQuery("k").gt("aaa").lt("zzz"))); + } + + @Test + public void testMatchAllQuery() throws IOException { + assertEquals("{\"match_all\":{}}", + toJson(QueryBuilders.matchAllQuery())); + } + + @Test + public void testWildCardQuery() throws IOException { + assertEquals("{\"wildcard\":{\"k1\":\"?aa*\"}}", + toJson(QueryBuilders.wildcardQuery("k1", "?aa*"))); + } + + private String toJson(QueryBuilders.QueryBuilder builder) throws IOException { + StringWriter writer = new StringWriter(); + JsonGenerator gen = mapper.getFactory().createGenerator(writer); + builder.toJson(gen); + gen.flush(); + gen.close(); + return writer.toString(); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org