From 85b877fa97d5edf47f3fd4780a170a0ff0f7a833 Mon Sep 17 00:00:00 2001 From: Guru Kathiresan Date: Tue, 19 Jan 2016 05:29:53 -0800 Subject: [PATCH 1/3] Added the String Contains Ignorecase criteria support --HG-- branch : INGORE_CASE_STRING_CRITERIA --- .../elasticsearch/ElasticsearchSerializer.java | 4 ++++ .../querydsl/elasticsearch/ElasticsearchQueryTest.java | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/querydsl-elasticsearch/src/main/java/com/querydsl/elasticsearch/ElasticsearchSerializer.java b/querydsl-elasticsearch/src/main/java/com/querydsl/elasticsearch/ElasticsearchSerializer.java index 0cea353..3f3e72a 100644 --- a/querydsl-elasticsearch/src/main/java/com/querydsl/elasticsearch/ElasticsearchSerializer.java +++ b/querydsl-elasticsearch/src/main/java/com/querydsl/elasticsearch/ElasticsearchSerializer.java @@ -212,6 +212,10 @@ public Object visit(Operation expr, @Nullable BoolQueryBuilder context) { String value = StringUtils.toString(asDBValue(expr, 1)); return QueryBuilders.queryString("*" + value + "*").field(asDBKey(expr, 0)).analyzeWildcard(true); + } else if (op == Ops.STRING_CONTAINS_IC) { + String value = StringUtils.toString(asDBValue(expr, 1)); + return QueryBuilders.queryString("*" + value + "*").field(asDBKey(expr, 0)).analyzer("standard").analyzeWildcard(true); + } else if (op == Ops.NOT) { // Handle the not's child BoolQueryBuilder subContext = QueryBuilders.boolQuery(); diff --git a/querydsl-elasticsearch/src/test/java/com/querydsl/elasticsearch/ElasticsearchQueryTest.java b/querydsl-elasticsearch/src/test/java/com/querydsl/elasticsearch/ElasticsearchQueryTest.java index 1ceb42c..e938d75 100644 --- a/querydsl-elasticsearch/src/test/java/com/querydsl/elasticsearch/ElasticsearchQueryTest.java +++ b/querydsl-elasticsearch/src/test/java/com/querydsl/elasticsearch/ElasticsearchQueryTest.java @@ -117,6 +117,16 @@ public void NotContains() { //assertQuery(user.friends.contains(u1).not(), u1); } + @Test + public void Contains_Ignore_Case() { + assertTrue(where(user.firstName.containsIgnoreCase("akk")).fetchCount() > 0); + } + + @Test + public void Contains_Ignore_Case_2() { + assertFalse(where(user.firstName.containsIgnoreCase("xyzzz")).fetchCount() > 0); + } + @Test public void Equals_Ignore_Case() { assertTrue(where(user.firstName.equalsIgnoreCase("jAaKko")).fetchCount() > 0); From d8db280069c0d9fe7b19176d7b7f517293df5b5a Mon Sep 17 00:00:00 2001 From: Guru Kathiresan Date: Tue, 19 Jan 2016 05:34:41 -0800 Subject: [PATCH 2/3] Close INGORE_CASE_STRING_CRITERIA branch --HG-- branch : INGORE_CASE_STRING_CRITERIA From 1ff673b3d8593ef228b9aeb025dcabaab3d8f00a Mon Sep 17 00:00:00 2001 From: Guru Kathiresan Date: Tue, 19 Jan 2016 05:57:14 -0800 Subject: [PATCH 3/3] Added support for elastic search 2 --HG-- branch : INGORE_CASE_STRING_CRITERIA --- pom.xml | 1 + querydsl-elasticsearch2/pom.xml | 159 +++++++++ .../javax.annotation.processing.Processor | 1 + querydsl-elasticsearch2/src/main/apt.xml | 19 ++ querydsl-elasticsearch2/src/main/assembly.xml | 25 ++ .../elasticsearch2/ElasticsearchQuery.java | 256 ++++++++++++++ .../ElasticsearchSerializer.java | 296 ++++++++++++++++ .../jackson/JacksonElasticsearchQueries.java | 114 +++++++ .../jackson/MappingException.java | 20 ++ .../ElasticsearchQueryTest.java | 315 +++++++++++++++++ .../ElasticsearchSerializerTest.java | 316 ++++++++++++++++++ .../elasticsearch2/PackageVerification.java | 54 +++ .../elasticsearch2/domain/AbstractEntity.java | 37 ++ .../querydsl/elasticsearch2/domain/User.java | 86 +++++ 14 files changed, 1699 insertions(+) create mode 100644 querydsl-elasticsearch2/pom.xml create mode 100644 querydsl-elasticsearch2/src/apt/META-INF/services/javax.annotation.processing.Processor create mode 100644 querydsl-elasticsearch2/src/main/apt.xml create mode 100644 querydsl-elasticsearch2/src/main/assembly.xml create mode 100644 querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/ElasticsearchQuery.java create mode 100644 querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/ElasticsearchSerializer.java create mode 100644 querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/jackson/JacksonElasticsearchQueries.java create mode 100644 querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/jackson/MappingException.java create mode 100644 querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/ElasticsearchQueryTest.java create mode 100644 querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/ElasticsearchSerializerTest.java create mode 100644 querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/PackageVerification.java create mode 100644 querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/domain/AbstractEntity.java create mode 100644 querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/domain/User.java diff --git a/pom.xml b/pom.xml index c76eeea..f52f48b 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,7 @@ querydsl-dynamodb querydsl-hazelcast querydsl-elasticsearch + querydsl-elasticsearch2 diff --git a/querydsl-elasticsearch2/pom.xml b/querydsl-elasticsearch2/pom.xml new file mode 100644 index 0000000..62af126 --- /dev/null +++ b/querydsl-elasticsearch2/pom.xml @@ -0,0 +1,159 @@ + + + 4.0.0 + + + com.querydsl.contrib + querydsl-contrib + 4.0.4 + ../pom.xml + + + querydsl-elasticsearch2 + Querydsl - Elasticsearch 2 support + Elasticsearch 2 support for Querydsl + jar + ${project.homepage} + + + ${project.checkout} + ${project.checkout} + ${project.githubpage} + + + + 2.1.0 + 2.6.3 + + org.elasticsearch.*;version="0.0.0", + com.fasterxml.jackson.core.*;version="0.0.0", + ${osgi.import.package.root} + + + + + + + + org.elasticsearch + elasticsearch + ${elasticsearch.version} + + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + com.querydsl + querydsl-apt + ${project.version} + provided + + + + + + + + com.springsource.bundlor + com.springsource.bundlor.maven + + + + com.mysema.maven + apt-maven-plugin + + + generate-test-sources + + test-process + add-test-sources + + + target/generated-test-sources/java + com.querydsl.apt.QuerydslAnnotationProcessor + + true + + + + + + + + maven-assembly-plugin + + + extra-jars + + single + + package + + + src/main/apt.xml + src/main/assembly.xml + + ${project.build.directory} + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + verification + + test + + verify + + + + version + ${project.version} + + + + com/mysema/query/PackageVerification.java + + + + + + + + + + + + kevinleturc + Kevin Leturc + leturc.kevin@gmail.com + + donator + + + + gkathire + Guru Kathiresan + gururamnath@yahoo.com + + donator + + + + + diff --git a/querydsl-elasticsearch2/src/apt/META-INF/services/javax.annotation.processing.Processor b/querydsl-elasticsearch2/src/apt/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000..819d6cd --- /dev/null +++ b/querydsl-elasticsearch2/src/apt/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +com.mysema.query.apt.QuerydslAnnotationProcessor \ No newline at end of file diff --git a/querydsl-elasticsearch2/src/main/apt.xml b/querydsl-elasticsearch2/src/main/apt.xml new file mode 100644 index 0000000..a8b530c --- /dev/null +++ b/querydsl-elasticsearch2/src/main/apt.xml @@ -0,0 +1,19 @@ + + apt + + jar + + false + + + src/apt + / + + + ${project.build.outputDirectory} + / + + + \ No newline at end of file diff --git a/querydsl-elasticsearch2/src/main/assembly.xml b/querydsl-elasticsearch2/src/main/assembly.xml new file mode 100644 index 0000000..1c14abc --- /dev/null +++ b/querydsl-elasticsearch2/src/main/assembly.xml @@ -0,0 +1,25 @@ + + apt-one-jar + + jar + + false + + + src/apt + / + + + src/license + /license + + + + + true + + + + diff --git a/querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/ElasticsearchQuery.java b/querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/ElasticsearchQuery.java new file mode 100644 index 0000000..454d2e7 --- /dev/null +++ b/querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/ElasticsearchQuery.java @@ -0,0 +1,256 @@ +/* + * Copyright 2014, Mysema Ltd + * + * Licensed 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 com.querydsl.elasticsearch2; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nonnegative; +import javax.annotation.Nullable; + +import org.elasticsearch.action.search.SearchRequestBuilder; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.SearchHits; + +import com.google.common.base.Function; +import com.mysema.commons.lang.CloseableIterator; +import com.mysema.commons.lang.IteratorAdapter; +import com.querydsl.core.*; +import com.querydsl.core.support.QueryMixin; +import com.querydsl.core.types.*; + +/** + * ElasticsearchQuery provides a general Querydsl query implementation with a pluggable String to Bean transformation + * + * @param result type + * @author Kevin Leturc + */ +public abstract class ElasticsearchQuery implements SimpleQuery>, Fetchable { + + private final QueryMixin> queryMixin; + + private final Client client; + + private final Function transformer; + + private final ElasticsearchSerializer serializer; + + public ElasticsearchQuery(Client client, Function transformer, ElasticsearchSerializer serializer) { + this.queryMixin = new QueryMixin>(this, new DefaultQueryMetadata().noValidate(), false); + this.client = client; + this.transformer = transformer; + this.serializer = serializer; + } + + @Override + public CloseableIterator iterate() { + return new IteratorAdapter(fetch().iterator()); + } + + public List fetch(Path... paths) { + queryMixin.setProjection(paths); + return fetch(); + } + + @Override + public List fetch() { + // Test if there're limit or offset, and if not, set them to retrieve all results + // because by default elasticsearch returns only 10 results + QueryMetadata metadata = queryMixin.getMetadata(); + QueryModifiers modifiers = metadata.getModifiers(); + if (modifiers.getLimit() == null && modifiers.getOffset() == null) { + long count = fetchCount(); + if (count > 0L) { + // Set the limit only if there's result + metadata.setModifiers(new QueryModifiers(count, 0L)); + } + } + + // Execute search + SearchResponse searchResponse = executeSearch(); + List results = new ArrayList(); + for (SearchHit hit : searchResponse.getHits().getHits()) { + results.add(transformer.apply(hit)); + } + return results; + } + + public K fetchFirst(Path... paths) { + queryMixin.setProjection(paths); + return fetchFirst(); + } + + @Nullable + @Override + public K fetchFirst() { + // Set the size of response + queryMixin.getMetadata().setModifiers(new QueryModifiers(1L, 0L)); + + SearchResponse searchResponse = executeSearch(); + SearchHits hits = searchResponse.getHits(); + if (hits.getTotalHits() > 0) { + return transformer.apply(hits.getAt(0)); + } else { + return null; + } + } + + public K fetchOne(Path... paths) { + queryMixin.setProjection(paths); + return fetchOne(); + } + + @Nullable + @Override + public K fetchOne() { + // Set the size of response + // Set 2 as limit because it has to be ony one result which match the condition + queryMixin.getMetadata().setModifiers(new QueryModifiers(2L, 0L)); + + SearchResponse searchResponse = executeSearch(); + SearchHits hits = searchResponse.getHits(); + long totalHits = hits.getTotalHits(); + if (totalHits == 1L) { + return transformer.apply(hits.getAt(0)); + } else if (totalHits > 1L) { + throw new NonUniqueResultException(); + } else { + return null; + } + } + + public QueryResults fetchResults(Path... paths) { + queryMixin.setProjection(paths); + return fetchResults(); + } + + @Override + public QueryResults fetchResults() { + long total = fetchCount(); + if (total > 0L) { + return new QueryResults(fetch(), queryMixin.getMetadata().getModifiers(), total); + } else { + return QueryResults.emptyResults(); + } + } + + @Override + public long fetchCount() { + Predicate filter = createFilter(queryMixin.getMetadata()); + return client.prepareCount().setQuery(createQuery(filter)).execute().actionGet().getCount(); + } + + @Override + public ElasticsearchQuery limit(@Nonnegative long limit) { + return queryMixin.limit(limit); + } + + @Override + public ElasticsearchQuery offset(@Nonnegative long offset) { + return queryMixin.offset(offset); + } + + @Override + public ElasticsearchQuery restrict(QueryModifiers modifiers) { + return queryMixin.restrict(modifiers); + } + + @Override + public ElasticsearchQuery orderBy(OrderSpecifier... o) { + return queryMixin.orderBy(o); + } + + @Override + public ElasticsearchQuery set(ParamExpression param, T value) { + return queryMixin.set(param, value); + } + + @Override + public ElasticsearchQuery distinct() { + return queryMixin.distinct(); + } + + @Override + public ElasticsearchQuery where(Predicate... o) { + return queryMixin.where(o); + } + + @Nullable + protected Predicate createFilter(QueryMetadata metadata) { + return metadata.getWhere(); + } + + private QueryBuilder createQuery(@Nullable Predicate predicate) { + if (predicate != null) { + return (QueryBuilder) serializer.handle(predicate); + } else { + return QueryBuilders.matchAllQuery(); + } + } + + private SearchResponse executeSearch() { + QueryMetadata metadata = queryMixin.getMetadata(); + Predicate filter = createFilter(metadata); + return executeSearch(getIndex(), getType(), filter, metadata.getProjection(), metadata.getModifiers(), + metadata.getOrderBy()); + } + + private SearchResponse executeSearch(String index, String type, Predicate filter, + Expression projection, QueryModifiers modifiers, List> orderBys) { + SearchRequestBuilder requestBuilder = client.prepareSearch(index).setTypes(type); + + // Set query + requestBuilder.setQuery(createQuery(filter)); + + // Add order by + for (OrderSpecifier sort : orderBys) { + requestBuilder.addSort(serializer.toSort(sort)); + } + + // Add projections + if (projection != null) { + List sourceFields = new ArrayList(); + if (projection instanceof FactoryExpression) { + for (Expression pr : ((FactoryExpression) projection).getArgs()) { + sourceFields.add(pr.accept(serializer, null).toString()); + } + } else { + sourceFields.add(projection.accept(serializer, null).toString()); + } + + requestBuilder.setFetchSource(sourceFields.toArray(new String[sourceFields.size()]), null); + } + + // Add limit and offset + Integer limit = modifiers.getLimitAsInteger(); + Integer offset = modifiers.getOffsetAsInteger(); + if (limit != null) { + requestBuilder.setSize(limit); + } + if (offset != null) { + requestBuilder.setFrom(offset); + } + + return requestBuilder.execute().actionGet(); + } + + public abstract String getIndex(); + + public abstract String getType(); + +} diff --git a/querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/ElasticsearchSerializer.java b/querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/ElasticsearchSerializer.java new file mode 100644 index 0000000..a2d31a1 --- /dev/null +++ b/querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/ElasticsearchSerializer.java @@ -0,0 +1,296 @@ +/* + * Copyright 2014, Mysema Ltd + * + * Licensed 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 com.querydsl.elasticsearch2; + +import java.util.Collection; +import java.util.Set; + +import javax.annotation.Nullable; + +import org.apache.lucene.queryparser.flexible.core.util.StringUtils; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.IdsQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.sort.SortBuilder; +import org.elasticsearch.search.sort.SortBuilders; +import org.elasticsearch.search.sort.SortOrder; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; +import com.querydsl.core.types.*; +import com.querydsl.core.types.dsl.Expressions; + +/** + * Serializes the given Querydsl query to a String query for Elasticsearch + * + * @author Kevin Leturc + */ +public class ElasticsearchSerializer implements Visitor { + + /** AND and OR operands. */ + private static final Set AND_OR = Sets.newHashSet(Ops.AND, Ops.OR); + + public Object handle(Expression expression) { + BoolQueryBuilder context = QueryBuilders.boolQuery(); + QueryBuilder query = (QueryBuilder) expression.accept(this, context); + if (!context.hasClauses() && query != null) { + context.must(query); + } + return context; + } + + public SortBuilder toSort(OrderSpecifier orderBy) { + Object key = orderBy.getTarget().accept(this, null); + return SortBuilders.fieldSort(key.toString()).order(orderBy.getOrder() == Order.ASC ? SortOrder.ASC : SortOrder.DESC); + } + + @Nullable + @Override + public Object visit(Constant expr, @Nullable BoolQueryBuilder context) { + if (Enum.class.isAssignableFrom(expr.getType())) { + return ((Enum) expr.getConstant()).name(); + } else { + return expr.getConstant(); + } + } + + @Nullable + @Override + public Object visit(FactoryExpression expr, @Nullable BoolQueryBuilder context) { + return null; + } + + public String asDBKey(Operation expr, int index) { + return StringUtils.toString(asDBValue(expr, index)); + } + + public Object asDBValue(Operation expr, int index) { + return expr.getArg(index).accept(this, null); + } + + @Nullable + @Override + public Object visit(Operation expr, @Nullable BoolQueryBuilder context) { + Preconditions.checkNotNull(context); + Operator op = expr.getOperator(); + if (op == Ops.EQ) { + Expression keyArg = expr.getArg(0); + String value = StringUtils.toString(asDBValue(expr, 1)); + if (keyArg instanceof Path && isIdPath((Path) expr.getArg(0))) { + return QueryBuilders.idsQuery().ids(value); + } else { + // Currently all queries are made with ignore case sensitive + // Because the query to get exact value have to be run on a not_analyzed field + return QueryBuilders.queryStringQuery(value).field(asDBKey(expr, 0)); + } + + } else if (op == Ops.EQ_IGNORE_CASE) { + String value = StringUtils.toString(asDBValue(expr, 1)); + return QueryBuilders.queryStringQuery(value).field(asDBKey(expr, 0)); + + } else if (op == Ops.NE) { + // Decompose the query as NOT and EQ query + return visit( + Expressions.predicate( + Ops.NOT, + Expressions.predicate( + Ops.EQ, + expr.getArg(0), + expr.getArg(1))), + context); + + } else if (op == Ops.STRING_IS_EMPTY) { + return QueryBuilders.queryStringQuery("").field(asDBKey(expr, 0)); + + } else if (op == Ops.AND || op == Ops.OR) { + Operation left = (Operation) expr.getArg(0); + Operation right = (Operation) expr.getArg(1); + // Perform the left expression + QueryBuilder leftResult = visitSubAndOr(op, context, left); + // Perform the right expression + QueryBuilder rightResult = visitSubAndOr(op, context, right); + + if (op == Ops.AND) { + safeMust(context, leftResult); + safeMust(context, rightResult); + } else { + safeShould(context, leftResult); + safeShould(context, rightResult); + } + return null; + + } else if (op == Ops.IN) { + + int constIndex = 0; + int exprIndex = 1; + if (expr.getArg(1) instanceof Constant) { + constIndex = 1; + exprIndex = 0; + } + Expression keyExpr = expr.getArg(exprIndex); + if (keyExpr instanceof Path && isIdPath((Path) keyExpr)) { + IdsQueryBuilder idsQuery = QueryBuilders.idsQuery(); + // Hope this is the only case for Elasticsearch ids + Collection values = (Collection) ((Constant) expr.getArg(constIndex)).getConstant(); + for (Object value : values) { + idsQuery.addIds(StringUtils.toString(value)); + } + return idsQuery; + } else { + // Currently all queries are made with ignore case sensitive + // Because the query to get exact value have to be run on a not_analyzed field + BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); + String key = asDBKey(expr, exprIndex); + if (Collection.class.isAssignableFrom(expr.getArg(constIndex).getType())) { + Collection values = (Collection) ((Constant) expr.getArg(constIndex)).getConstant(); + for (Object value : values) { + boolQuery.should(QueryBuilders.queryStringQuery(StringUtils.toString(value)).field(key)); + } + return boolQuery; + + } + } + + } else if (op == Ops.NOT_IN) { + // Decompose the query as NOT and IN query + return visit( + Expressions.predicate( + Ops.NOT, + Expressions.predicate( + Ops.IN, + expr.getArg(1))), + context); + + } else if (op == Ops.BETWEEN) { + Object from = asDBValue(expr, 1); + Object to = asDBValue(expr, 2); + return QueryBuilders.rangeQuery(asDBKey(expr, 0)).from(from).to(to); + + } else if (op == Ops.LT) { + return QueryBuilders.rangeQuery(asDBKey(expr, 0)).lt(asDBValue(expr, 1)); + + } else if (op == Ops.GT) { + return QueryBuilders.rangeQuery(asDBKey(expr, 0)).gt(asDBValue(expr, 1)); + + } else if (op == Ops.LOE) { + return QueryBuilders.rangeQuery(asDBKey(expr, 0)).lte(asDBValue(expr, 1)); + + } else if (op == Ops.GOE) { + return QueryBuilders.rangeQuery(asDBKey(expr, 0)).gte(asDBValue(expr, 1)); + + } else if (op == Ops.STARTS_WITH) { + // Currently all queries are made with ignore case sensitive + String value = StringUtils.toString(asDBValue(expr, 1)); + return QueryBuilders.queryStringQuery(value + "*").field(asDBKey(expr, 0)).analyzeWildcard(true); + + } else if (op == Ops.STARTS_WITH_IC) { + String value = StringUtils.toString(asDBValue(expr, 1)); + return QueryBuilders.queryStringQuery(value + "*").field(asDBKey(expr, 0)).analyzeWildcard(true); + + } else if (op == Ops.ENDS_WITH) { + // Currently all queries are made with ignore case sensitive + String value = StringUtils.toString(asDBValue(expr, 1)); + return QueryBuilders.queryStringQuery("*" + value).field(asDBKey(expr, 0)).analyzeWildcard(true); + + } else if (op == Ops.ENDS_WITH_IC) { + String value = StringUtils.toString(asDBValue(expr, 1)); + return QueryBuilders.queryStringQuery("*" + value).field(asDBKey(expr, 0)).analyzeWildcard(true); + + } else if (op == Ops.STRING_CONTAINS) { + String value = StringUtils.toString(asDBValue(expr, 1)); + return QueryBuilders.queryStringQuery("*" + value + "*").field(asDBKey(expr, 0)).analyzeWildcard(true); + + } else if (op == Ops.STRING_CONTAINS_IC) { + String value = StringUtils.toString(asDBValue(expr, 1)); + return QueryBuilders.queryStringQuery("*" + value + "*").field(asDBKey(expr, 0)).analyzer("standard").analyzeWildcard(true); + + } else if (op == Ops.NOT) { + // Handle the not's child + BoolQueryBuilder subContext = QueryBuilders.boolQuery(); + QueryBuilder result = (QueryBuilder) expr.getArg(0).accept(this, subContext); + if (result == null) { + result = subContext; + } + return QueryBuilders.boolQuery().mustNot(result); + + } + + throw new UnsupportedOperationException("Illegal operation " + expr); + } + + @Nullable + @Override + public Object visit(ParamExpression expr, @Nullable BoolQueryBuilder context) { + return null; + } + + @Nullable + @Override + public Object visit(Path expr, @Nullable BoolQueryBuilder context) { + PathMetadata metadata = expr.getMetadata(); + return getKeyForPath(expr, metadata); + } + + @Nullable + @Override + public Object visit(SubQueryExpression expr, @Nullable BoolQueryBuilder context) { + return null; + } + + @Nullable + @Override + public Object visit(TemplateExpression expr, @Nullable BoolQueryBuilder context) { + return null; + } + + protected String getKeyForPath(Path expr, PathMetadata metadata) { + if (isIdPath(expr)) { + return "_id"; + } else { + return metadata.getElement().toString(); + } + } + + protected boolean isIdPath(Path expr) { + return "id".equals(expr.getMetadata().getElement().toString()); + } + + private QueryBuilder visitSubAndOr(Operator op, BoolQueryBuilder context, Operation subOperation) { + QueryBuilder result; + if (AND_OR.contains(subOperation.getOperator()) && subOperation.getOperator() != op) { + // Opposite case, if current operator is an AND so sub operation is a OR, so create a sub query + result = QueryBuilders.boolQuery(); + subOperation.accept(this, (BoolQueryBuilder) result); + } else { + // Here let's do recursive if sub operation has the same operator than the current one (result is null) + // or it's another operator than AND/OR so add it to query + result = (QueryBuilder) subOperation.accept(this, context); + } + return result; + } + + private void safeMust(BoolQueryBuilder context, QueryBuilder query) { + if (query != null) { + context.must(query); + } + } + + private void safeShould(BoolQueryBuilder context, QueryBuilder query) { + if (query != null) { + context.should(query); + } + } + +} diff --git a/querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/jackson/JacksonElasticsearchQueries.java b/querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/jackson/JacksonElasticsearchQueries.java new file mode 100644 index 0000000..0a1aca9 --- /dev/null +++ b/querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/jackson/JacksonElasticsearchQueries.java @@ -0,0 +1,114 @@ +package com.querydsl.elasticsearch2.jackson; + +import java.io.IOException; +import java.lang.reflect.Field; + +import javax.annotation.Nullable; + +import org.elasticsearch.client.Client; +import org.elasticsearch.search.SearchHit; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Function; +import com.querydsl.elasticsearch2.ElasticsearchQuery; +import com.querydsl.elasticsearch2.ElasticsearchSerializer; + +/** + * JacksonElasticsearchQueries is a factory to provide ElasticsearchQuery basic implementation. + * + * @author Kevin Leturc + */ +public class JacksonElasticsearchQueries { + + private final Client client; + + /** + * Default constructor. + * + * @param client The elasticsearch client. + */ + public JacksonElasticsearchQueries(Client client) { + this.client = client; + } + + public ElasticsearchQuery query(Class entityClass, String index, String type) { + return query(entityClass, index, type, new ElasticsearchSerializer()); + } + + public ElasticsearchQuery query(Class entityClass, String index, String type, ElasticsearchSerializer serializer) { + return query(index, type, serializer, defaultTransformer(entityClass)); + } + + public ElasticsearchQuery query(String index, String type, Function transformer) { + return query(index, type, new ElasticsearchSerializer(), transformer); + } + + public ElasticsearchQuery query(final String index, final String type, ElasticsearchSerializer serializer, Function transformer) { + return new ElasticsearchQuery(client, transformer, serializer) { + + /** + * {@inheritDoc} + */ + @Override + public String getIndex() { + return index; + } + + /** + * {@inheritDoc} + */ + @Override + public String getType() { + return type; + } + + }; + } + + /** + * Returns the default transformer. + * + * @param entityClass The entity class. + * @param The entity type. + * @return The default transformer. + */ + private Function defaultTransformer(final Class entityClass) { + final ObjectMapper mapper = new ObjectMapper(); + return new Function() { + + /** + * {@inheritDoc} + */ + @Nullable + @Override + public K apply(@Nullable SearchHit input) { + try { + K bean = mapper.readValue(input.getSourceAsString(), entityClass); + + Field idField = null; + Class target = entityClass; + while (idField == null && target != Object.class) { + for (Field field : target.getDeclaredFields()) { + if ("id".equals(field.getName())) { + idField = field; + } + } + target = target.getSuperclass(); + } + if (idField != null) { + idField.setAccessible(true); + idField.set(bean, input.getId()); + } + + return bean; + } catch (SecurityException se) { + throw new MappingException("Unable to lookup id field, may be use a custom transformer ?", se); + } catch (IllegalAccessException e) { + throw new MappingException("Unable to set id value in id field, may be use a custom transformer ?", e); + } catch (IOException e) { + throw new MappingException("Unable to read the Elasticsearch response.", e); + } + } + }; + } +} diff --git a/querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/jackson/MappingException.java b/querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/jackson/MappingException.java new file mode 100644 index 0000000..fd2d041 --- /dev/null +++ b/querydsl-elasticsearch2/src/main/java/com/querydsl/elasticsearch2/jackson/MappingException.java @@ -0,0 +1,20 @@ +package com.querydsl.elasticsearch2.jackson; + +/** + * Mapping exception for factory purposes. + * + * @author Kevin Leturc + */ +public class MappingException extends RuntimeException { + + /** + * Default constructor. + * + * @param message The message. + * @param cause The cause. + */ + public MappingException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/ElasticsearchQueryTest.java b/querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/ElasticsearchQueryTest.java new file mode 100644 index 0000000..6653728 --- /dev/null +++ b/querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/ElasticsearchQueryTest.java @@ -0,0 +1,315 @@ +package com.querydsl.elasticsearch2; + +import com.querydsl.elasticsearch2.ElasticsearchQuery; +import static java.util.Arrays.asList; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; +import static org.elasticsearch.client.Requests.refreshRequest; +import static org.junit.Assert.*; + +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; +import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; +import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.Requests; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.node.Node; +import org.elasticsearch.node.NodeBuilder; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; +import com.querydsl.core.NonUniqueResultException; +import com.querydsl.core.QueryResults; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Predicate; +import com.querydsl.elasticsearch2.domain.QUser; +import com.querydsl.elasticsearch2.domain.User; +import com.querydsl.elasticsearch2.jackson.JacksonElasticsearchQueries; + +public class ElasticsearchQueryTest { + + private static final ObjectMapper mapper = new ObjectMapper(); + + private static Client client; + + private final String indexUser = "index1"; + private final String typeUser = "user"; + + private final QUser user = QUser.user; + List users = Lists.newArrayList(); + User u1, u2, u3, u4; + + public ElasticsearchQueryTest() { + } + + @BeforeClass + public static void beforeClass() { + String path = ElasticsearchQueryTest.class.getResource("").getPath(); + path = System.getProperty("os.name").contains("indow") ? path.substring(1) : path; + Settings.Builder settings = Settings.builder().put("path.home",path); + Node node = NodeBuilder.nodeBuilder().local(true).settings(settings).node(); + client = node.client(); + + } + + @Before + public void before() { + deleteType(indexUser); + createIndex(indexUser); + + u1 = addUser("Jaakko", "Jantunen", 20); + u2 = addUser("Jaakki", "Jantunen", 30); + u3 = addUser("Jaana", "Aakkonen", 40); + u4 = addUser("Jaana", "BeekkoNen", 50); + + refresh(indexUser, false); + } + + @Test + public void Count() { + assertEquals(4, query().fetchCount()); + } + + @Test + public void Count_Predicate() { + assertEquals(2, where(user.lastName.eq("Jantunen")).fetchCount()); + } + + @Test + public void SingleResult_Keys() { + User u = where(user.firstName.eq("Jaakko")).fetchFirst(user.firstName); + assertEquals("Jaakko", u.getFirstName()); + assertNull(u.getLastName()); + assertEquals(0, u.getAge()); + } + + @Test + public void UniqueResult_Keys() { + User u = where(user.firstName.eq("Jaakko")).fetchOne(user.firstName); + assertEquals("Jaakko", u.getFirstName()); + assertNull(u.getLastName()); + assertEquals(0, u.getAge()); + } + + @Test(expected = NonUniqueResultException.class) + public void UniqueResult_Keys_Non_Unique() { + where(user.firstName.eq("Jaana")).fetchOne(user.firstName); + } + + @Test + public void Contains() { + //assertQuery(user.friends.contains(u1), u3, u4, u2); + } + + @Test + public void Contains2() { + //assertQuery(user.friends.contains(u4)); + } + + @Test + public void NotContains() { + //assertQuery(user.friends.contains(u1).not(), u1); + } + + @Test + public void Contains_Ignore_Case() { + assertTrue(where(user.firstName.containsIgnoreCase("akk")).fetchCount() > 0); + } + + @Test + public void Contains_Ignore_Case_2() { + assertFalse(where(user.firstName.containsIgnoreCase("xyzzz")).fetchCount() > 0); + } + + @Test + public void Equals_Ignore_Case() { + assertTrue(where(user.firstName.equalsIgnoreCase("jAaKko")).fetchCount() > 0); + assertTrue(where(user.firstName.equalsIgnoreCase("AaKk")).fetchCount() == 0); + } + + @Test + public void Starts_With_and_Between() { + assertQuery(user.firstName.startsWith("Jaa").and(user.age.between(20, 30)), u2, u1); + assertQuery(user.firstName.startsWith("Jaa").and(user.age.goe(20).and(user.age.loe(30))), u2, u1); + } + + @Test + public void Exists() { + assertTrue(where(user.firstName.eq("Jaakko")).fetchCount() > 0); + assertTrue(where(user.firstName.eq("JaakkoX")).fetchCount() == 0); + assertTrue(where(user.id.eq(u1.getId())).fetchCount() > 0); + } + + @Test + public void Find_By_Id() { + assertNotNull(where(user.id.eq(u1.getId())).fetchOne()); + } + + @Test + public void Find_By_Ids() { + assertQuery(user.id.in(u1.getId(), u2.getId()), u2, u1); + } + + @Test + public void Order() { + List users = query().orderBy(user.age.asc()).fetch(); + assertEquals(asList(u1, u2, u3, u4), users); + + users = query().orderBy(user.age.desc()).fetch(); + assertEquals(asList(u4, u3, u2, u1), users); + } + + @Test + public void ListResults() { + QueryResults results = query().limit(2).orderBy(user.age.asc()).fetchResults(); + assertEquals(4L, results.getTotal()); + assertEquals(2, results.getResults().size()); + + results = query().offset(2).orderBy(user.age.asc()).fetchResults(); + assertEquals(4L, results.getTotal()); + assertEquals(2, results.getResults().size()); + } + + @Test + public void EmptyResults() { + QueryResults results = query().where(user.firstName.eq("XXX")).fetchResults(); + assertEquals(0L, results.getTotal()); + assertEquals(Collections.emptyList(), results.getResults()); + } + + @Test + public void EqInAndOrderByQueries() { + assertQuery(user.firstName.eq("Jaakko"), u1); + assertQuery(user.firstName.equalsIgnoreCase("jaakko"), u1); + assertQuery(user.lastName.eq("Aakkonen"), u3); + + assertQuery(user.firstName.in("Jaakko","Teppo"), u1); + assertQuery(user.lastName.in("Aakkonen", "BeekkoNen"), u3, u4); + + assertQuery(user.firstName.eq("Jouko")); + + assertQuery(user.firstName.eq("Jaana"), user.lastName.asc(), u3, u4); + assertQuery(user.firstName.eq("Jaana"), user.lastName.desc(), u4, u3); + assertQuery(user.lastName.eq("Jantunen"), user.firstName.asc(), u2, u1); + assertQuery(user.lastName.eq("Jantunen"), user.firstName.desc(), u1, u2); + + assertQuery(user.firstName.eq("Jaana").and(user.lastName.eq("Aakkonen")), u3); + //This shoud produce 'and' also + assertQuery(where(user.firstName.eq("Jaana"), user.lastName.eq("Aakkonen")), u3); + + assertQuery(user.firstName.ne("Jaana"), u2, u1); + assertQuery(user.firstName.ne("Jaana").and(user.lastName.ne("Jantunen"))); + assertQuery(user.firstName.eq("Jaana").and(user.lastName.eq("Aakkonen")).not(), u4, u2, u1); + + } + + @Test + public void Iterate() { + User a = addUser("A", "A", 10); + User b = addUser("A1", "B", 10); + User c = addUser("A2", "C", 10); + + refresh(indexUser, false); + + Iterator i = where(user.firstName.startsWith("A")) + .orderBy(user.firstName.asc()) + .iterate(); + + assertEquals(a, i.next()); + assertEquals(b, i.next()); + assertEquals(c, i.next()); + assertEquals(false, i.hasNext()); + } + + @Test + public void Enum_Eq() { + assertQuery(user.gender.eq(User.Gender.MALE), u3, u4, u2, u1); + } + + @Test + public void Enum_Ne() { + assertQuery(user.gender.ne(User.Gender.MALE)); + } + + private ElasticsearchQuery query() { + return new JacksonElasticsearchQueries(client).query(User.class, indexUser, typeUser); + } + + private ElasticsearchQuery where(Predicate predicate) { + return query().where(predicate); + } + + private ElasticsearchQuery where(Predicate ... e) { + return query().where(e); + } + + private void assertQuery(Predicate e, User ... expected) { + assertQuery(where(e).orderBy(user.lastName.asc(), user.firstName.asc()), expected); + } + + private void assertQuery(Predicate e, OrderSpecifier orderBy, User ... expected) { + assertQuery(where(e).orderBy(orderBy), expected); + } + + private void assertQuery(ElasticsearchQuery query, User ... expected) { + List results = query.fetch(); + + assertNotNull(results); + if (expected == null) { + assertEquals("Should get empty result", 0, results.size()); + return; + } + assertEquals(expected.length, results.size()); + int i = 0; + for (User u : expected) { + assertEquals(u, results.get(i++)); + } + } + + private User addUser(String first, String last, int age) { + User user = new User(first, last, age, new Date()); + user.setGender(User.Gender.MALE); + try { + IndexResponse response = client.prepareIndex(indexUser, typeUser).setSource(mapper.writeValueAsString(user)).execute().actionGet(); + user.setId(response.getId()); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + users.add(user); + return user; + } + + public void deleteType(String index) { + if (indexExists(index)) { + client.admin().indices().delete(new DeleteIndexRequest(index)).actionGet(); + } + } + + public boolean indexExists(String index) { + return client.admin().indices().exists(Requests.indicesExistsRequest(index)).actionGet().isExists(); + } + + public boolean createIndex(String index) { + if (indexExists(index)) { + return true; + } + + CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(index); + return createIndexRequestBuilder.execute().actionGet().isAcknowledged(); + } + + public void refresh(String indexName, boolean waitForOperation) { + client.admin().indices().refresh(refreshRequest(indexName)).actionGet(); + } + +} diff --git a/querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/ElasticsearchSerializerTest.java b/querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/ElasticsearchSerializerTest.java new file mode 100644 index 0000000..ce8ee46 --- /dev/null +++ b/querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/ElasticsearchSerializerTest.java @@ -0,0 +1,316 @@ +/* +* Copyright 2011, Mysema Ltd +* +* Licensed 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 com.querydsl.elasticsearch2; + +import com.querydsl.elasticsearch2.ElasticsearchSerializer; +import static org.elasticsearch.index.query.QueryBuilders.*; +import static org.junit.Assert.assertEquals; + +import java.sql.Timestamp; +import java.util.Date; + +import org.apache.lucene.queryparser.flexible.core.util.StringUtils; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.Lists; +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.dsl.*; +import com.querydsl.elasticsearch2.domain.QUser; + +public class ElasticsearchSerializerTest { + + private PathBuilder entityPath; + private StringPath id; + private StringPath title; + private NumberPath year; + private NumberPath gross; + + private NumberPath longField; + private NumberPath shortField; + private NumberPath byteField; + private NumberPath floatField; + + private DatePath date; + private final Date dateVal = new Date(); + private DateTimePath dateTime; + private final Timestamp dateTimeVal = new Timestamp(System.currentTimeMillis()); + + private ElasticsearchSerializer serializer; + + @Before + public void before() { + serializer = new ElasticsearchSerializer(); + entityPath = new PathBuilder(Object.class, "obj"); + id = entityPath.getString("id"); + title = entityPath.getString("title"); + year = entityPath.getNumber("year", Integer.class); + gross = entityPath.getNumber("gross", Double.class); + longField = entityPath.getNumber("longField", Long.class); + shortField = entityPath.getNumber("shortField", Short.class); + byteField = entityPath.getNumber("byteField", Byte.class); + floatField = entityPath.getNumber("floatField", Float.class); + date = entityPath.getDate("date", Date.class); + dateTime = entityPath.getDateTime("dateTime", Timestamp.class); + } + + @Test + public void Paths() { + QUser user = QUser.user; + assertEquals("user", serializer.visit(user, null)); + //assertEquals("addresses", serializer.visit(user.addresses, null)); + //assertEquals("addresses", serializer.visit(user.addresses.any(), null)); + //assertEquals("addresses.street", serializer.visit(user.addresses.any().street, null)); + assertEquals("firstName", serializer.visit(user.firstName, null)); + } + + @Test + public void PropertyAnnotation() { + //QDummyEntity entity = QDummyEntity.dummyEntity; + //assertEquals("prop", serializer.visit(entity.property, null)); + } + + @Test + public void IndexedAccess() { + QUser user = QUser.user; + //assertEquals("addresses.0.street", serializer.visit(user.addresses.get(0).street, null)); + } + + @Test + public void CollectionAny() { + QUser user = QUser.user; + //assertQuery(eq("addresses.street", "Aakatu"), user.addresses.any().street.eq("Aakatu")); + } + + @Test + public void Equals() { + assertQuery(and(eq("title", "A")), title.eq("A")); + assertQuery(and(eq("year", 1)), year.eq(1)); + assertQuery(and(eq("gross", 1.0D)), gross.eq(1.0D)); + assertQuery(and(eq("longField", 1L)), longField.eq(1L)); + assertQuery(and(eq("shortField", 1)), shortField.eq((short) 1)); + assertQuery(and(eq("byteField", 1L)), byteField.eq((byte) 1)); + assertQuery(and(eq("floatField", 1.0F)), floatField.eq(1.0F)); + + assertQuery(and(eq("date", dateVal)), date.eq(dateVal)); + assertQuery(and(eq("dateTime", dateTimeVal)), dateTime.eq(dateTimeVal)); + + assertQuery(and(idsQuery().ids("id1")), id.eq("id1")); + } + + @Test + public void EqAndEq() { + assertQuery( + and(eq("title", "A"), eq("year", 1)), + title.eq("A").and(year.eq(1)) + ); + + assertQuery( + and(eq("year", 1), eq("gross", 1.0D), eq("title", "A")), + title.eq("A").and(year.eq(1).and(gross.eq(1.0D))) + ); + + assertQuery( + and(eq("title", "A"), eq("year", 1), eq("gross", 1.0D)), + title.eq("A").and(year.eq(1)).and(gross.eq(1.0D)) + ); + } + + @Test + public void EqOrEq() { + assertQuery( + or(eq("title", "A"), eq("year", 1)), + title.eq("A").or(year.eq(1)) + ); + + assertQuery( + or(eq("year", 1), eq("gross", 1.0D), eq("title", "A")), + title.eq("A").or(year.eq(1).or(gross.eq(1.0D))) + ); + + assertQuery( + or(eq("title", "A"), eq("year", 1), eq("gross", 1.0D)), + title.eq("A").or(year.eq(1)).or(gross.eq(1.0D)) + ); + } + + @Test + public void In() { + assertQuery( + and(in("title", Lists.newArrayList("A", "B", "C"))), + title.in(Lists.newArrayList("A", "B", "C")) + ); + } + + @Test + public void Between() { + Date start = new Date(31L * 24L * 60L * 60L * 1000L); + Date end = new Date(); + assertQuery( + and(between("date", start, end)), + date.between(start, end) + ); + + assertQuery( + and(between("year", 1, 2)), + year.between(1, 2) + ); + } + + @Test + public void InAndBetween() { + Date start = new Date(31L * 24L * 60L * 60L * 1000L); + Date end = new Date(); + assertQuery( + and(in("title", Lists.newArrayList("A", "B", "C")), between("date", start, end)), + title.in(Lists.newArrayList("A", "B", "C")).and(date.between(start, end)) + ); + } + + @Test + public void EqOrEqAndEq() { + + assertQuery( + or(eq("title", "A"), and(eq("year", 1), eq("gross", 1.0D))), + title.eq("A").or(year.eq(1).and(gross.eq(1.0D))) + ); + + assertQuery( + and( + or(eq("title", "A"), eq("year", 1)), + eq("gross", 1.0D)), + title.eq("A").or(year.eq(1)).and(gross.eq(1.0D)) + ); + } + + @Test + public void EqAndEqOrEq() { + + assertQuery( + and( + eq("title", "A"), + or(eq("year", 1), eq("gross", 1.0D))), + title.eq("A").and(year.eq(1).or(gross.eq(1.0D))) + ); + + assertQuery( + or( + and(eq("title", "A"), eq("year", 1)), + eq("gross", 1.0D)), + title.eq("A").and(year.eq(1)).or(gross.eq(1.0D)) + ); + } + + @Test + public void EqAndEqOrEqAndEq() { + + assertQuery( + and( + or( + and(eq("title", "A"), eq("year", 1)), + eq("title", "B")), + eq("year", 2) + ), + title.eq("A").and(year.eq(1)).or(title.eq("B")).and(year.eq(2)) + ); + + assertQuery( + or( + and(eq("title", "A"), eq("year", 1)), + and(eq("title", "B"), eq("year", 2)) + ), + title.eq("A").and(year.eq(1)).or(title.eq("B").and(year.eq(2))) + ); + + assertQuery( + and( + or( + eq("year", 1), + eq("title", "B")), + eq("year", 2), + eq("title", "A")), + title.eq("A").and(year.eq(1).or(title.eq("B")).and(year.eq(2))) + ); + + assertQuery( + and( + eq("title", "A"), + or( + eq("year", 1), + and(eq("title", "B"), eq("year", 2)))), + title.eq("A").and(year.eq(1).or(title.eq("B").and(year.eq(2)))) + ); + + } + + public static QueryBuilder eq(String key, Object value) { + return QueryBuilders.queryStringQuery(StringUtils.toString(value)).field(key); + } + + public static QueryBuilder in(String key, Iterable values) { + BoolQueryBuilder query = boolQuery(); + for (Object value : values) { + query.should(eq(key, value)); + } + return query; + } + + public static QueryBuilder and(QueryBuilder... builders) { + BoolQueryBuilder query = boolQuery(); + must(query, builders); + return query; + } + + public static QueryBuilder or(QueryBuilder... builders) { + BoolQueryBuilder query = boolQuery(); + should(query, builders); + return query; + } + + public static BoolQueryBuilder must(BoolQueryBuilder query, QueryBuilder... builders) { + for (QueryBuilder builder : builders) { + query.must(builder); + } + return query; + } + + public static BoolQueryBuilder should(BoolQueryBuilder query, QueryBuilder... builders) { + for (QueryBuilder builder : builders) { + query.should(builder); + } + return query; + } + + public static QueryBuilder between(String key, int start, int end) { + return rangeQuery(key).from(start).to(end); + } + + public static QueryBuilder between(String key, double start, double end) { + return rangeQuery(key).from(start).to(end); + } + + public static QueryBuilder between(String key, Date start, Date end) { + return rangeQuery(key).from(start).to(end); + } + + private void assertQuery(QueryBuilder expected, Expression e) { + QueryBuilder result = (QueryBuilder) serializer.handle(e); + assertEquals(expected.toString(), result.toString()); + } + + +} diff --git a/querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/PackageVerification.java b/querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/PackageVerification.java new file mode 100644 index 0000000..9c0b08a --- /dev/null +++ b/querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/PackageVerification.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014, Mysema Ltd + * + * Licensed 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 com.querydsl.elasticsearch2; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; + +import org.junit.Test; + +import com.google.common.base.Charsets; +import com.google.common.io.Resources; +import com.mysema.codegen.CodeWriter; +import com.querydsl.apt.QuerydslAnnotationProcessor; +import com.querydsl.codegen.CodegenModule; +import com.querydsl.core.types.Expression; + +public class PackageVerification { + + @Test + public void Verify_Package() throws Exception { + String version = System.getProperty("version"); + verify(new File("target/querydsl-elasticsearch2-" + version + "-apt-one-jar.jar")); + } + + private void verify(File oneJar) throws Exception { + assertTrue(oneJar.getPath() + " doesn't exist", oneJar.exists()); + // verify classLoader + URLClassLoader oneJarClassLoader = new URLClassLoader(new URL[]{oneJar.toURI().toURL()}); + oneJarClassLoader.loadClass(Expression.class.getName()); // querydsl-core + oneJarClassLoader.loadClass(CodeWriter.class.getName()); // codegen + oneJarClassLoader.loadClass(CodegenModule.class.getName()).newInstance(); + //oneJarClassLoader.loadClass(Entity.class.getName()); // elasticsearch + Class cl = oneJarClassLoader.loadClass(QuerydslAnnotationProcessor.class.getName()); // querydsl-apt + cl.newInstance(); + String resourceKey = "META-INF/services/javax.annotation.processing.Processor"; + assertEquals(QuerydslAnnotationProcessor.class.getName(), Resources.toString(oneJarClassLoader.findResource(resourceKey), Charsets.UTF_8)); + } + +} diff --git a/querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/domain/AbstractEntity.java b/querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/domain/AbstractEntity.java new file mode 100644 index 0000000..48b8515 --- /dev/null +++ b/querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/domain/AbstractEntity.java @@ -0,0 +1,37 @@ +package com.querydsl.elasticsearch2.domain; + +import com.google.common.base.Objects; +import com.querydsl.core.annotations.QuerySupertype; + +@QuerySupertype +public abstract class AbstractEntity { + + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public int hashCode() { + return id != null ? 0 : id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof AbstractEntity) { + AbstractEntity other = (AbstractEntity) obj; + return Objects.equal(other.getId(), id); + } else { + return false; + } + } + + +} diff --git a/querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/domain/User.java b/querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/domain/User.java new file mode 100644 index 0000000..7e9b992 --- /dev/null +++ b/querydsl-elasticsearch2/src/test/java/com/querydsl/elasticsearch2/domain/User.java @@ -0,0 +1,86 @@ +package com.querydsl.elasticsearch2.domain; + +import java.util.Date; + +import com.querydsl.core.annotations.QueryEntity; + +@QueryEntity +public class User extends AbstractEntity { + + public enum Gender { MALE, FEMALE } + + private String firstName; + + private String lastName; + + private Date created; + + private Gender gender; + + //@QueryEmbedded + //private final List
addresses = new ArrayList
(); + + //@QueryEmbedded + //private Address mainAddress; + + private int age; + + public User() { + } + + public User(String firstName, String lastName) { + this.firstName = firstName; this.lastName = lastName; + this.created = new Date(); + } + + public User(String firstName, String lastName, int age, Date created) { + this.firstName = firstName; this.lastName = lastName; this.age = age; this.created = created; + } + + @Override + public String toString() { + return "TestUser [id=" + getId() + ", firstName=" + firstName + ", lastName=" + lastName + + "]"; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public Gender getGender() { + return gender; + } + + public void setGender(Gender gender) { + this.gender = gender; + } + +}