├── query-builder-flow-chart.jpg ├── src ├── main │ └── java │ │ └── cn │ │ └── thare │ │ └── feqb │ │ ├── able │ │ └── Sortable.java │ │ ├── enums │ │ └── SortOrder.java │ │ ├── annotation │ │ ├── func │ │ │ ├── Page.java │ │ │ ├── aggs │ │ │ │ ├── Aggregation.java │ │ │ │ ├── StatsAggregation.java │ │ │ │ ├── ExtendedStatsAggregation.java │ │ │ │ ├── CardinalityAggregation.java │ │ │ │ └── TermsAggregation.java │ │ │ ├── PageNo.java │ │ │ ├── PageSize.java │ │ │ ├── Source.java │ │ │ ├── Sort.java │ │ │ └── Highlighters.java │ │ └── query │ │ │ ├── Must.java │ │ │ ├── Query.java │ │ │ ├── Filter.java │ │ │ ├── MustNot.java │ │ │ ├── Should.java │ │ │ ├── Or.java │ │ │ ├── OrNot.java │ │ │ └── type │ │ │ ├── Term.java │ │ │ ├── Wildcard.java │ │ │ ├── Exists.java │ │ │ ├── Terms.java │ │ │ ├── MatchPhrase.java │ │ │ ├── Range.java │ │ │ └── Match.java │ │ ├── helper │ │ ├── SourceHelper.java │ │ ├── SortHelper.java │ │ ├── HighlightHelper.java │ │ ├── PageHelper.java │ │ ├── PageTool.java │ │ ├── AggregationHelper.java │ │ └── QueryHelper.java │ │ └── builder │ │ ├── BaseQueryBuilder.java │ │ └── AbstractQueryBuilder.java └── test │ └── java │ └── cn │ └── thare │ └── feqb │ └── test │ ├── StuFieldName.java │ ├── StuSortEnum.java │ ├── TestQueryBuilder.java │ ├── StuSearchCriteria.java │ └── StuQueryBuilder.java ├── .gitignore ├── pom.xml ├── README.zh-cn.md ├── README.md └── LICENSE /query-builder-flow-chart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thare-Lam/fast-elasticsearch-query-builder/HEAD/query-builder-flow-chart.jpg -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/able/Sortable.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.able; 2 | 3 | import cn.thare.feqb.enums.SortOrder; 4 | 5 | public interface Sortable { 6 | 7 | String fieldName(); 8 | 9 | SortOrder order(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/enums/SortOrder.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.enums; 2 | 3 | public enum SortOrder { 4 | 5 | ASC("asc"), DESC("desc"); 6 | 7 | private String value; 8 | 9 | SortOrder(String value) { 10 | this.value = value; 11 | } 12 | 13 | public String value() { 14 | return this.value; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/func/Page.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.func; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.ANNOTATION_TYPE}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Page { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/query/Must.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.query; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Query 9 | @Target({ElementType.FIELD}) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | public @interface Must { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/query/Query.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.query; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.ANNOTATION_TYPE}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Query { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/query/Filter.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.query; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Query 9 | @Target({ElementType.FIELD}) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | public @interface Filter { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/query/MustNot.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.query; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Query 9 | @Target({ElementType.FIELD}) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | public @interface MustNot { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/query/Should.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.query; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Query 9 | @Target({ElementType.FIELD}) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | public @interface Should { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/func/aggs/Aggregation.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.func.aggs; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.ANNOTATION_TYPE}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Aggregation { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/func/PageNo.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.func; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Field's type must be {@link Integer} 10 | */ 11 | @Page 12 | @Target({ElementType.FIELD}) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface PageNo { 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/func/PageSize.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.func; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Field's type must be {@link Integer} 10 | */ 11 | @Page 12 | @Target({ElementType.FIELD}) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface PageSize { 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/helper/SourceHelper.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.helper; 2 | 3 | import org.elasticsearch.search.builder.SearchSourceBuilder; 4 | 5 | import java.util.Collection; 6 | 7 | public class SourceHelper { 8 | 9 | @SuppressWarnings("unchecked") 10 | public static void set(SearchSourceBuilder searchSource, Object value) { 11 | searchSource.fetchSource(((Collection) value).toArray(new String[0]), new String[0]); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/func/Source.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.func; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Field's type must be {@link java.util.Collection}<{@link String}> 10 | */ 11 | @Target({ElementType.FIELD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Source { 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/func/Sort.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.func; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Field's type must be {@link java.util.List}<{@link cn.thare.feqb.able.Sortable}> 10 | */ 11 | @Target({ElementType.FIELD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Sort { 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | *.versionsBackup 23 | 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | 27 | # idea 28 | *.iml 29 | .idea 30 | target/ 31 | out/ 32 | 33 | # mac 34 | *.DS_Store 35 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/func/Highlighters.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.func; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Field's type must be {@link java.util.Collection}<{@link String}> 10 | */ 11 | @Target({ElementType.FIELD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Highlighters { 14 | 15 | String type() default ""; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/cn/thare/feqb/test/StuFieldName.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.test; 2 | 3 | public class StuFieldName { 4 | 5 | public final static String NAME = "name"; 6 | 7 | public final static String AGE = "age"; 8 | 9 | public final static String TAG = "tag"; 10 | 11 | public final static String ADDRESS = "address"; 12 | 13 | public final static String MATH_SCORE = "mathScore"; 14 | 15 | public final static String INTERNSHIP = "internship"; 16 | 17 | public final static String INTRODUCE = "introduce"; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/query/Or.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.query; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Will be converted to a bool.should clause under the filter context 10 | * At least one clause should be satisfied 11 | * Mapped to "or" query in sql 12 | */ 13 | @Query 14 | @Target({ElementType.FIELD}) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface Or { 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/query/OrNot.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.query; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Will be converted to a bool.should.bool.must_not clause under the Filter context 10 | * At least one clause should be satisfied 11 | * Mapped to "or not" query in sql 12 | */ 13 | @Query 14 | @Target({ElementType.FIELD}) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface OrNot { 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/query/type/Term.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.query.type; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.FIELD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Term { 11 | 12 | /** 13 | * Query's field name 14 | * 15 | * @return query's field name. If return "", the name will be the DTO 's field name. 16 | */ 17 | String fieldName() default ""; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/func/aggs/StatsAggregation.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.func.aggs; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Field's type must be {@link Boolean} 10 | *

Implied whether handle StatsAggregation 11 | */ 12 | @Aggregation 13 | @Target({ElementType.FIELD}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface StatsAggregation { 16 | 17 | String name(); 18 | 19 | String field(); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/query/type/Wildcard.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.query.type; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.FIELD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Wildcard { 11 | 12 | /** 13 | * Query's field name 14 | * 15 | * @return query's field name. If return "", the name will be the DTO 's field name. 16 | */ 17 | String fieldName() default ""; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/helper/SortHelper.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.helper; 2 | 3 | import cn.thare.feqb.able.Sortable; 4 | import org.elasticsearch.search.builder.SearchSourceBuilder; 5 | import org.elasticsearch.search.sort.SortOrder; 6 | 7 | import java.util.List; 8 | 9 | public class SortHelper { 10 | 11 | @SuppressWarnings("unchecked") 12 | public static void set(SearchSourceBuilder searchSource, Object value) { 13 | ((List) value).forEach(sortable -> 14 | searchSource.sort(sortable.fieldName(), SortOrder.fromString(sortable.order().value()))); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/func/aggs/ExtendedStatsAggregation.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.func.aggs; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Field's type must be {@link Boolean} 10 | *

Implied whether handle ExtendedStatsAggregation 11 | */ 12 | @Aggregation 13 | @Target({ElementType.FIELD}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface ExtendedStatsAggregation { 16 | 17 | String name(); 18 | 19 | String field(); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/query/type/Exists.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.query.type; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Field's type must be {@link Boolean} 10 | */ 11 | @Target({ElementType.FIELD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Exists { 14 | 15 | /** 16 | * Query's field name 17 | * 18 | * @return query's field name. If return "", the name will be the DTO 's field name. 19 | */ 20 | String fieldName() default ""; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/func/aggs/CardinalityAggregation.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.func.aggs; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Field's type must be {@link Boolean} 10 | *

Implied whether handle CardinalityAggregation 11 | */ 12 | @Aggregation 13 | @Target({ElementType.FIELD}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface CardinalityAggregation { 16 | 17 | String name(); 18 | 19 | String field(); 20 | 21 | long precisionThreshold() default -1L; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/query/type/Terms.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.query.type; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Field's type must be {@link java.util.Collection} 10 | */ 11 | @Target({ElementType.FIELD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Terms { 14 | 15 | /** 16 | * Query's field name 17 | * 18 | * @return query's field name. If return "", the name will be the DTO 's field name. 19 | */ 20 | String fieldName() default ""; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/query/type/MatchPhrase.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.query.type; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.FIELD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface MatchPhrase { 11 | 12 | /** 13 | * Query's field name 14 | * 15 | * @return query's field name. If return "", the name will be the DTO 's field name. 16 | */ 17 | String fieldName() default ""; 18 | 19 | String analyzer() default ""; 20 | 21 | int slop() default -1; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/query/type/Range.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.query.type; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Field's type must be {@link Number} 10 | */ 11 | @Target({ElementType.FIELD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Range { 14 | 15 | /** 16 | * Query's field name 17 | * 18 | * @return query's field name. If return "", the name will be the DTO 's field name. 19 | */ 20 | String fieldName() default ""; 21 | 22 | Type type(); 23 | 24 | boolean includedBoundary() default true; 25 | 26 | enum Type { 27 | FROM, TO 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/cn/thare/feqb/test/StuSortEnum.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.test; 2 | 3 | import cn.thare.feqb.able.Sortable; 4 | import cn.thare.feqb.enums.SortOrder; 5 | 6 | public enum StuSortEnum implements Sortable { 7 | 8 | SCORE_ASC("_score", SortOrder.ASC), 9 | SCORE_DESC("_score", SortOrder.DESC), 10 | MATH_SCORE_ASC(StuFieldName.MATH_SCORE, SortOrder.ASC), 11 | MATH_SCORE_DESC(StuFieldName.MATH_SCORE, SortOrder.ASC); 12 | 13 | private String fieldName; 14 | 15 | private SortOrder sortOrder; 16 | 17 | StuSortEnum(String fieldName, SortOrder sortOrder) { 18 | this.fieldName = fieldName; 19 | this.sortOrder = sortOrder; 20 | } 21 | 22 | @Override 23 | public String fieldName() { 24 | return this.fieldName; 25 | } 26 | 27 | @Override 28 | public SortOrder order() { 29 | return this.sortOrder; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/query/type/Match.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.query.type; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.FIELD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Match { 11 | 12 | /** 13 | * Query's field name 14 | * 15 | * @return query's field name. If return "", the name will be the DTO 's field name. 16 | */ 17 | String fieldName() default ""; 18 | 19 | Operator operator() default Operator.OR; 20 | 21 | enum Operator { 22 | AND("AND"), OR("OR"); 23 | 24 | private String value; 25 | 26 | Operator(String value) { 27 | this.value = value; 28 | } 29 | 30 | public String value() { 31 | return this.value; 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/helper/HighlightHelper.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.helper; 2 | 3 | import cn.thare.feqb.annotation.func.Highlighters; 4 | import org.elasticsearch.search.builder.SearchSourceBuilder; 5 | import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; 6 | 7 | import java.lang.annotation.Annotation; 8 | import java.util.Collection; 9 | 10 | public class HighlightHelper { 11 | 12 | @SuppressWarnings("unchecked") 13 | public static void set(SearchSourceBuilder searchSource, Annotation annotation, Object value) { 14 | Highlighters highlighters = (Highlighters) annotation; 15 | HighlightBuilder highlight = new HighlightBuilder(); 16 | String type = highlighters.type().trim(); 17 | if (type.length() > 0) { 18 | highlight.highlighterType(type); 19 | } 20 | ((Collection) value).forEach(highlight::field); 21 | searchSource.highlighter(highlight); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/annotation/func/aggs/TermsAggregation.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.annotation.func.aggs; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Field's type must be {@link Integer} 10 | *

Implied the size of aggregation set, limited by {@link TermsAggregation#maxSize()} 11 | */ 12 | @Aggregation 13 | @Target({ElementType.FIELD}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface TermsAggregation { 16 | 17 | String name(); 18 | 19 | String field(); 20 | 21 | /** 22 | * @return the max value of the size of aggregation set 23 | */ 24 | int maxSize() default Integer.MAX_VALUE; 25 | 26 | Order[] order() default {}; 27 | 28 | String executionHint() default ""; 29 | 30 | enum Order { 31 | 32 | COUNT_ASC, COUNT_DESC, KEY_ASC, KEY_DESC 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/helper/PageHelper.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.helper; 2 | 3 | import cn.thare.feqb.annotation.func.PageNo; 4 | import cn.thare.feqb.annotation.func.PageSize; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.elasticsearch.search.builder.SearchSourceBuilder; 7 | 8 | import java.lang.annotation.Annotation; 9 | import java.lang.reflect.Field; 10 | import java.util.Optional; 11 | 12 | @Slf4j 13 | public class PageHelper { 14 | 15 | private Integer pageNo; 16 | 17 | private Integer pageSize; 18 | 19 | public static PageHelper create() { 20 | return new PageHelper(); 21 | } 22 | 23 | public void fill(Annotation annotation, Field field, Object value) { 24 | if (value instanceof Integer) { 25 | if (PageNo.class == annotation.annotationType()) { 26 | pageNo = (Integer) value; 27 | } else if (PageSize.class == annotation.annotationType()) { 28 | pageSize = (Integer) value; 29 | } 30 | } else { 31 | log.warn("{}'s field type is not Integer", field.getName()); 32 | } 33 | } 34 | 35 | public void set(SearchSourceBuilder searchSource, PageTool pageTool) { 36 | Optional.ofNullable(pageTool).ifPresent(pt -> 37 | Optional.ofNullable(pt.getPageInfoSafe(pageNo, pageSize)).ifPresent(pi -> 38 | searchSource.from(pi.getFrom()).size(pi.getSize()))); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/cn/thare/feqb/test/TestQueryBuilder.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.test; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.junit.Test; 5 | 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | 9 | @Slf4j 10 | public class TestQueryBuilder { 11 | 12 | @Test 13 | public void testBuildQuery() { 14 | StuQueryBuilder queryBuilder = new StuQueryBuilder(); 15 | StuSearchCriteria searchCriteria = StuSearchCriteria.builder() 16 | .pageNo(3) 17 | .pageSize(15) 18 | .highlighters(Collections.singletonList(StuFieldName.INTRODUCE)) 19 | .sources(Arrays.asList(StuFieldName.NAME, StuFieldName.AGE, StuFieldName.MATH_SCORE)) 20 | .ageCardinalityAggregation(true) 21 | .ageExtendedStatsAggregation(true) 22 | .ageStatsAggregation(true) 23 | .ageTermsAggregation(2) 24 | .sorts(Arrays.asList(StuSortEnum.MATH_SCORE_DESC, StuSortEnum.SCORE_DESC)) 25 | .school("USTC") 26 | .tags(Arrays.asList("basketball", "swimming")) 27 | .existInternship(Boolean.TRUE) 28 | .mathScoreFrom(80) 29 | .name("ja*") 30 | .excludedAddress("Beijing") 31 | .introduce("java python") 32 | .nameMatchPhrase("jack li") 33 | .customField("customFieldValue") 34 | .build(); 35 | log.info("dsl: \n{}", queryBuilder.build(searchCriteria)); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | cn.thare 8 | fast-elasticsearch-query-builder 9 | 1.0.0-SNAPSHOT 10 | 11 | 12 | 13 | junit 14 | junit 15 | 4.12 16 | test 17 | 18 | 19 | org.elasticsearch 20 | elasticsearch 21 | 6.3.0 22 | 23 | 24 | org.projectlombok 25 | lombok 26 | 1.18.2 27 | provided 28 | 29 | 30 | ch.qos.logback 31 | logback-core 32 | 1.1.7 33 | 34 | 35 | ch.qos.logback 36 | logback-classic 37 | 1.1.7 38 | 39 | 40 | 41 | 42 | 43 | 44 | org.apache.maven.plugins 45 | maven-compiler-plugin 46 | 3.1 47 | 48 | 1.8 49 | 1.8 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/helper/PageTool.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.helper; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NonNull; 7 | 8 | import java.util.Objects; 9 | 10 | /** 11 | * Set from and size safely 12 | */ 13 | @Builder 14 | public class PageTool { 15 | 16 | @NonNull 17 | @Builder.Default 18 | private Integer minPageNo = 1; 19 | 20 | @NonNull 21 | @Builder.Default 22 | private Integer maxPageNo = 10000; 23 | 24 | @NonNull 25 | @Builder.Default 26 | private Integer defaultPageNo = 1; 27 | 28 | @NonNull 29 | @Builder.Default 30 | private Integer minPageSize = 0; 31 | 32 | @NonNull 33 | @Builder.Default 34 | private Integer maxPageSize = 10000; 35 | 36 | @NonNull 37 | @Builder.Default 38 | private Integer defaultPageSize = 20; 39 | 40 | @NonNull 41 | @Builder.Default 42 | private Integer maxResultWindow = 10000; 43 | 44 | public static PageTool defaultPageTool() { 45 | return PageTool.builder().build(); 46 | } 47 | 48 | @AllArgsConstructor 49 | class PageInfo { 50 | 51 | @Getter 52 | private Integer from; 53 | 54 | @Getter 55 | private Integer size; 56 | 57 | } 58 | 59 | public PageInfo getPageInfoSafe(Integer pageNo, Integer pageSize) { 60 | pageNo = choseValue(pageNo, defaultPageNo, minPageNo, maxPageNo); 61 | pageSize = choseValue(pageSize, defaultPageSize, minPageSize, maxPageSize); 62 | 63 | Integer from = (pageNo - 1) * pageSize; 64 | Integer size = pageSize; 65 | 66 | from = from > maxResultWindow ? maxResultWindow : from; 67 | size = from + size > maxResultWindow ? maxResultWindow - from : size; 68 | 69 | return new PageInfo(from, size); 70 | } 71 | 72 | private Integer choseValue(Integer target, Integer def, Integer min, Integer max) { 73 | target = Objects.nonNull(target) ? target : def; 74 | target = target < min ? min : target; 75 | target = target > max ? max : target; 76 | return target; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/cn/thare/feqb/test/StuSearchCriteria.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.test; 2 | 3 | import cn.thare.feqb.annotation.func.*; 4 | import cn.thare.feqb.annotation.func.aggs.CardinalityAggregation; 5 | import cn.thare.feqb.annotation.func.aggs.ExtendedStatsAggregation; 6 | import cn.thare.feqb.annotation.func.aggs.StatsAggregation; 7 | import cn.thare.feqb.annotation.func.aggs.TermsAggregation; 8 | import cn.thare.feqb.annotation.query.*; 9 | import cn.thare.feqb.annotation.query.type.*; 10 | import lombok.Builder; 11 | import lombok.Data; 12 | 13 | import java.util.List; 14 | 15 | @Builder 16 | @Data 17 | public class StuSearchCriteria { 18 | 19 | // func 20 | 21 | @PageNo 22 | private Integer pageNo; 23 | 24 | @PageSize 25 | private Integer pageSize; 26 | 27 | @Highlighters 28 | private List highlighters; 29 | 30 | @Source 31 | private List sources; 32 | 33 | @Sort 34 | private List sorts; 35 | 36 | // func - aggs 37 | 38 | @CardinalityAggregation(name = "ageCardinalityAggregation", field = StuFieldName.AGE) 39 | private Boolean ageCardinalityAggregation; 40 | 41 | @ExtendedStatsAggregation(name = "ageExtendedStatsAggregation", field = StuFieldName.AGE) 42 | private Boolean ageExtendedStatsAggregation; 43 | 44 | @StatsAggregation(name = "ageStatsAggregation", field = StuFieldName.AGE) 45 | private Boolean ageStatsAggregation; 46 | 47 | @TermsAggregation(name = "ageTermsAggregation", field = StuFieldName.AGE, maxSize = 5, order = TermsAggregation.Order.KEY_ASC) 48 | private Integer ageTermsAggregation; 49 | 50 | // query 51 | 52 | @Filter 53 | @Term 54 | private String school; 55 | 56 | @Filter 57 | @Terms(fieldName = StuFieldName.TAG) 58 | private List tags; 59 | 60 | @Filter 61 | @Exists(fieldName = StuFieldName.INTERNSHIP) 62 | private Boolean existInternship; 63 | 64 | @Filter 65 | @Range(fieldName = StuFieldName.MATH_SCORE, type = Range.Type.FROM, includedBoundary = false) 66 | private Integer mathScoreFrom; 67 | 68 | @Must 69 | @Wildcard 70 | private String name; 71 | 72 | @MustNot 73 | @Term(fieldName = StuFieldName.ADDRESS) 74 | private String excludedAddress; 75 | 76 | @Should 77 | @Match(operator = Match.Operator.AND) 78 | private String introduce; 79 | 80 | @Filter 81 | @MatchPhrase(fieldName = StuFieldName.NAME) 82 | private String nameMatchPhrase; 83 | 84 | /** 85 | * this field will affect query in customXxQueries method 86 | */ 87 | private String customField; 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/builder/BaseQueryBuilder.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.builder; 2 | 3 | import org.elasticsearch.index.query.QueryBuilder; 4 | import org.elasticsearch.search.builder.SearchSourceBuilder; 5 | 6 | import java.util.List; 7 | 8 | public class BaseQueryBuilder extends AbstractQueryBuilder { 9 | 10 | /** 11 | * Custom before {@link AbstractQueryBuilder#setSearchSource(SearchSourceBuilder, Object)} 12 | * 13 | * @param searchSource {@link SearchSourceBuilder} object 14 | * @param t search criteria DTO 15 | */ 16 | @Override 17 | protected void preBuild(SearchSourceBuilder searchSource, T t) { 18 | 19 | } 20 | 21 | /** 22 | * Custom after {@link AbstractQueryBuilder#setSearchSource(SearchSourceBuilder, Object)} 23 | * 24 | * @param searchSource {@link SearchSourceBuilder} object 25 | * @param t search criteria DTO 26 | */ 27 | @Override 28 | protected void postBuild(SearchSourceBuilder searchSource, T t) { 29 | 30 | } 31 | 32 | /** 33 | * Custom must query 34 | * 35 | * @param mustQueries queries in must clause 36 | * @param t search criteria DTO 37 | */ 38 | @Override 39 | protected void customMustQueries(List mustQueries, T t) { 40 | 41 | } 42 | 43 | /** 44 | * Custom must_ot query 45 | * 46 | * @param mustNotQueries queries in must_not clause 47 | * @param t search criteria DTO 48 | */ 49 | @Override 50 | protected void customMustNotQueries(List mustNotQueries, T t) { 51 | 52 | } 53 | 54 | /** 55 | * Custom should query 56 | * 57 | * @param shouldQueries queries in must_not clause 58 | * @param t search criteria DTO 59 | */ 60 | @Override 61 | protected void customShouldQueries(List shouldQueries, T t) { 62 | 63 | } 64 | 65 | /** 66 | * Custom filter query 67 | * 68 | * @param filterQueries queries in filter clause 69 | * @param t search criteria DTO 70 | */ 71 | @Override 72 | protected void customFilterQueries(List filterQueries, T t) { 73 | 74 | } 75 | 76 | /** 77 | * Custom "or" query, which will be converted to a bool.should clause under the filter context 78 | * 79 | * @param orQueries queries in "or" clause 80 | * @param t search criteria DTO 81 | */ 82 | @Override 83 | protected void customOrQueries(List orQueries, T t) { 84 | 85 | } 86 | 87 | /** 88 | * Custom "or not" query, which will be converted to a bool.should.bool.must_not clause under the filter context 89 | * 90 | * @param orNotQueries queries in "or not" clause 91 | * @param t search criteria DTO 92 | */ 93 | @Override 94 | protected void customOrNotQueries(List orNotQueries, T t) { 95 | 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/cn/thare/feqb/test/StuQueryBuilder.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.test; 2 | 3 | import cn.thare.feqb.builder.BaseQueryBuilder; 4 | import cn.thare.feqb.helper.PageTool; 5 | import org.elasticsearch.index.query.QueryBuilder; 6 | import org.elasticsearch.search.builder.SearchSourceBuilder; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | import static org.elasticsearch.index.query.QueryBuilders.*; 12 | 13 | public class StuQueryBuilder extends BaseQueryBuilder { 14 | 15 | @Override 16 | protected PageTool generatePageTool() { 17 | // custom PageTool 18 | return PageTool.builder().defaultPageSize(10).maxPageSize(500).build(); 19 | } 20 | 21 | @Override 22 | protected void preBuild(SearchSourceBuilder searchSource, StuSearchCriteria stuSearchCriteria) { 23 | // rewrite search criteria 24 | stuSearchCriteria.setCustomField("" + stuSearchCriteria.getCustomField() + "- addedByPreBuild"); 25 | } 26 | 27 | @Override 28 | protected void postBuild(SearchSourceBuilder searchSource, StuSearchCriteria stuSearchCriteria) { 29 | // special logic 30 | if (stuSearchCriteria.getPageSize() != null) { 31 | searchSource.size(stuSearchCriteria.getPageSize() * 10); 32 | } 33 | } 34 | 35 | @Override 36 | protected void customMustQueries(List mustQueries, StuSearchCriteria stuSearchCriteria) { 37 | Optional.ofNullable(stuSearchCriteria.getCustomField()) 38 | .ifPresent(t -> mustQueries.add(termQuery("customMustField", t))); 39 | } 40 | 41 | @Override 42 | protected void customMustNotQueries(List mustNotQueries, 43 | StuSearchCriteria stuSearchCriteria) { 44 | Optional.ofNullable(stuSearchCriteria.getCustomField()) 45 | .ifPresent(t -> mustNotQueries.add(termQuery("customMustNotField", t))); 46 | } 47 | 48 | @Override 49 | protected void customShouldQueries(List shouldQueries, 50 | StuSearchCriteria stuSearchCriteria) { 51 | Optional.ofNullable(stuSearchCriteria.getCustomField()) 52 | .ifPresent(t -> shouldQueries.add(termQuery("customShouldField", t))); 53 | } 54 | 55 | @Override 56 | protected void customFilterQueries(List filterQueries, 57 | StuSearchCriteria stuSearchCriteria) { 58 | Optional.ofNullable(stuSearchCriteria.getCustomField()) 59 | .ifPresent(t -> filterQueries.add(termQuery("customFilterField", t))); 60 | } 61 | 62 | @Override 63 | protected void customOrQueries(List orQueries, StuSearchCriteria stuSearchCriteria) { 64 | Optional.ofNullable(stuSearchCriteria.getCustomField()) 65 | .ifPresent(t -> orQueries.add(termQuery("customOrField", t))); 66 | } 67 | 68 | @Override 69 | protected void customOrNotQueries(List orNotQueries, StuSearchCriteria stuSearchCriteria) { 70 | Optional.ofNullable(stuSearchCriteria.getCustomField()) 71 | .ifPresent(t -> orNotQueries.add(termQuery("customOrNotField", t))); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/helper/AggregationHelper.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.helper; 2 | 3 | import cn.thare.feqb.annotation.func.aggs.CardinalityAggregation; 4 | import cn.thare.feqb.annotation.func.aggs.ExtendedStatsAggregation; 5 | import cn.thare.feqb.annotation.func.aggs.StatsAggregation; 6 | import cn.thare.feqb.annotation.func.aggs.TermsAggregation; 7 | import org.elasticsearch.search.aggregations.AggregationBuilders; 8 | import org.elasticsearch.search.aggregations.BucketOrder; 9 | import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; 10 | import org.elasticsearch.search.aggregations.metrics.cardinality.CardinalityAggregationBuilder; 11 | import org.elasticsearch.search.aggregations.metrics.stats.StatsAggregationBuilder; 12 | import org.elasticsearch.search.aggregations.metrics.stats.extended.ExtendedStatsAggregationBuilder; 13 | import org.elasticsearch.search.builder.SearchSourceBuilder; 14 | 15 | import java.lang.annotation.Annotation; 16 | import java.util.List; 17 | import java.util.Objects; 18 | import java.util.stream.Collectors; 19 | import java.util.stream.Stream; 20 | 21 | public class AggregationHelper { 22 | 23 | public static void set(SearchSourceBuilder searchSource, Annotation annotation, Object value) { 24 | if (TermsAggregation.class == annotation.annotationType()) { 25 | setTermsAggregation(searchSource, (TermsAggregation) annotation, (Integer) value); 26 | } else if (StatsAggregation.class == annotation.annotationType()) { 27 | setStatsAggregation(searchSource, (StatsAggregation) annotation, (Boolean) value); 28 | } else if (ExtendedStatsAggregation.class == annotation.annotationType()) { 29 | setExtendedStatsAggregation(searchSource, (ExtendedStatsAggregation) annotation, (Boolean) value); 30 | } else if (CardinalityAggregation.class == annotation.annotationType()) { 31 | setCardinalityAggregation(searchSource, (CardinalityAggregation) annotation, (Boolean) value); 32 | } 33 | } 34 | 35 | private static void setTermsAggregation(SearchSourceBuilder searchSource, TermsAggregation aggregation, Integer size) { 36 | TermsAggregationBuilder termsAggregation = AggregationBuilders.terms(aggregation.name()) 37 | .field(aggregation.field()); 38 | if (size > 0) { 39 | termsAggregation.size(Integer.min(size, aggregation.maxSize())); 40 | } 41 | if (aggregation.order().length > 0) { 42 | List orders = Stream.of(aggregation.order()).distinct().map(t -> { 43 | switch (t) { 44 | case COUNT_ASC: 45 | return BucketOrder.count(Boolean.TRUE); 46 | case COUNT_DESC: 47 | return BucketOrder.count(Boolean.FALSE); 48 | case KEY_ASC: 49 | return BucketOrder.key(Boolean.TRUE); 50 | case KEY_DESC: 51 | return BucketOrder.key(Boolean.FALSE); 52 | default: 53 | return null; 54 | } 55 | }).filter(Objects::nonNull).collect(Collectors.toList()); 56 | termsAggregation.order(orders); 57 | } 58 | String executionHint = aggregation.executionHint().trim(); 59 | if (executionHint.length() > 0) { 60 | termsAggregation.executionHint(executionHint); 61 | } 62 | searchSource.aggregation(termsAggregation); 63 | } 64 | 65 | private static void setStatsAggregation(SearchSourceBuilder searchSource, StatsAggregation aggregation, 66 | Boolean value) { 67 | if (!value) { 68 | return; 69 | } 70 | StatsAggregationBuilder statsAggregation = new StatsAggregationBuilder(aggregation.name()).field(aggregation.field()); 71 | searchSource.aggregation(statsAggregation); 72 | } 73 | 74 | private static void setExtendedStatsAggregation(SearchSourceBuilder searchSource, ExtendedStatsAggregation aggregation, 75 | Boolean value) { 76 | if (!value) { 77 | return; 78 | } 79 | ExtendedStatsAggregationBuilder extendedStatsAggregation = new ExtendedStatsAggregationBuilder(aggregation.name()).field(aggregation.field()); 80 | searchSource.aggregation(extendedStatsAggregation); 81 | } 82 | 83 | private static void setCardinalityAggregation(SearchSourceBuilder searchSource, CardinalityAggregation aggregation, 84 | Boolean value) { 85 | if (!value) { 86 | return; 87 | } 88 | CardinalityAggregationBuilder cardinalityAggregation = AggregationBuilders.cardinality(aggregation.name()).field(aggregation.field()); 89 | long precisionThreshold = aggregation.precisionThreshold(); 90 | if (precisionThreshold > 0) { 91 | cardinalityAggregation.precisionThreshold(precisionThreshold); 92 | } 93 | searchSource.aggregation(cardinalityAggregation); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/helper/QueryHelper.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.helper; 2 | 3 | import cn.thare.feqb.annotation.query.*; 4 | import cn.thare.feqb.annotation.query.type.*; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.elasticsearch.index.query.*; 7 | import org.elasticsearch.search.builder.SearchSourceBuilder; 8 | 9 | import java.lang.annotation.Annotation; 10 | import java.lang.reflect.Field; 11 | import java.util.*; 12 | 13 | import static org.elasticsearch.index.query.QueryBuilders.*; 14 | 15 | @Slf4j 16 | public class QueryHelper { 17 | 18 | private final static List OCCUR_TYPES = Arrays.asList(Must.class, MustNot.class, Should.class, Filter.class, 19 | Or.class, OrNot.class); 20 | 21 | private Map> queryMap; 22 | 23 | private QueryHelper() { 24 | queryMap = new HashMap<>(); 25 | OCCUR_TYPES.forEach(t -> queryMap.put(t, new ArrayList<>())); 26 | } 27 | 28 | public static QueryHelper create() { 29 | return new QueryHelper(); 30 | } 31 | 32 | public void fill(Annotation annotation, Field field, Object value) { 33 | Optional.ofNullable(getQuery(field, value)) 34 | .ifPresent(t -> queryMap.get(annotation.annotationType()).add(t)); 35 | } 36 | 37 | public void set(SearchSourceBuilder searchSource) { 38 | BoolQueryBuilder boolQuery = boolQuery(); 39 | 40 | if (orQueries().size() > 0 || orNotQueries().size() > 0) { 41 | BoolQueryBuilder orQuery = boolQuery(); 42 | orQueries().forEach(orQuery::should); 43 | orNotQueries().forEach(t -> orQuery.should(boolQuery().mustNot(t))); 44 | filterQueries().add(orQuery); 45 | } 46 | 47 | mustQueries().forEach(boolQuery::must); 48 | mustNotQueries().forEach(boolQuery::mustNot); 49 | shouldQueries().forEach(boolQuery::should); 50 | filterQueries().forEach(boolQuery::filter); 51 | 52 | searchSource.query(boolQuery); 53 | } 54 | 55 | public List mustQueries() { 56 | return queryMap.get(Must.class); 57 | } 58 | 59 | public List mustNotQueries() { 60 | return queryMap.get(MustNot.class); 61 | } 62 | 63 | public List shouldQueries() { 64 | return queryMap.get(Should.class); 65 | } 66 | 67 | public List filterQueries() { 68 | return queryMap.get(Filter.class); 69 | } 70 | 71 | public List orQueries() { 72 | return queryMap.get(Or.class); 73 | } 74 | 75 | public List orNotQueries() { 76 | return queryMap.get(OrNot.class); 77 | } 78 | 79 | private QueryBuilder getQuery(Field field, Object value) { 80 | try { 81 | if (Objects.nonNull(field.getDeclaredAnnotation(Match.class))) { 82 | Match match = field.getDeclaredAnnotation(Match.class); 83 | String fieldName = firstHasText(match.fieldName(), field.getName()); 84 | return matchQuery(fieldName, value).operator(Operator.fromString(match.operator().value())); 85 | } else if (Objects.nonNull(field.getDeclaredAnnotation(MatchPhrase.class))) { 86 | MatchPhrase matchPhrase = field.getDeclaredAnnotation(MatchPhrase.class); 87 | String fieldName = firstHasText(matchPhrase.fieldName(), field.getName()); 88 | MatchPhraseQueryBuilder query = matchPhraseQuery(fieldName, value); 89 | String analyzer = matchPhrase.analyzer().trim(); 90 | if (analyzer.length() > 0) { 91 | query.analyzer(analyzer); 92 | } 93 | int slop = matchPhrase.slop(); 94 | if (slop != -1) { 95 | query.slop(slop); 96 | } 97 | return query; 98 | } else if (Objects.nonNull(field.getDeclaredAnnotation(Term.class))) { 99 | Term term = field.getDeclaredAnnotation(Term.class); 100 | String fieldName = firstHasText(term.fieldName(), field.getName()); 101 | return termQuery(fieldName, value); 102 | } else if (Objects.nonNull(field.getDeclaredAnnotation(Terms.class))) { 103 | Terms terms = field.getDeclaredAnnotation(Terms.class); 104 | String fieldName = firstHasText(terms.fieldName(), field.getName()); 105 | return termsQuery(fieldName, (Collection) value); 106 | } else if (Objects.nonNull(field.getDeclaredAnnotation(Wildcard.class))) { 107 | Wildcard wildcard = field.getDeclaredAnnotation(Wildcard.class); 108 | String fieldName = firstHasText(wildcard.fieldName(), field.getName()); 109 | return wildcardQuery(fieldName, value.toString()); 110 | } else if (Objects.nonNull(field.getDeclaredAnnotation(Exists.class))) { 111 | Exists exists = field.getDeclaredAnnotation(Exists.class); 112 | String fieldName = firstHasText(exists.fieldName(), field.getName()); 113 | QueryBuilder query = existsQuery(fieldName); 114 | return (Boolean) value ? query : boolQuery().mustNot(query); 115 | } else if (Objects.nonNull(field.getDeclaredAnnotation(Range.class))) { 116 | Range range = field.getDeclaredAnnotation(Range.class); 117 | String fieldName = firstHasText(range.fieldName(), field.getName()); 118 | RangeQueryBuilder rangeQuery = rangeQuery(fieldName); 119 | switch (range.type()) { 120 | case FROM: 121 | rangeQuery.from(value, range.includedBoundary()); 122 | break; 123 | case TO: 124 | rangeQuery.to(value, range.includedBoundary()); 125 | break; 126 | default: 127 | break; 128 | } 129 | return rangeQuery; 130 | } 131 | } catch (Exception e) { 132 | log.warn("getQuery failed, field: {}, value: {}, cause: {}", field, value, e.getStackTrace()); 133 | } 134 | return null; 135 | } 136 | 137 | private String firstHasText(String first, String second) { 138 | return (Objects.nonNull(first) && !first.trim().isEmpty()) ? first.trim() : second; 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /README.zh-cn.md: -------------------------------------------------------------------------------- 1 | # fast-elasticsearch-query-builder 2 | 3 | 迅速构建**ElasticSearch**查询 dsl,***甚至可以不写一行实现代码。*** 4 | 5 | *其它语言: [English](README.md), [简体中文](README.zh-cn.md)* 6 | 7 | ## 简介 8 | 9 | 只需以下几步即可快速集成: 10 | 11 | 1. 编写搜索条件 DTO **MySearchCirteria**(***在字段上添加相应@注解***) 12 | 13 | ```java 14 | public class MySearchCirteria { 15 | @Must 16 | @Match 17 | private String name; 18 | } 19 | ``` 20 | 21 | 2. 编写 query builder 类 **MyQueryBuilder** 继承 **BaseQueryBuilder**, 并指定 **MySearchCriteria** 为泛型类 22 | 23 | ```java 24 | public class MyQueryBuilder extends BaseQueryBuilder { 25 | } 26 | ``` 27 | 28 | 3. 调用 **MyQueryBuilder#build** 构造 query 29 | 30 | ```java 31 | public class TestQueryBuilder { 32 | public static void main(String[] args) { 33 | // 实例化 MyQueryBuilder 34 | MyQueryBuilder myQueryBuilder = new MyQueryBuilder(); 35 | // 实例化 MySearchCriteria 36 | MySearchCriteria mySearchCriteria = new MySearchCriteria(); 37 | mySearchCriteria.setName("jack"); 38 | // 调用 build 方法 39 | String dsl = myQueryBuilder.build(mySearchCriteria); 40 | // enjoy it! 41 | System.out.println(dsl); 42 | } 43 | } 44 | ``` 45 | 46 | 结果 47 | 48 | ```json 49 | { 50 | "from": 0, 51 | "size": 20, 52 | "query": { 53 | "bool": { 54 | "must": [ 55 | { 56 | "match": { 57 | "name": { 58 | "query": "jack", 59 | "operator": "OR", 60 | "prefix_length": 0, 61 | "max_expansions": 50, 62 | "fuzzy_transpositions": true, 63 | "lenient": false, 64 | "zero_terms_query": "NONE", 65 | "auto_generate_synonyms_phrase_query": true, 66 | "boost": 1 67 | } 68 | } 69 | } 70 | ], 71 | "adjust_pure_negative": true, 72 | "boost": 1 73 | } 74 | } 75 | } 76 | ``` 77 | 78 | ***fase-elasticsearch-query-builder*** *基于 `org.elasticsearch-elasticsearch-6.3.0` 来生成dsl,故兼容性与 `org.elasticsearch-elasticsearch-6.3.0` 一致。* 79 | 80 | ## 说明 81 | 82 | ### 注解说明 83 | 84 | ***fast-elasticsearch-query-builder*** 提供两种类型的注解来构造 query: **功能性注解**和**查询类注解** 85 | 86 | #### 功能性注解 87 | 88 | ##### 设置查询属性 89 | 90 | | 注解 | 字段类型 | 功能 | 参数 | 91 | | ------------------------- | ------------------- | ----------------------------------------------- | ------------------------------------------------------------ | 92 | | @Higilighters | Collection\ | 设置 ***highlight*** | **type**: 高亮类型 | 93 | | @PageNo | Integer | 设置 ***from*** | - | 94 | | @PageSize | Integer | 设置 **size** | - | 95 | | @Sort | List\ | 设置 ***sort***,需自行实现 ***Sortable*** 接口 | - | 96 | | @Source | Collection\ | 设置 ***_source.includes*** | - | 97 | 98 | > 保证 from + size <= max_result_window 99 | 100 | ##### 设置聚合 101 | 102 | 103 | | 注解 | 字段类型 | 功能 | 参数 | 104 | | ------------------------- | -------- | ------------------------------------- | ------------------------------------------------------------ | 105 | | @CardinalityAggregation | Boolean | 设置 ***cardinality aggregation*** | **name**: 聚合名称
**field**: 聚合字段
**precisionThreshold**: 精度极限值 | 106 | | @ExtendedStatsAggregation | Boolean | 设置 ***extended stats aggregation*** | **name**: 聚合名称
**field**: 聚合字段 | 107 | | @StatsAggregation | Boolean | 设置 ***stats aggregation*** | **name**: 聚合名称
**field**: 聚合字段 | 108 | | @TermsAggregation | Integer | 设置 ***terms aggregation*** | **name**: 聚合名称
**field**: 聚合字段
**maxSize**: 聚合最大结果集
**order**: 聚合结果排序方式
**executionHint**: 聚合机制 | 109 | 110 | #### 查询类注解 111 | 112 | 用于构造query子句,又分为**搜索上下文注解**和**搜索类型注解**,两者**必须一起使用**。 113 | 114 | ##### 搜索上下文注解 115 | 116 | 决定**搜索类型注解**如何影响搜索结果(过滤还是影响评分) 117 | 118 | | 注解 | 功能 | 119 | | -------- | ------------------------------------------------------------ | 120 | | @Must | 设置 ***must query*** | 121 | | @MustNot | 设置 ***must_not query*** | 122 | | @Should | 设置 ***should query*** | 123 | | @Filter | 设置 ***filter query*** | 124 | | *@Or* | 将转换成 ***filter query*** 中的 **should** 子句,同 **sql** 中的 **or** | 125 | | *@OrNot* | 将转换成 ***filter query*** 中的 **should.mustNot** 子句,同 **sql** 中的 **or not** | 126 | 127 | > @Or 和 @OrNot 不是标准的 ES 搜索上下文类型 128 | 129 | ##### 搜索类型注解 130 | 131 | 决定匹配行为 132 | 133 | | 注解 | 字段类型 | 功能 | 参数 | 134 | | ------------ | ------------- | ----------------------------- | ------------------------------------------------------------ | 135 | | @Match | - | 设置 ***match query*** | **fieldName**:索引字段名,默认取注解所在字段的名称
**operator**: 控制 boolean 子句关系(or / and) | 136 | | @MatchPhrase | - | 设置 ***match phrase query*** | **fieldName**:索引字段名,默认取注解所在字段的名称
**analyzer**: 分词器
**slop**: 词项距离 | 137 | | @Term | - | 设置 ***term query*** | **fieldName**:索引字段名,默认取注解所在字段的名称 | 138 | | @Terms | Collection | 设置 ***terms query*** | **fieldName**:索引字段名,默认取注解所在字段的名称 | 139 | | @Range | Number | 设置 ***range query*** | **fieldName**:索引字段名,默认取注解所在字段的名称
type**: 边界类型(from / to)
**includedBoundary**: 是否包含边界 | 140 | | @Exists | Boolean | 设置 ***exists query*** | **fieldName**:索引字段名,默认取注解所在字段的名称 | 141 | | @Wildcard | - | 设置 ***wildcard query*** | **fieldName**:索引字段名,默认取注解所在字段的名称 | 142 | 143 | ### 方法说明 144 | 145 | AbstractQueryBuilder#build 方法流程如下 146 | 147 | ![AbstractQueryBuilder#build flow chart](https://raw.githubusercontent.com/Thare-Lam/fast-elasticsearch-query-builder/master/query-builder-flow-chart.jpg) 148 | 149 | 你可以在抽象方法中自定义 query。 150 | 151 | 例如,自定义 **filter query** 152 | 153 | ```java 154 | @Override 155 | protected void customFilterQueries(List filterQueries, 156 | StuSearchCriteria stuSearchCriteria) { 157 | // 在这里自定义不通过注解方式生成的 query 158 | Optional.ofNullable(stuSearchCriteria.getCustomField()) 159 | .ifPresent(t -> filterQueries.add(termQuery("customFilterField", t))); 160 | } 161 | ``` 162 | 163 | **更详细的用法可以参考`test/`目录下的样例。** 164 | -------------------------------------------------------------------------------- /src/main/java/cn/thare/feqb/builder/AbstractQueryBuilder.java: -------------------------------------------------------------------------------- 1 | package cn.thare.feqb.builder; 2 | 3 | import cn.thare.feqb.annotation.func.*; 4 | import cn.thare.feqb.annotation.func.aggs.Aggregation; 5 | import cn.thare.feqb.annotation.query.Query; 6 | import cn.thare.feqb.helper.*; 7 | import cn.thare.feqb.helper.QueryHelper; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.elasticsearch.index.query.QueryBuilder; 10 | import org.elasticsearch.search.builder.SearchSourceBuilder; 11 | 12 | import java.lang.annotation.Annotation; 13 | import java.lang.reflect.Field; 14 | import java.util.Collection; 15 | import java.util.List; 16 | import java.util.Objects; 17 | 18 | /** 19 | * Abstract query builder 20 | * @param search criteria DTO 21 | */ 22 | @Slf4j 23 | public abstract class AbstractQueryBuilder { 24 | 25 | private PageTool pageTool; 26 | 27 | protected AbstractQueryBuilder() { 28 | this.pageTool = generatePageTool(); 29 | } 30 | 31 | /** 32 | * Build query string 33 | * 34 | * @param t search criteria DTO 35 | * @return query string 36 | */ 37 | public String build(T t) { 38 | SearchSourceBuilder searchSource = new SearchSourceBuilder(); 39 | preBuild(searchSource, t); 40 | setSearchSource(searchSource, t); 41 | postBuild(searchSource, t); 42 | return searchSource.toString(); 43 | } 44 | 45 | /** 46 | * Generate {@link PageTool} 47 | * @return the {@link PageTool} object 48 | */ 49 | protected PageTool generatePageTool() { 50 | return PageTool.defaultPageTool(); 51 | } 52 | 53 | private void setSearchSource(SearchSourceBuilder searchSource, T t) { 54 | PageHelper pageHelper = PageHelper.create(); 55 | QueryHelper queryHelper = QueryHelper.create(); 56 | 57 | Field[] fields = t.getClass().getDeclaredFields(); 58 | for (Field field : fields) { 59 | if (!field.isAccessible()) { 60 | field.setAccessible(true); 61 | } 62 | Object value; 63 | try { 64 | value = field.get(t); 65 | } catch (IllegalAccessException ignore) { 66 | continue; 67 | } 68 | if (nullOrEmpty(value)) { 69 | continue; 70 | } 71 | Annotation[] annotations = field.getDeclaredAnnotations(); 72 | for (Annotation annotation : annotations) { 73 | try { 74 | if (Objects.nonNull(annotation.annotationType().getAnnotation(Query.class))) { 75 | queryHelper.fill(annotation, field, value); 76 | break; 77 | } else if (Objects.nonNull(annotation.annotationType().getAnnotation(Page.class))) { 78 | pageHelper.fill(annotation, field, value); 79 | break; 80 | } else if (Objects.nonNull(annotation.annotationType().getAnnotation(Aggregation.class))) { 81 | AggregationHelper.set(searchSource, annotation, value); 82 | break; 83 | } else if (Highlighters.class == annotation.annotationType()) { 84 | HighlightHelper.set(searchSource, annotation, value); 85 | break; 86 | } else if (Sort.class == annotation.annotationType()) { 87 | SortHelper.set(searchSource, value); 88 | break; 89 | } else if (Source.class == annotation.annotationType()) { 90 | SourceHelper.set(searchSource, value); 91 | break; 92 | } 93 | } catch (Exception e) { 94 | log.warn("analyse annotation failed, field: {}, annotation: {}, value:{}, cause: {}", 95 | field.getName(), annotation.getClass(), value, e.getStackTrace()); 96 | } 97 | } 98 | } 99 | 100 | customQuery(queryHelper, t); 101 | queryHelper.set(searchSource); 102 | pageHelper.set(searchSource, pageTool); 103 | } 104 | 105 | private void customQuery(QueryHelper queryHelper, T t) { 106 | customMustQueries(queryHelper.mustQueries(), t); 107 | customMustNotQueries(queryHelper.mustNotQueries(), t); 108 | customShouldQueries(queryHelper.shouldQueries(), t); 109 | customFilterQueries(queryHelper.filterQueries(), t); 110 | customOrQueries(queryHelper.orQueries(), t); 111 | customOrNotQueries(queryHelper.orNotQueries(), t); 112 | } 113 | 114 | private boolean nullOrEmpty(Object value) { 115 | if (Objects.isNull(value)) { 116 | return true; 117 | } 118 | try { 119 | if (value instanceof Collection) { 120 | return ((Collection) value).isEmpty(); 121 | } else { 122 | return value.toString().isEmpty(); 123 | } 124 | } catch (Exception ignored) { 125 | return true; 126 | } 127 | } 128 | 129 | /** 130 | * Custom before {@link AbstractQueryBuilder#setSearchSource(SearchSourceBuilder, Object)} 131 | * 132 | * @param searchSource {@link SearchSourceBuilder} object 133 | * @param t search criteria DTO 134 | */ 135 | protected abstract void preBuild(SearchSourceBuilder searchSource, T t); 136 | 137 | /** 138 | * Custom after {@link AbstractQueryBuilder#setSearchSource(SearchSourceBuilder, Object)} 139 | * 140 | * @param searchSource {@link SearchSourceBuilder} object 141 | * @param t search criteria DTO 142 | */ 143 | protected abstract void postBuild(SearchSourceBuilder searchSource, T t); 144 | 145 | /** 146 | * Custom must query 147 | * 148 | * @param mustQueries queries in must clause 149 | * @param t search criteria DTO 150 | */ 151 | protected abstract void customMustQueries(List mustQueries, T t); 152 | 153 | /** 154 | * Custom must_ot query 155 | * 156 | * @param mustNotQueries queries in must_not clause 157 | * @param t search criteria DTO 158 | */ 159 | protected abstract void customMustNotQueries(List mustNotQueries, T t); 160 | 161 | /** 162 | * Custom should query 163 | * 164 | * @param shouldQueries queries in must_not clause 165 | * @param t search criteria DTO 166 | */ 167 | protected abstract void customShouldQueries(List shouldQueries, T t); 168 | 169 | /** 170 | * Custom filter query 171 | * 172 | * @param filterQueries queries in filter clause 173 | * @param t search criteria DTO 174 | */ 175 | protected abstract void customFilterQueries(List filterQueries, T t); 176 | 177 | /** 178 | * Custom "or" query, which will be converted to a bool.should clause under the filter context 179 | * 180 | * @param orQueries queries in "or" clause 181 | * @param t search criteria DTO 182 | */ 183 | protected abstract void customOrQueries(List orQueries, T t); 184 | 185 | /** 186 | * Custom "or not" query, which will be converted to a bool.should.bool.must_not clause under the filter context 187 | * 188 | * @param orNotQueries queries in "or not" clause 189 | * @param t search criteria DTO 190 | */ 191 | protected abstract void customOrNotQueries(List orNotQueries, T t); 192 | 193 | } 194 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fast-elasticsearch-query-builder 2 | A fast way to build **ElasticSearch** query dsl string, ***even without writing implement code.*** 3 | 4 | *Read this in other languages: [English](README.md), [简体中文](README.zh-cn.md)* 5 | 6 | ## Brief 7 | 8 | You can integrate it in your project with these steps: 9 | 10 | 1. Complete the search criteria DTO **MySearchCirteria**(***add correct @annotation on the field***) 11 | 12 | ```java 13 | public class MySearchCirteria { 14 | @Must 15 | @Match 16 | private String name; 17 | } 18 | ``` 19 | 20 | 2. Complete the query builder class **MyQueryBuilder** extending the **BaseQueryBuilder**, with specifing **MySearchCirteria** as the generics class 21 | 22 | ```java 23 | public class MyQueryBuilder extends BaseQueryBuilder { 24 | } 25 | ``` 26 | 27 | 3. Invoke **MyQueryBuilder#build** to build the query string 28 | 29 | ```java 30 | public class TestQueryBuilder { 31 | public static void main(String[] args) { 32 | // instance MyQueryBuilder 33 | MyQueryBuilder myQueryBuilder = new MyQueryBuilder(); 34 | // instance MySearchCriteria 35 | MySearchCriteria mySearchCriteria = new MySearchCriteria(); 36 | mySearchCriteria.setName("jack"); 37 | // invoke build method 38 | String dsl = myQueryBuilder.build(mySearchCriteria); 39 | // enjoy it! 40 | System.out.println(dsl); 41 | } 42 | } 43 | ``` 44 | 45 | result 46 | 47 | ```json 48 | { 49 | "from": 0, 50 | "size": 20, 51 | "query": { 52 | "bool": { 53 | "must": [ 54 | { 55 | "match": { 56 | "name": { 57 | "query": "jack", 58 | "operator": "OR", 59 | "prefix_length": 0, 60 | "max_expansions": 50, 61 | "fuzzy_transpositions": true, 62 | "lenient": false, 63 | "zero_terms_query": "NONE", 64 | "auto_generate_synonyms_phrase_query": true, 65 | "boost": 1 66 | } 67 | } 68 | } 69 | ], 70 | "adjust_pure_negative": true, 71 | "boost": 1 72 | } 73 | } 74 | } 75 | ``` 76 | 77 | ***fast-elasticsearch-query-builder*** generates dsl based on `org.elasticsearch-elasticsearch-6.3.0` 78 | 79 | ## Instruction 80 | 81 | ### Annotation Instruction 82 | 83 | ***fast-elasticsearch-query-builder*** privides two kinds of annotation to build query string: **Function Annotation** and **Query Annotation**. 84 | 85 | #### Function Annotation 86 | 87 | ##### Set query properties. 88 | 89 | | Annotation | Field Type | Function | Parameters | 90 | | ------------- | ------------------- | ------------------------------------------------------------ | ---------------------------- | 91 | | @Higilighters | Collection\ | set ***highlight*** | **type**: the highlight type | 92 | | @PageNo | Integer | set ***from*** | - | 93 | | @PageSize | Integer | set **size** | - | 94 | | @Sort | List\ | set ***sort***, you should implement ***Sortable*** interface | - | 95 | | @Source | Collection\ | set ***_source.includes*** | - | 96 | 97 | > from + size <= max_result_window 98 | 99 | ##### Set aggregation 100 | 101 | 102 | | Annotation | Field Type | Function | Parameters | 103 | | ------------------------- | ---------- | ------------------------------------ | ------------------------------------------------------------ | 104 | | @CardinalityAggregation | Boolean | set ***cardinality aggregation*** | **name**: aggregation name
**field**: aggregation field
**precisionThreshold**: precision threshold | 105 | | @ExtendedStatsAggregation | Boolean | set ***extended stats aggregation*** | **name**: aggregation name
**field**: aggregation field | 106 | | @StatsAggregation | Boolean | set ***stats aggregation*** | **name**: aggregation name
**field**: aggregation field | 107 | | @TermsAggregation | Integer | set ***terms aggregation*** | **name**: aggregation name
**field**: aggregation field
**maxSize**: max value of the field
**order**: buckets' order
**executionHint**: mechanisms of aggregations execution | 108 | 109 | #### Query Annotation 110 | 111 | To build query clause. It includes **Query Context Annotation** and **Query Type Annotation**, and they ***must be used together***. 112 | 113 | ##### Query Context Annotation 114 | 115 | Decides how **Query Type Annotation** affect the hits (just filter the hits or affect the score). 116 | 117 | | Annotation | Function | 118 | | ---------- | ------------------------------------------------------------ | 119 | | @Must | set ***must query*** | 120 | | @MustNot | set ***must_not query*** | 121 | | @Should | set ***should query*** | 122 | | @Filter | set ***filter query*** | 123 | | *@Or* | will be converted to **should clause** in ***filter query*** , like **or** in **sql** | 124 | | *@OrNot* | will be converted to **should.mustNot clause** in ***filter query*** 中的 **should.mustNot**, like **or not** in **sql** | 125 | 126 | > @Or and @OrNot are not standard query context in Elasticsearch 127 | 128 | ##### Query Type Annotation 129 | 130 | Decides the behavior of search. 131 | 132 | | Annotation | Field Type | Function | Parameters | 133 | | ------------ | ------------- | ---------------------------- | ------------------------------------------------------------ | 134 | | @Match | - | set ***match query*** | **fieldName**: the index field name using in building query, default to the field name that the annotation works on
**operator**: control boolean clause (or / and) | 135 | | @MatchPhrase | - | set ***match phrase query*** | **fieldName**: the index field name using in building query, default to the field name that the annotation works on
**analyzer**: analyzer
**slop**: term slop | 136 | | @Term | - | set ***term query*** | **fieldName**: the index field name using in building query, default to the field name that the annotation works on | 137 | | @Terms | Collection | set ***terms query*** | **fieldName**: the index field name using in building query, default to the field name that the annotation works on | 138 | | @Range | Number | set ***range query*** | **fieldName**: the index field name using in building query, default to the field name that the annotation works on
**type**: boundary type (from / to)
**includedBoundary**: whether includes boundary | 139 | | @Exists | Boolean | set ***exists query*** | **fieldName**: the index field name using in building query, default to the field name that the annotation works on | 140 | | @Wildcard | - | set ***wildcard query*** | **fieldName**: the index field name using in building query, default to the field name that the annotation works on | 141 | 142 | ### Method Instruction 143 | 144 | AbstractQueryBuilder#build flow chart like this: 145 | 146 | ![AbstractQueryBuilder#build flow chart](https://raw.githubusercontent.com/Thare-Lam/fast-elasticsearch-query-builder/master/query-builder-flow-chart.jpg) 147 | 148 | You can custom your query in the abstract method. 149 | 150 | For example,you can custon **filter query** like this: 151 | 152 | ```java 153 | @Override 154 | protected void customFilterQueries(List filterQueries, 155 | StuSearchCriteria stuSearchCriteria) { 156 | // custom query that generated not through annotation 157 | Optional.ofNullable(stuSearchCriteria.getCustomField()) 158 | .ifPresent(t -> filterQueries.add(termQuery("customFilterField", t))); 159 | } 160 | ``` 161 | 162 | **You can see more detail usage in the demo under the `test/` directory.** 163 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------