chennan 8 jaren geleden
bovenliggende
commit
d2f8cb38da
100 gewijzigde bestanden met toevoegingen van 2922 en 484 verwijderingen
  1. 0 5
      .travis.yml
  2. 7 15
      pom.xml
  3. 3 3
      src/main/java/org/es/jdbc/api/ElasticSingleConnectionDataSource.java
  4. 4 4
      src/main/java/org/es/jdbc/es/JdbcSearchActionExecutor.java
  5. 1 1
      src/main/java/org/es/jdbc/es/JdbcSearchResponseResolver.java
  6. 10 0
      src/main/java/org/es/mapping/annotations/IgnoreField.java
  7. 81 0
      src/main/java/org/es/mapping/annotations/TypeSetting.java
  8. 56 0
      src/main/java/org/es/mapping/annotations/enums/IndexOptions.java
  9. 107 0
      src/main/java/org/es/mapping/annotations/enums/NumberType.java
  10. 55 0
      src/main/java/org/es/mapping/annotations/enums/RangeType.java
  11. 18 0
      src/main/java/org/es/mapping/annotations/enums/SimilarityAlgorithm.java
  12. 32 0
      src/main/java/org/es/mapping/annotations/enums/StringType.java
  13. 55 0
      src/main/java/org/es/mapping/annotations/enums/TermVector.java
  14. 22 0
      src/main/java/org/es/mapping/annotations/fieldtype/BinaryField.java
  15. 37 0
      src/main/java/org/es/mapping/annotations/fieldtype/BooleanField.java
  16. 10 0
      src/main/java/org/es/mapping/annotations/fieldtype/CompletionContext.java
  17. 54 0
      src/main/java/org/es/mapping/annotations/fieldtype/CompletionField.java
  18. 66 0
      src/main/java/org/es/mapping/annotations/fieldtype/DateField.java
  19. 28 0
      src/main/java/org/es/mapping/annotations/fieldtype/Fielddata.java
  20. 32 0
      src/main/java/org/es/mapping/annotations/fieldtype/FielddataFrequencyFilter.java
  21. 17 0
      src/main/java/org/es/mapping/annotations/fieldtype/GeoPointField.java
  22. 48 0
      src/main/java/org/es/mapping/annotations/fieldtype/IPField.java
  23. 26 0
      src/main/java/org/es/mapping/annotations/fieldtype/MultiField.java
  24. 15 0
      src/main/java/org/es/mapping/annotations/fieldtype/MultiNestedField.java
  25. 77 0
      src/main/java/org/es/mapping/annotations/fieldtype/NumberField.java
  26. 20 0
      src/main/java/org/es/mapping/annotations/fieldtype/PercolatorField.java
  27. 55 0
      src/main/java/org/es/mapping/annotations/fieldtype/RangeField.java
  28. 147 0
      src/main/java/org/es/mapping/annotations/fieldtype/StringField.java
  29. 61 0
      src/main/java/org/es/mapping/annotations/fieldtype/TokenCountField.java
  30. 33 0
      src/main/java/org/es/mapping/annotations/meta/MetaField_All.java
  31. 49 0
      src/main/java/org/es/mapping/annotations/meta/MetaField_Parent.java
  32. 19 0
      src/main/java/org/es/mapping/annotations/meta/MetaField_Routing.java
  33. 57 0
      src/main/java/org/es/mapping/annotations/meta/MetaField_Source.java
  34. 28 0
      src/main/java/org/es/mapping/bean/GeoPoint.java
  35. 24 0
      src/main/java/org/es/mapping/bean/Range.java
  36. 38 0
      src/main/java/org/es/mapping/mapper/BinaryFieldMapper.java
  37. 67 0
      src/main/java/org/es/mapping/mapper/BooleanFieldMapper.java
  38. 76 0
      src/main/java/org/es/mapping/mapper/CompletionFieldMapper.java
  39. 84 0
      src/main/java/org/es/mapping/mapper/DateFieldMapper.java
  40. 32 0
      src/main/java/org/es/mapping/mapper/GeoPointFieldMapper.java
  41. 58 0
      src/main/java/org/es/mapping/mapper/IPFieldMapper.java
  42. 192 0
      src/main/java/org/es/mapping/mapper/MappingBuilder.java
  43. 42 0
      src/main/java/org/es/mapping/mapper/MultiFieldMapper.java
  44. 118 0
      src/main/java/org/es/mapping/mapper/NumericFieldMapper.java
  45. 24 0
      src/main/java/org/es/mapping/mapper/PercolatorFieldMapper.java
  46. 56 0
      src/main/java/org/es/mapping/mapper/RangeFieldMapper.java
  47. 154 0
      src/main/java/org/es/mapping/mapper/StringFieldMapper.java
  48. 46 0
      src/main/java/org/es/mapping/mapper/TokenCountFieldMapper.java
  49. 49 0
      src/main/java/org/es/mapping/utils/BeanUtils.java
  50. 70 0
      src/main/java/org/es/mapping/utils/StringUtils.java
  51. 8 3
      src/main/java/org/es/sql/bean/ElasticDslContext.java
  52. 1 0
      src/main/java/org/es/sql/bean/ElasticSqlParseResult.java
  53. 1 0
      src/main/java/org/es/sql/bean/ElasticSqlQueryFields.java
  54. 0 45
      src/main/java/org/es/sql/enums/SortOption.java
  55. 1 0
      src/main/java/org/es/sql/helper/ElasticSqlDateParseHelper.java
  56. 4 6
      src/main/java/org/es/sql/parser/ElasticSql2DslParser.java
  57. 1 0
      src/main/java/org/es/sql/parser/query/exact/BetweenAndAtomQueryParser.java
  58. 3 3
      src/main/java/org/es/sql/parser/query/exact/BinaryAtomQueryParser.java
  59. 1 0
      src/main/java/org/es/sql/parser/query/exact/InListAtomQueryParser.java
  60. 1 1
      src/main/java/org/es/sql/parser/query/method/AbstractFieldSpecificMethodQueryParser.java
  61. 9 7
      src/main/java/org/es/sql/parser/query/method/MethodInvocation.java
  62. 1 0
      src/main/java/org/es/sql/parser/query/method/MethodQueryParser.java
  63. 1 0
      src/main/java/org/es/sql/parser/query/method/ParameterizedMethodQueryParser.java
  64. 1 0
      src/main/java/org/es/sql/parser/query/method/expr/ParameterizedMethodExpression.java
  65. 72 0
      src/main/java/org/es/sql/parser/query/method/join/HasChildAtomQueryParser.java
  66. 63 0
      src/main/java/org/es/sql/parser/query/method/join/HasParentAtomQueryParser.java
  67. 51 0
      src/main/java/org/es/sql/parser/query/method/join/JoinAtomQueryParser.java
  68. 3 3
      src/main/java/org/es/sql/parser/query/method/script/ScriptAtomQueryParser.java
  69. 1 0
      src/main/java/org/es/sql/parser/query/method/term/FuzzyAtomQueryParser.java
  70. 1 0
      src/main/java/org/es/sql/parser/query/method/term/PrefixAtomQueryParser.java
  71. 1 0
      src/main/java/org/es/sql/parser/query/method/term/RegexpAtomQueryParser.java
  72. 1 0
      src/main/java/org/es/sql/parser/query/method/term/TermAtomQueryParser.java
  73. 8 7
      src/main/java/org/es/sql/parser/query/method/term/TermLevelAtomQueryParser.java
  74. 1 0
      src/main/java/org/es/sql/parser/query/method/term/TermsAtomQueryParser.java
  75. 29 15
      src/main/java/org/es/sql/parser/sql/AbstractQueryConditionParser.java
  76. 1 0
      src/main/java/org/es/sql/parser/sql/QueryFromParser.java
  77. 1 0
      src/main/java/org/es/sql/parser/sql/QueryGroupByParser.java
  78. 2 2
      src/main/java/org/es/sql/parser/sql/QueryMatchConditionParser.java
  79. 8 2
      src/main/java/org/es/sql/parser/sql/QueryOrderConditionParser.java
  80. 1 0
      src/main/java/org/es/sql/parser/sql/QueryParser.java
  81. 1 4
      src/main/java/org/es/sql/parser/sql/QueryRoutingValParser.java
  82. 1 0
      src/main/java/org/es/sql/parser/sql/QuerySelectFieldListParser.java
  83. 2 2
      src/main/java/org/es/sql/parser/sql/QueryWhereConditionParser.java
  84. 2 2
      src/main/java/org/es/sql/parser/sql/sort/AbstractMethodSortParser.java
  85. 1 0
      src/main/java/org/es/sql/parser/sql/sort/MethodSortParser.java
  86. 86 0
      src/main/java/org/es/sql/parser/sql/sort/NestedSortMethodParser.java
  87. 10 15
      src/main/java/org/es/sql/parser/sql/sort/NvlMethodSortParser.java
  88. 1 0
      src/main/java/org/es/sql/parser/sql/sort/ParseSortBuilderHelper.java
  89. 5 5
      src/main/java/org/es/sql/parser/sql/sort/ScriptMethodSortParser.java
  90. 0 18
      src/main/java/org/es/sql/utils/ElasticMockClient.java
  91. 1 1
      src/main/java/org/es/sql/utils/PersistLogger.java
  92. BIN
      src/main/resources/crack/LicenseVerifier.class.bin
  93. 0 180
      src/main/resources/crack/_xpack_info.js
  94. 0 1
      src/main/resources/crack/license.json
  95. 0 10
      src/main/resources/crack/read-me.txt
  96. 75 12
      src/test/java/org/es/test/ProductIndexQueryTest.java
  97. 0 23
      src/test/java/org/es/test/bean/Buyer.java
  98. 0 66
      src/test/java/org/es/test/bean/Product.java
  99. 0 23
      src/test/java/org/es/test/bean/Provider.java
  100. 0 0
      src/test/java/org/es/test/jdbc/BaseJdbcTest.java

+ 0 - 5
.travis.yml

@@ -1,5 +0,0 @@
-language: java
-jdk:
-    - oraclejdk8
-
-script: mvn -DskipTests=true clean package

+ 7 - 15
pom.xml

@@ -19,7 +19,7 @@
     </licenses>
 
     <properties>
-        <elasticsearch.version>5.4.1</elasticsearch.version>
+        <elasticsearch.version>5.2.0</elasticsearch.version>
         <maven.compiler.target>1.8</maven.compiler.target>
         <skip.unit.tests>false</skip.unit.tests>
     </properties>
@@ -39,18 +39,6 @@
     </repositories>
 
     <dependencies>
-
-        <dependency>
-            <groupId>org.apache.logging.log4j</groupId>
-            <artifactId>log4j-api</artifactId>
-            <version>2.8.2</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.logging.log4j</groupId>
-            <artifactId>log4j-core</artifactId>
-            <version>2.8.2</version>
-        </dependency>
-
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-orm</artifactId>
@@ -106,11 +94,15 @@
             <version>${elasticsearch.version}</version>
             <scope>compile</scope>
         </dependency>
-
         <dependency>
             <groupId>org.apache.logging.log4j</groupId>
             <artifactId>log4j-api</artifactId>
-            <version>2.3</version>
+            <version>2.7</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+            <version>2.7</version>
         </dependency>
 
         <dependency>

+ 3 - 3
src/main/java/org/es/jdbc/api/ElasticSingleConnectionDataSource.java

@@ -3,7 +3,7 @@ package org.es.jdbc.api;
 
 import org.elasticsearch.client.Client;
 import org.es.jdbc.es.ElasticClientProvider;
-import org.es.sql.utils.PersistLogger;
+import org.es.sql.utils.EsPersistLogger;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
@@ -127,7 +127,7 @@ public class ElasticSingleConnectionDataSource extends DriverManagerDataSource i
                 this.target.close();
             }
             catch (Throwable ex) {
-                PersistLogger.warn(this, "Could not close shared JDBC Connection", ex);
+                EsPersistLogger.warn(this, "Could not close shared JDBC Connection", ex);
             }
         }
 
@@ -136,7 +136,7 @@ public class ElasticSingleConnectionDataSource extends DriverManagerDataSource i
                 client.close();
             }
             catch (Exception ex) {
-                PersistLogger.error(this, "Could not close elasticsearch client", ex);
+                EsPersistLogger.error(this, "Could not close elasticsearch client", ex);
             }
         }
     }

+ 4 - 4
src/main/java/org/es/jdbc/es/JdbcSearchActionExecutor.java

@@ -1,7 +1,7 @@
 package org.es.jdbc.es;
 
 import org.elasticsearch.action.*;
-import org.es.sql.utils.PersistLogger;
+import org.es.sql.utils.EsPersistLogger;
 
 public class JdbcSearchActionExecutor {
 
@@ -44,13 +44,13 @@ public class JdbcSearchActionExecutor {
         return new ActionListener<Response>() {
             @Override
             public void onResponse(Response response) {
-                PersistLogger.debug(this, String.format("[Search_Request] %s", requestBuilder.toString()));
-                PersistLogger.debug(this, String.format("[Search_Response] %s", response.toString()));
+                EsPersistLogger.debug(this, String.format("[Search_Request] %s", requestBuilder.toString()));
+                EsPersistLogger.debug(this, String.format("[Search_Response] %s", response.toString()));
             }
 
             @Override
             public void onFailure(Exception ex) {
-                PersistLogger.error(this, "Execute es req error!", ex);
+                EsPersistLogger.error(this, "Execute es req error!", ex);
             }
         };
     }

+ 1 - 1
src/main/java/org/es/jdbc/es/JdbcSearchResponseResolver.java

@@ -100,7 +100,7 @@ public class JdbcSearchResponseResolver {
     protected JdbcSearchResponse<String> parseSearchResponseGson(String searchRespGson) {
         JdbcSearchResponse<String> searchRespStrGson;
         try {
-            searchRespStrGson = new Gson().fromJson(oriSearchResponseGson, new TypeToken<JdbcSearchResponse<String>>() {
+            searchRespStrGson = new Gson().fromJson(searchRespGson, new TypeToken<JdbcSearchResponse<String>>() {
             }.getType());
         }
         catch (Exception exp) {

+ 10 - 0
src/main/java/org/es/mapping/annotations/IgnoreField.java

@@ -0,0 +1,10 @@
+package org.es.mapping.annotations;
+
+import java.lang.annotation.*;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface IgnoreField {
+
+}

+ 81 - 0
src/main/java/org/es/mapping/annotations/TypeSetting.java

@@ -0,0 +1,81 @@
+package org.es.mapping.annotations;
+
+import org.es.mapping.annotations.meta.MetaField_All;
+import org.es.mapping.annotations.meta.MetaField_Parent;
+import org.es.mapping.annotations.meta.MetaField_Routing;
+import org.es.mapping.annotations.meta.MetaField_Source;
+
+import java.lang.annotation.*;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface TypeSetting {
+
+    /**
+     * By default, fields can be added dynamically to a document,
+     * or to inner objects within a document,
+     * just by indexing a document containing the new field.
+     */
+    boolean dynamic() default true;
+
+    /**
+     * the type of document
+     */
+    String _type();
+
+    /**
+     * The _all field is a special catch-all field which concatenates
+     * the values of all of the other fields into one big string,
+     * using space as a delimiter, which is then analyzed and indexed,
+     * but not stored. This means that it can be searched, but not retrieved
+     */
+    MetaField_All _all() default @MetaField_All(enabled = true, store = false);
+
+    /**
+     * parent type
+     */
+    MetaField_Parent _parent() default @MetaField_Parent(parentClass = {});
+
+    /**
+     * A document is routed to a particular shard in an index using the following formula:
+     * <pre>shard_num = hash(_routing) % num_primary_shards</pre>
+     * <p>
+     * Forgetting the routing value can lead to a document being indexed on more than one shard.
+     * As a safeguard, the _routing field can be configured to make a custom routing value required for all CRUD operations
+     */
+    MetaField_Routing _routing() default @MetaField_Routing(required = false);
+
+
+    /**
+     * The _source field contains the original JSON document body that was passed at index time.
+     * The _source field itself is not indexed (and thus is not searchable),
+     * but it is stored so that it can be returned when executing fetch requests, like get or search
+     */
+    MetaField_Source _source() default @MetaField_Source(enabled = true);
+
+    /**
+     * Each mapping type can have custom meta data associated with it.
+     * These are not used at all by Elasticsearch, but can be used to store application-specific metadata,
+     * such as the class that a document belongs to:
+     * <pre>
+     * PUT my_index
+     * {
+     *   "mappings": {
+     *     "user": {
+     *       "_meta": {
+     *         "class": "MyApp::User",
+     *         "version": {
+     *           "min": "1.0",
+     *           "max": "1.3"
+     *         }
+     *       }
+     *     }
+     *   }
+     * }
+     * </pre>
+     * <p>
+     * https://www.elastic.co/guide/en/elasticsearch/reference/5.2/mapping-meta-field.html
+     */
+    String _meta() default "";
+}

+ 56 - 0
src/main/java/org/es/mapping/annotations/enums/IndexOptions.java

@@ -0,0 +1,56 @@
+package org.es.mapping.annotations.enums;
+
+public enum IndexOptions {
+    Default {
+        @Override
+        public String code() {
+            return "none";
+        }
+    },
+
+    /**
+     * Only the doc number is indexed. Can answer the question Does this term exist in this field?
+     */
+    Docs {
+        @Override
+        public String code() {
+            return "docs";
+        }
+    },
+
+    /**
+     * Doc number and term frequencies are indexed.
+     * Term frequencies are used to score repeated terms higher than single terms.
+     */
+    Freqs {
+        @Override
+        public String code() {
+            return "freqs";
+        }
+    },
+
+    /**
+     * Doc number, term frequencies, and term positions (or order) are indexed.
+     * Positions can be used for proximity or phrase queries.
+     */
+    Positions {
+        @Override
+        public String code() {
+            return "positions";
+        }
+    },
+
+    /**
+     * Doc number, term frequencies, positions,
+     * and start and end character offsets (which map the term back to the original string) are indexed.
+     * Offsets are used by the postings highlighter.
+     */
+    Offsets {
+        @Override
+        public String code() {
+            return "offsets";
+        }
+    };
+
+    public abstract String code();
+}

+ 107 - 0
src/main/java/org/es/mapping/annotations/enums/NumberType.java

@@ -0,0 +1,107 @@
+package org.es.mapping.annotations.enums;
+
+/**
+ * As far as integer types (byte, short, integer and long) are concerned,
+ * you should pick the smallest type which is enough for your use-case.
+ * This will help indexing and searching be more efficient. Note however that given that
+ * storage is optimized based on the actual values that are stored, picking one type over
+ * another one will have no impact on storage requirements.
+ * <p/>
+ * For floating-point types, it is often more efficient to store floating-point data into an integer using a scaling factor,
+ * which is what the scaled_float type does under the hood. For instance, a price field could be
+ * stored in a scaled_float with a scaling_factor of 100. All APIs would work as if the field was stored as a double,
+ * but under the hood elasticsearch would be working with the number of cents, price*100, which is an integer.
+ * This is mostly helpful to save disk space since integers are way easier to compress than floating points.
+ * scaled_float is also fine to use in order to trade accuracy for disk space.
+ * For instance imagine that you are tracking cpu utilization as a number between 0 and 1.
+ * It usually does not matter much whether cpu utilization is 12.7% or 13%,
+ * so you could use a scaled_float with a scaling_factor of 100 in order to round cpu utilization to the closest percent in order to save space.
+ * <p/>
+ * If scaled_float is not a good fit, then you should pick the smallest type that is enough for the use-case
+ * among the floating-point types: double, float and half_float. Here is a table that compares these types in order to help make a decision.
+ *
+ * @author chennan
+ */
+public enum NumberType {
+    /**
+     * A signed 64-bit integer with a minimum value of -2^63 and a maximum value of 2^63-1.
+     */
+    Long {
+        @Override
+        public String code() {
+            return "long";
+        }
+    },
+
+    /**
+     * A signed 32-bit integer with a minimum value of -2^31 and a maximum value of 2^31-1.
+     */
+    Integer {
+        @Override
+        public String code() {
+            return "integer";
+        }
+    },
+
+    /**
+     * A signed 16-bit integer with a minimum value of -32,768 and a maximum value of 32,767.
+     */
+    Short {
+        @Override
+        public String code() {
+            return "short";
+        }
+    },
+
+    /**
+     * A signed 8-bit integer with a minimum value of -128 and a maximum value of 127.
+     */
+    Byte {
+        @Override
+        public String code() {
+            return "byte";
+        }
+    },
+
+    /**
+     * A double-precision 64-bit IEEE 754 floating point
+     */
+    Double {
+        @Override
+        public String code() {
+            return "double";
+        }
+    },
+
+    /**
+     * A single-precision 32-bit IEEE 754 floating point.
+     */
+    Float {
+        @Override
+        public String code() {
+            return "float";
+        }
+    },
+
+    /**
+     * A half-precision 16-bit IEEE 754 floating point.
+     */
+    HalfFloat {
+        @Override
+        public String code() {
+            return "half_float";
+        }
+    },
+
+    /**
+     * A floating point that is backed by a long and a fixed scaling factor.
+     */
+    ScaledFloat {
+        @Override
+        public String code() {
+            return "scaled_float";
+        }
+    };
+
+    public abstract String code();
+}

+ 55 - 0
src/main/java/org/es/mapping/annotations/enums/RangeType.java

@@ -0,0 +1,55 @@
+package org.es.mapping.annotations.enums;
+
+public enum RangeType {
+    /**
+     * A range of signed 32-bit integers with a minimum value of -2^31 and maximum of 2^31-1.
+     */
+    IntegerRange {
+        @Override
+        public String code() {
+            return "integer_range";
+        }
+    },
+
+    /**
+     * A range of single-precision 32-bit IEEE 754 floating point values.
+     */
+    FloatRange {
+        @Override
+        public String code() {
+            return "float_range";
+        }
+    },
+
+    /**
+     * A range of signed 64-bit integers with a minimum value of -263 and maximum of 263-1.
+     */
+    LongRange {
+        @Override
+        public String code() {
+            return "long_range";
+        }
+    },
+
+    /**
+     * A range of double-precision 64-bit IEEE 754 floating point values.
+     */
+    DoubleRange {
+        @Override
+        public String code() {
+            return "double_range";
+        }
+    },
+
+    /**
+     * A range of date values represented as unsigned 64-bit integer milliseconds elapsed since system epoch.
+     */
+    DateRange {
+        @Override
+        public String code() {
+            return "date_range";
+        }
+    };
+
+    public abstract String code();
+}

+ 18 - 0
src/main/java/org/es/mapping/annotations/enums/SimilarityAlgorithm.java

@@ -0,0 +1,18 @@
+package org.es.mapping.annotations.enums;
+
+public enum SimilarityAlgorithm {
+    Default {
+        @Override
+        public String code() {
+            return "BM25";
+        }
+    },
+    BM25 {
+        @Override
+        public String code() {
+            return "BM25";
+        }
+    };
+
+    public abstract String code();
+}

+ 32 - 0
src/main/java/org/es/mapping/annotations/enums/StringType.java

@@ -0,0 +1,32 @@
+package org.es.mapping.annotations.enums;
+
+public enum StringType {
+    /**
+     * A field to index structured content such as email addresses, hostnames, status codes, zip codes or tags.
+     * <p/>
+     * They are typically used for filtering (Find me all blog posts where status is published),
+     * for sorting, and for aggregations. Keyword fields are only searchable by their exact value.
+     */
+    Keyword {
+        @Override
+        public String code() {
+            return "keyword";
+        }
+    },
+
+    /**
+     * A field to index full-text values, such as the body of an email or the description of a product.
+     * These fields are analyzed, that is they are passed through an analyzer to convert the string into
+     * a list of individual terms before being indexed.
+     * The analysis process allows Elasticsearch to search for individual words within each full text field.
+     * Text fields are not used for sorting and seldom used for aggregations
+     */
+    Text {
+        @Override
+        public String code() {
+            return "text";
+        }
+    };
+
+    public abstract String code();
+}

+ 55 - 0
src/main/java/org/es/mapping/annotations/enums/TermVector.java

@@ -0,0 +1,55 @@
+package org.es.mapping.annotations.enums;
+
+
+public enum TermVector {
+    /**
+     * No term vectors are stored. (default)
+     */
+    No {
+        @Override
+        public String code() {
+            return "code";
+        }
+    },
+
+    /**
+     * Just the terms in the field are stored.
+     */
+    Yes {
+        @Override
+        public String code() {
+            return "yes";
+        }
+    },
+
+    /**
+     * Terms and positions are stored.
+     */
+    WithPositions {
+        @Override
+        public String code() {
+            return "with_positions";
+        }
+    },
+    /**
+     * Terms and character offsets are stored.
+     */
+    WithOffsets {
+        @Override
+        public String code() {
+            return "with_offsets";
+        }
+    },
+
+    /**
+     * Terms, positions, and character offsets are stored.
+     */
+    WithPositionsOffsets {
+        @Override
+        public String code() {
+            return "with_positions_offsets";
+        }
+    };
+
+    public abstract String code();
+}

+ 22 - 0
src/main/java/org/es/mapping/annotations/fieldtype/BinaryField.java

@@ -0,0 +1,22 @@
+package org.es.mapping.annotations.fieldtype;
+
+import java.lang.annotation.*;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface BinaryField {
+
+    /**
+     * Should the field be stored on disk in a column-stride fashion,
+     * so that it can later be used for sorting, aggregations, or scripting?
+     * Accepts true (default) or false.
+     */
+    boolean doc_values() default true;
+
+    /**
+     * Whether the field value should be stored and retrievable separately from the _source field.
+     * Accepts true or false (default).
+     */
+    boolean store() default false;
+}

+ 37 - 0
src/main/java/org/es/mapping/annotations/fieldtype/BooleanField.java

@@ -0,0 +1,37 @@
+package org.es.mapping.annotations.fieldtype;
+
+import java.lang.annotation.*;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface BooleanField {
+    /**
+     * Mapping field-level query time boosting. Accepts a floating point number, defaults to 1.0.
+     */
+    float boost() default 1.0f;
+
+    /**
+     * Should the field be stored on disk in a column-stride fashion,
+     * so that it can later be used for sorting, aggregations, or scripting?
+     * Accepts true (default) or false.
+     */
+    boolean doc_values() default true;
+
+    /**
+     * Should the field be searchable? Accepts true (default) or false.
+     */
+    boolean index() default true;
+
+    /**
+     * Accepts any of the true or false values listed above. The value is substituted for any explicit null values.
+     * Defaults to null, which means the field is treated as missing.
+     */
+    String null_value() default "";
+
+    /**
+     * Whether the field value should be stored and retrievable separately from the _source field.
+     * Accepts true or false (default).
+     */
+    boolean store() default false;
+}

+ 10 - 0
src/main/java/org/es/mapping/annotations/fieldtype/CompletionContext.java

@@ -0,0 +1,10 @@
+package org.es.mapping.annotations.fieldtype;
+
+public @interface CompletionContext {
+
+    String name();
+
+    String type() default "category";
+
+    String path() default "";
+}

+ 54 - 0
src/main/java/org/es/mapping/annotations/fieldtype/CompletionField.java

@@ -0,0 +1,54 @@
+package org.es.mapping.annotations.fieldtype;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+@Documented
+@Inherited
+public @interface CompletionField {
+
+    /**
+     * The index analyzer to use, defaults to simple. In case you are wondering why we did
+     * not opt for the standard analyzer: We try to have easy to understand behaviour here,
+     * and if you index the field content At the Drive-in,
+     * you will not get any suggestions for a, nor for d (the first non stopword).
+     */
+    String analyzer() default "simple";
+
+    /**
+     * The search analyzer to use, defaults to value of analyzer.
+     */
+    String search_analyzer() default "simple";
+
+    /**
+     * Preserves the separators, defaults to true.
+     * If disabled, you could find a field starting with Foo Fighters,if you suggest for foof.
+     */
+    boolean preserve_separators() default true;
+
+    /**
+     * Enables position increments, defaults to true. If disabled and using stopwords analyzer,
+     * you could get a field starting with The Beatles,
+     * if you suggest for b. Note: You could also achieve this by indexing two inputs,
+     * Beatles and The Beatles, no need to change a simple analyzer, if you are able to enrich your data.
+     */
+    boolean preserve_position_increments() default true;
+
+    /**
+     * Limits the length of a single input, defaults to 50 UTF-16 code points.
+     * This limit is only used at index time to reduce the total number of characters per input string
+     * in order to prevent massive inputs from bloating the underlying datastructure.
+     * Most usecases won’t be influenced by the default value since prefix completions seldom grow beyond
+     * prefixes longer than a handful of characters.
+     */
+    int max_input_length() default 50;
+
+    /**
+     * To achieve suggestion filtering and/or boosting, you can add context mappings while configuring a completion field.
+     * You can define multiple context mappings for a completion field. Every context mapping has a unique name and a type.
+     * There are two types: category and geo.
+     * Context mappings are configured under the contexts parameter in the field mapping.
+     */
+    CompletionContext[] contexts() default {};
+}

+ 66 - 0
src/main/java/org/es/mapping/annotations/fieldtype/DateField.java

@@ -0,0 +1,66 @@
+package org.es.mapping.annotations.fieldtype;
+
+import java.lang.annotation.*;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface DateField {
+
+    /**
+     * Mapping field-level query time boosting. Accepts a floating point number, defaults to 1.0.
+     */
+    float boost() default 1.0f;
+
+    /**
+     * Should the field be stored on disk in a column-stride fashion,
+     * so that it can later be used for sorting, aggregations, or scripting?
+     * Accepts true (default) or false.
+     */
+    boolean doc_values() default true;
+
+    /**
+     * The date format(s) that can be parsed. Defaults to strict_date_optional_time||epoch_millis.
+     * <p/>
+     * https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html
+     */
+    String format() default "strict_date_optional_time||epoch_millis";
+
+    /**
+     * The locale to use when parsing dates since months do not have the same names and/or abbreviations in all languages.
+     * The default is the ROOT locale,
+     * <p/>
+     * https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#ROOT
+     */
+    String locale() default "ROOT";
+
+    /**
+     * If true, malformed numbers are ignored. If false (default),
+     * malformed numbers throw an exception and reject the whole document.
+     */
+    boolean ignore_malformed() default false;
+
+    /**
+     * Whether or not the field value should be included in the _all field? Accepts true or false.
+     * Defaults to false if index is set to false, or if a parent object field sets include_in_all to false.
+     * Otherwise defaults to true.
+     */
+    boolean include_in_all() default true;
+
+    /**
+     * Should the field be searchable? Accepts true (default) and false.
+     */
+    boolean index() default true;
+
+    /**
+     * Accepts a numeric value of the same type as the field which is substituted for any explicit null values.
+     * Defaults to null, which means the field is treated as missing.
+     */
+    String null_value() default "";
+
+    /**
+     * Whether the field value should be stored and retrievable separately from
+     * the _source field. Accepts true or false (default).
+     */
+    boolean store() default false;
+}

+ 28 - 0
src/main/java/org/es/mapping/annotations/fieldtype/Fielddata.java

@@ -0,0 +1,28 @@
+package org.es.mapping.annotations.fieldtype;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+@Inherited
+public @interface Fielddata {
+
+    /**
+     * Can the field use in-memory fielddata for sorting, aggregations, or scripting?
+     * Accepts true or false (default).
+     */
+    boolean enable() default false;
+
+    /**
+     * Expert settings which allow to decide which values to load in memory when fielddata is enabled.
+     * By default all values are loaded.
+     */
+    FielddataFrequencyFilter frequency() default @FielddataFrequencyFilter(
+            enable = false,
+            min = 0d,
+            max = 0d,
+            min_segment_size = 0
+    );
+
+
+}

+ 32 - 0
src/main/java/org/es/mapping/annotations/fieldtype/FielddataFrequencyFilter.java

@@ -0,0 +1,32 @@
+package org.es.mapping.annotations.fieldtype;
+
+import java.lang.annotation.*;
+
+/**
+ * Fielddata filtering can be used to reduce the number of terms loaded into memory,
+ * and thus reduce memory usage. Terms can be filtered by frequency:
+ * <p/>
+ * The frequency filter allows you to only load terms whose document frequency falls between a min and max value,
+ * which can be expressed an absolute number (when the number is bigger than 1.0) or as a percentage (eg 0.01 is 1% and 1.0 is 100%).
+ * Frequency is calculated per segment. Percentages are based on the number of docs which have a value for the field,
+ * as opposed to all docs in the segment.
+ * <p/>
+ * Small segments can be excluded completely by specifying the minimum number of docs that the segment should contain with min_segment_size
+ * <p/>
+ * https://www.elastic.co/guide/en/elasticsearch/reference/current/fielddata.html#field-data-filtering
+ *
+ * @author chennan
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+@Inherited
+public @interface FielddataFrequencyFilter {
+
+    boolean enable() default false;
+
+    double min();
+
+    double max();
+
+    int min_segment_size();
+}

+ 17 - 0
src/main/java/org/es/mapping/annotations/fieldtype/GeoPointField.java

@@ -0,0 +1,17 @@
+package org.es.mapping.annotations.fieldtype;
+
+import java.lang.annotation.*;
+
+/**
+ * https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-point.html
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface GeoPointField {
+    /**
+     * If true, malformed geo-points are ignored.
+     * If false (default), malformed geo-points throw an exception and reject the whole document.
+     */
+    boolean ignore_malformed() default false;
+}

+ 48 - 0
src/main/java/org/es/mapping/annotations/fieldtype/IPField.java

@@ -0,0 +1,48 @@
+package org.es.mapping.annotations.fieldtype;
+
+import java.lang.annotation.*;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface IPField {
+
+    /**
+     * Mapping field-level query time boosting. Accepts a floating point number, defaults to 1.0.
+     */
+    float boost() default 1.0f;
+
+    /**
+     * Should the field be stored on disk in a column-stride fashion,
+     * so that it can later be used for sorting, aggregations, or scripting?
+     * Accepts true (default) or false.
+     */
+    boolean doc_values() default true;
+
+    /**
+     * Whether or not the field value should be included in the _all field?
+     * Accepts true or false. Defaults to false if index is set to no,
+     * or if a parent object field sets include_in_all to false. Otherwise defaults to true.
+     */
+    boolean include_in_all() default true;
+
+    /**
+     * Should the field be searchable? Accepts true (default) or false.
+     */
+    boolean index() default true;
+
+    /**
+     * Accepts a string value which is substituted for any explicit null values.
+     * Defaults to null, which means the field is treated as missing.
+     */
+    String null_value() default "";
+
+    /**
+     * Whether the field value should be stored and retrievable separately from the _source field.
+     * Accepts true or false (default).
+     */
+    boolean store() default false;
+
+}
+
+

+ 26 - 0
src/main/java/org/es/mapping/annotations/fieldtype/MultiField.java

@@ -0,0 +1,26 @@
+package org.es.mapping.annotations.fieldtype;
+
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+@Documented
+public @interface MultiField {
+    /**
+     * The main string field
+     */
+    StringField mainField();
+
+    /**
+     * Multi-fields allow the same string value to be indexed in multiple ways for different purposes,
+     * such as one field for search and a multi-field for sorting and aggregations.
+     */
+    MultiNestedField[] fields() default {};
+
+    /**
+     * A field of type token_count is really an integer field which accepts string values, analyzes them,
+     * then indexes the number of tokens in the string
+     */
+    TokenCountField[] tokenFields() default {};
+}

+ 15 - 0
src/main/java/org/es/mapping/annotations/fieldtype/MultiNestedField.java

@@ -0,0 +1,15 @@
+package org.es.mapping.annotations.fieldtype;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface MultiNestedField {
+
+    String name();
+
+    StringField field();
+}

+ 77 - 0
src/main/java/org/es/mapping/annotations/fieldtype/NumberField.java

@@ -0,0 +1,77 @@
+package org.es.mapping.annotations.fieldtype;
+
+
+import org.es.mapping.annotations.enums.NumberType;
+
+import java.lang.annotation.*;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface NumberField {
+    /**
+     * type of number.
+     * <p/>
+     * {@link NumberType}
+     */
+    NumberType type();
+
+    /**
+     * Try to convert strings to numbers and truncate fractions for integers.
+     * Accepts true (default) and false.
+     */
+    boolean coerce() default true;
+
+    /**
+     * Mapping field-level query time boosting. Accepts a floating point number, defaults to 1.0.
+     */
+    float boost() default 1.0f;
+
+    /**
+     * Should the field be stored on disk in a column-stride fashion,
+     * so that it can later be used for sorting, aggregations, or scripting?
+     * Accepts true (default) or false.
+     */
+    boolean doc_values() default true;
+
+    /**
+     * If true, malformed numbers are ignored. If false (default),
+     * malformed numbers throw an exception and reject the whole document.
+     */
+    boolean ignore_malformed() default false;
+
+    /**
+     * Whether or not the field value should be included in the _all field? Accepts true or false.
+     * Defaults to false if index is set to false, or if a parent object field sets include_in_all to false.
+     * Otherwise defaults to true.
+     */
+    boolean include_in_all() default true;
+
+    /**
+     * Should the field be searchable? Accepts true (default) and false.
+     */
+    boolean index() default true;
+
+    /**
+     * Accepts a numeric value of the same type as the field which is substituted for any explicit null values.
+     * Defaults to null, which means the field is treated as missing.
+     */
+    String null_value() default "";
+
+    /**
+     * Whether the field value should be stored and retrievable separately from
+     * the _source field. Accepts true or false (default).
+     */
+    boolean store() default false;
+
+
+    /**
+     * scaled_float accepts an additional parameter:
+     * <p/>
+     * The scaling factor to use when encoding values. Values will be multiplied by this factor at index time and rounded to the closest long value.
+     * For instance, a scaled_float with a scaling_factor of 10 would internally store 2.34 as 23 and all
+     * search-time operations (queries, aggregations, sorting) will behave as if the document had a value of 2.3.
+     * High values of scaling_factor improve accuracy but also increase space requirements. This parameter is required.
+     */
+    int scaling_factor() default 1;
+}

+ 20 - 0
src/main/java/org/es/mapping/annotations/fieldtype/PercolatorField.java

@@ -0,0 +1,20 @@
+package org.es.mapping.annotations.fieldtype;
+
+import java.lang.annotation.*;
+
+/**
+ * The percolator field type parses a json structure into a native query and stores that query,
+ * so that the percolate query can use it to match provided documents.
+ * <p>
+ * Any field that contains a json object can be configured to be a percolator field.
+ * The percolator field type has no settings. Just configuring the percolator field type is
+ * sufficient to instruct Elasticsearch to treat a field as a query.
+ * <p>
+ * https://www.elastic.co/guide/en/elasticsearch/reference/current/percolator.html
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface PercolatorField {
+
+}

+ 55 - 0
src/main/java/org/es/mapping/annotations/fieldtype/RangeField.java

@@ -0,0 +1,55 @@
+package org.es.mapping.annotations.fieldtype;
+
+
+import org.es.mapping.annotations.enums.RangeType;
+
+import java.lang.annotation.*;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface RangeField {
+    /**
+     * type of range
+     * <p>
+     * {@link RangeType}
+     */
+    RangeType type();
+
+    /**
+     * The date format(s) that can be parsed. Defaults to strict_date_optional_time||epoch_millis.
+     * <p>
+     * https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html
+     */
+    String format() default "strict_date_optional_time||epoch_millis";
+
+    /**
+     * Try to convert strings to numbers and truncate fractions for integers.
+     * Accepts true (default) and false.
+     */
+    boolean coerce() default true;
+
+    /**
+     * Mapping field-level query time boosting. Accepts a floating point number, defaults to 1.0.
+     */
+    float boost() default 1.0f;
+
+    /**
+     * Whether or not the field value should be included in the _all field?
+     * Accepts true or false. Defaults to false if index is set to no,
+     * or if a parent object field sets include_in_all to false. Otherwise defaults to true.
+     */
+    boolean include_in_all() default true;
+
+    /**
+     * Should the field be searchable? Accepts true (default) or false.
+     */
+    boolean index() default true;
+
+
+    /**
+     * Whether the field value should be stored and retrievable separately from the _source field.
+     * Accepts true or false (default).
+     */
+    boolean store() default false;
+}

+ 147 - 0
src/main/java/org/es/mapping/annotations/fieldtype/StringField.java

@@ -0,0 +1,147 @@
+package org.es.mapping.annotations.fieldtype;
+
+
+
+import org.es.mapping.annotations.enums.IndexOptions;
+import org.es.mapping.annotations.enums.SimilarityAlgorithm;
+import org.es.mapping.annotations.enums.StringType;
+import org.es.mapping.annotations.enums.TermVector;
+
+import java.lang.annotation.*;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface StringField {
+    /**
+     * type of string
+     * <p>
+     * {@link StringType}
+     */
+    StringType type();
+
+    /**
+     * Mapping field-level query time boosting. Accepts a floating point number, defaults to 1.0.
+     */
+    float boost() default 1.0f;
+
+    /**
+     * The copy_to parameter allows you to create custom _all fields. In other words,
+     * the values of multiple fields can be copied into a group field,
+     * which can then be queried as a single field. For instance,
+     * the first_name and last_name fields can be copied to the full_name field
+     */
+    String[] copy_to() default {};
+
+    /**
+     * Should the field be stored on disk in a column-stride fashion,
+     * so that it can later be used for sorting, aggregations, or scripting?
+     * Accepts true (default) or false.
+     */
+    boolean doc_values() default true;
+
+    /**
+     * Should global ordinals be loaded eagerly on refresh? Accepts true or false (default).
+     * Enabling this is a good idea on fields that are frequently used for terms aggregations.
+     */
+    boolean eager_global_ordinals() default false;
+
+
+    /**
+     * Do not index any string longer than this value.
+     * Defaults to 2147483647 so that all values would be accepted.
+     */
+    int ignore_above() default 2147483647;
+
+    /**
+     * Whether or not the field value should be included in the _all field?
+     * Accepts true or false. Defaults to false if index is set to no,
+     * or if a parent object field sets include_in_all to false. Otherwise defaults to true.
+     */
+    boolean include_in_all() default true;
+
+    /**
+     * Should the field be searchable? Accepts true (default) or false.
+     */
+    boolean index() default true;
+
+    /**
+     * What information should be stored in the index, for scoring purposes.
+     * Defaults to docs but can also be set to freqs to take term frequency into account when computing scores.
+     */
+    IndexOptions index_options() default IndexOptions.Default;
+
+    /**
+     * Accepts a string value which is substituted for any explicit null values.
+     * Defaults to null, which means the field is treated as missing.
+     */
+    String null_value() default "";
+
+    /**
+     * Whether the field value should be stored and retrievable separately from the _source field.
+     * Accepts true or false (default).
+     */
+    boolean store() default false;
+
+    /**
+     * Which scoring algorithm or similarity should be used. Defaults to BM25.
+     */
+    SimilarityAlgorithm similarity() default SimilarityAlgorithm.Default;
+
+
+    /**
+     * How to pre-process the keyword prior to indexing. Defaults to null, meaning the keyword is kept as-is.
+     *
+     * NOTE: This functionality is experimental and may be changed or removed completely in a future release.
+     * Elastic will take a best effort approach to fix any issues,
+     * but experimental features are not subject to the support SLA of official GA features.
+     */
+    // normalizer, not support
+
+
+    /**
+     * *********************************    NEXT FOR FULL TEXT   *********************************
+     */
+
+
+    /**
+     * The analyzer which should be used for analyzed string fields, both at index-time and at
+     * search-time (unless overridden by the search_analyzer). Defaults to the default index analyzer,
+     * or the standard analyzer.
+     */
+    String analyzer() default "";
+
+    /**
+     * The analyzer that should be used at search time on analyzed fields. Defaults to the analyzer setting.
+     */
+    String search_analyzer() default "";
+
+    /**
+     * The analyzer that should be used at search time when a phrase is encountered. Defaults to the search_analyzer setting.
+     */
+    String search_quote_analyzer() default "";
+
+    /**
+     * The number of fake term position which should be inserted between each element of an array of strings.
+     * Defaults to the position_increment_gap configured on the analyzer which defaults to 100. 100 was chosen
+     * because it prevents phrase queries with reasonably large slops (less than 100) from matching terms across field values.
+     */
+    int position_increment_gap() default 100;
+
+    /**
+     * Whether field-length should be taken into account when scoring queries. Accepts true (default) or false.
+     */
+    boolean norms() default true;
+
+    /**
+     * Can the field use in-memory fielddata for sorting, aggregations, or scripting? Accepts true or false (default).
+     */
+    Fielddata fielddata() default @Fielddata;
+
+    /**
+     * Whether term vectors should be stored for an analyzed field. Defaults to no.
+     */
+    TermVector term_vector() default TermVector.No;
+}
+
+

+ 61 - 0
src/main/java/org/es/mapping/annotations/fieldtype/TokenCountField.java

@@ -0,0 +1,61 @@
+package org.es.mapping.annotations.fieldtype;
+
+import java.lang.annotation.*;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface TokenCountField {
+
+    String name();
+
+    /**
+     * The analyzer which should be used to analyze the string value.
+     * Required. For best performance, use an analyzer without token filters.
+     */
+    String analyzer();
+
+    /**
+     * Indicates if position increments should be counted.
+     * Set to false if you don��t want to count tokens removed by analyzer filters (like stop). Defaults to true.
+     */
+    boolean enable_position_increments() default true;
+
+    /**
+     * Mapping field-level query time boosting. Accepts a floating point number, defaults to 1.0.
+     */
+    float boost() default 1.0f;
+
+    /**
+     * Should the field be stored on disk in a column-stride fashion,
+     * so that it can later be used for sorting, aggregations, or scripting?
+     * Accepts true (default) or false.
+     */
+    boolean doc_values() default true;
+
+    /**
+     * Whether or not the field value should be included in the _all field?
+     * Accepts true or false. Defaults to false if index is set to no,
+     * or if a parent object field sets include_in_all to false. Otherwise defaults to true.
+     */
+    boolean include_in_all() default true;
+
+    /**
+     * Should the field be searchable? Accepts true (default) or false.
+     */
+    boolean index() default true;
+
+    /**
+     * Accepts a string value which is substituted for any explicit null values.
+     * Defaults to null, which means the field is treated as missing.
+     */
+    String null_value() default "";
+
+    /**
+     * Whether the field value should be stored and retrievable separately from the _source field.
+     * Accepts true or false (default).
+     */
+    boolean store() default false;
+}
+
+

+ 33 - 0
src/main/java/org/es/mapping/annotations/meta/MetaField_All.java

@@ -0,0 +1,33 @@
+package org.es.mapping.annotations.meta;
+
+import java.lang.annotation.*;
+
+/**
+ * The _all field is a special catch-all field which concatenates
+ * the values of all of the other fields into one big string,
+ * using space as a delimiter, which is then analyzed and indexed,
+ * but not stored. This means that it can be searched, but not retrieved.
+ * <p>
+ * The _all field is just a text field, and accepts the same parameters that other string fields accept,
+ * including analyzer, term_vectors, index_options, and store.
+ * <p>
+ * https://www.elastic.co/guide/en/elasticsearch/reference/5.2/mapping-all-field.html
+ *
+ * @author chennan
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface MetaField_All {
+    /**
+     * The _all field can be completely disabled per-type by setting enabled to false
+     * default set to true
+     */
+    boolean enabled() default true;
+
+    /**
+     * If store is set to true, then the original field value is retrievable and can be highlighted
+     * default set to false
+     */
+    boolean store() default false;
+}

+ 49 - 0
src/main/java/org/es/mapping/annotations/meta/MetaField_Parent.java

@@ -0,0 +1,49 @@
+package org.es.mapping.annotations.meta;
+
+import java.lang.annotation.*;
+
+/**
+ * A parent-child relationship can be established between documents
+ * in the same index by making one mapping type the parent of another
+ * <p/>
+ * Parent-child restrictions
+ * 1. The parent and child types must be different parent-child relationships
+ * cannot be established between documents of the same type.
+ * <p/>
+ * 2. The _parent.type setting can only point to a type that doesn't exist yet.
+ * This means that a type cannot become a parent type after it has been created.
+ * <p/>
+ * 3. Parent and child documents must be indexed on the same shard.
+ * The parent ID is used as the routing value for the child, to ensure that the child is indexed on the same shard as the parent.
+ * This means that the same parent value needs to be provided when getting, deleting, or updating a child document.
+ * <p/>
+ * https://www.elastic.co/guide/en/elasticsearch/reference/5.2/mapping-parent-field.html
+ *
+ * @author chennan
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface MetaField_Parent {
+
+    /**
+     * parent class
+     */
+    Class<?>[] parentClass();
+
+    /**
+     * Parent-child uses global ordinals to speed up joins. Global ordinals need to be rebuilt after any change to a shard.
+     * The more parent id values are stored in a shard, the longer it takes to rebuild the global ordinals for the _parent field.
+     * <p/>
+     * Global ordinals, by default, are built eagerly: if the index has changed, global ordinals for the _parent field
+     * will be rebuilt as part of the refresh. This can add significant time the refresh. However most of the times this is the right trade-off,
+     * otherwise global ordinals are rebuilt when the first parent-child query or aggregation is used.
+     * This can introduce a significant latency spike for your users and usually this is worse as multiple global ordinals for the _parent
+     * field may be attempt rebuilt within a single refresh interval when many writes are occurring.
+     * <p/>
+     * NOTE: When the parent/child is used infrequently and writes occur frequently it may make sense to disable eager loading
+     * <p/>
+     * default set to true
+     */
+    boolean eager_global_ordinals() default true;
+}

+ 19 - 0
src/main/java/org/es/mapping/annotations/meta/MetaField_Routing.java

@@ -0,0 +1,19 @@
+package org.es.mapping.annotations.meta;
+
+import java.lang.annotation.*;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface MetaField_Routing {
+    /**
+     * A document is routed to a particular shard in an index using the following formula:
+     * <pre>shard_num = hash(_routing) % num_primary_shards</pre>
+     * <p>
+     * Forgetting the routing value can lead to a document being indexed on more than one shard.
+     * As a safeguard, the _routing field can be configured to make a custom routing value required for all CRUD operations
+     * <p>
+     * default set to false to disable custom routing value
+     */
+    boolean required() default false;
+}

+ 57 - 0
src/main/java/org/es/mapping/annotations/meta/MetaField_Source.java

@@ -0,0 +1,57 @@
+package org.es.mapping.annotations.meta;
+
+import java.lang.annotation.*;
+
+/**
+ * The _source field contains the original JSON document body that was passed at index time.
+ * The _source field itself is not indexed (and thus is not searchable),
+ * but it is stored so that it can be returned when executing fetch requests, like get or search
+ * <p>
+ * https://www.elastic.co/guide/en/elasticsearch/reference/5.2/mapping-source-field.html
+ *
+ * @author chennan
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface MetaField_Source {
+    /**
+     * default set to true
+     */
+    boolean enabled() default true;
+
+
+    /**
+     * An expert-only feature is the ability to prune the contents of the _source field
+     * after the document has been indexed, but before the _source field is stored.
+     * <p>
+     * Removing fields from the _source has similar downsides to disabling _source,
+     * especially the fact that you cannot reindex documents from one Elasticsearch index to another.
+     * Consider using source filtering instead.
+     * <p>
+     * The includes/excludes parameters (which also accept wildcards) can be used as follows:
+     * <p>
+     * <pre>
+     * PUT logs
+     * {
+     *   "mappings": {
+     *     "event": {
+     *       "_source": {
+     *         "includes": [
+     *           "*.count",
+     *           "meta.*"
+     *         ],
+     *         "excludes": [
+     *           "meta.description",
+     *           "meta.other.*"
+     *         ]
+     *        }
+     *      }
+     *    }
+     * }
+     * </pre>
+     */
+    String[] includes() default {};
+
+    String[] excludes() default {};
+}

+ 28 - 0
src/main/java/org/es/mapping/bean/GeoPoint.java

@@ -0,0 +1,28 @@
+package org.es.mapping.bean;
+
+public class GeoPoint {
+    /**
+     * latitude
+     */
+    private float lat;
+    /**
+     * longitude
+     */
+    private float lon;
+
+    public float getLat() {
+        return lat;
+    }
+
+    public void setLat(float lat) {
+        this.lat = lat;
+    }
+
+    public float getLon() {
+        return lon;
+    }
+
+    public void setLon(float lon) {
+        this.lon = lon;
+    }
+}

+ 24 - 0
src/main/java/org/es/mapping/bean/Range.java

@@ -0,0 +1,24 @@
+package org.es.mapping.bean;
+
+public class Range<DataType> {
+
+    DataType gte;
+
+    DataType lte;
+
+    public DataType getGte() {
+        return gte;
+    }
+
+    public void setGte(DataType gte) {
+        this.gte = gte;
+    }
+
+    public DataType getLte() {
+        return lte;
+    }
+
+    public void setLte(DataType lte) {
+        this.lte = lte;
+    }
+}

+ 38 - 0
src/main/java/org/es/mapping/mapper/BinaryFieldMapper.java

@@ -0,0 +1,38 @@
+package org.es.mapping.mapper;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.es.mapping.annotations.fieldtype.BinaryField;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+public class BinaryFieldMapper {
+
+    public static boolean isValidBinaryType(Field field) {
+        Class<?> fieldClass = field.getType();
+        return (String.class == fieldClass) && field.isAnnotationPresent(BinaryField.class);
+    }
+
+
+    public static void mapDataType(XContentBuilder mappingBuilder, BinaryField booleanField) throws IOException {
+        mappingBuilder.field("type", "binary");
+
+        if (!booleanField.doc_values()) {
+            mappingBuilder.field("doc_values", booleanField.doc_values());
+        }
+
+        if (booleanField.store()) {
+            mappingBuilder.field("store", booleanField.store());
+        }
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, Field field) throws IOException {
+        if (!isValidBinaryType(field)) {
+            throw new IllegalArgumentException(
+                    String.format("field type[%s] is invalid type of binary.", field.getType()));
+        }
+
+        BinaryField binaryField = field.getDeclaredAnnotation(BinaryField.class);
+        mapDataType(mappingBuilder, binaryField);
+    }
+}

+ 67 - 0
src/main/java/org/es/mapping/mapper/BooleanFieldMapper.java

@@ -0,0 +1,67 @@
+package org.es.mapping.mapper;
+
+import org.apache.commons.lang.StringUtils;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.es.mapping.annotations.fieldtype.BooleanField;
+import org.es.mapping.utils.BeanUtils;
+
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+public class BooleanFieldMapper {
+
+    public static boolean isValidBooleanType(Field field) {
+        if (BeanUtils.isCollectionType(field)) {
+            if (!BeanUtils.isValidCollectionType(field)) {
+                throw new IllegalArgumentException(
+                        String.format("Unsupported list class type, name[%s].", field.getName()));
+            }
+
+            Class genericTypeClass = (Class) BeanUtils.getCollectionGenericType(field);
+            return Boolean.class == genericTypeClass || boolean.class == genericTypeClass;
+        }
+
+        Class<?> fieldClass = field.getType();
+        return Boolean.class == fieldClass || boolean.class == fieldClass;
+    }
+
+
+    public static void mapDataType(XContentBuilder mappingBuilder, BooleanField booleanField) throws IOException {
+        mappingBuilder.field("type", "boolean");
+        if (booleanField.boost() != 1.0f) {
+            mappingBuilder.field("boost", booleanField.boost());
+        }
+
+        if (!booleanField.doc_values()) {
+            mappingBuilder.field("doc_values", booleanField.doc_values());
+        }
+
+        if (!booleanField.index()) {
+            mappingBuilder.field("index", booleanField.index());
+        }
+
+        if (StringUtils.isNotBlank(booleanField.null_value())) {
+            mappingBuilder.field("null_value", booleanField.null_value());
+        }
+
+        if (booleanField.store()) {
+            mappingBuilder.field("store", booleanField.store());
+        }
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, Field field) throws IOException {
+        if (!isValidBooleanType(field)) {
+            throw new IllegalArgumentException(
+                    String.format("field type[%s] is invalid type of boolean.", field.getType()));
+        }
+
+        if (field.isAnnotationPresent(BooleanField.class)) {
+            BooleanField booleanField = field.getDeclaredAnnotation(BooleanField.class);
+            mapDataType(mappingBuilder, booleanField);
+            return;
+        }
+
+        mappingBuilder.field("type", "boolean");
+    }
+}

+ 76 - 0
src/main/java/org/es/mapping/mapper/CompletionFieldMapper.java

@@ -0,0 +1,76 @@
+package org.es.mapping.mapper;
+
+import org.apache.commons.lang.StringUtils;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.es.mapping.annotations.fieldtype.CompletionContext;
+import org.es.mapping.annotations.fieldtype.CompletionField;
+import org.es.mapping.utils.BeanUtils;
+
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+public class CompletionFieldMapper {
+
+    public static boolean isValidCompletionFieldType(Field field) {
+        if (BeanUtils.isCollectionType(field)) {
+            if (!BeanUtils.isValidCollectionType(field)) {
+                throw new IllegalArgumentException(
+                        String.format("Unsupported list class type, name[%s].", field.getName()));
+            }
+
+            Class genericTypeClass = (Class) BeanUtils.getCollectionGenericType(field);
+            return String.class.isAssignableFrom(genericTypeClass) && field.isAnnotationPresent(CompletionField.class);
+        }
+
+        Class<?> fieldClass = field.getType();
+        return (String.class == fieldClass) && field.isAnnotationPresent(CompletionField.class);
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, CompletionField completionField) throws IOException {
+        mappingBuilder.field("type", "completion");
+
+        if (!"simple".equalsIgnoreCase(completionField.analyzer())) {
+            mappingBuilder.field("analyzer", completionField.analyzer());
+        }
+
+        if (!"simple".equalsIgnoreCase(completionField.search_analyzer())) {
+            mappingBuilder.field("search_analyzer", completionField.search_analyzer());
+        }
+
+        if (!completionField.preserve_separators()) {
+            mappingBuilder.field("preserve_separators", completionField.preserve_separators());
+        }
+
+        if (!completionField.preserve_position_increments()) {
+            mappingBuilder.field("preserve_position_increments", completionField.preserve_position_increments());
+        }
+
+        if (completionField.max_input_length() != 50) {
+            mappingBuilder.field("max_input_length", completionField.max_input_length());
+        }
+
+        if (completionField.contexts().length > 0) {
+            mappingBuilder.startArray("contexts");
+            for (CompletionContext completionContext : completionField.contexts()) {
+                mappingBuilder.field("name", completionContext.name());
+                mappingBuilder.field("type", completionContext.type());
+
+                if (StringUtils.isNotBlank(completionContext.path())) {
+                    mappingBuilder.field("path", completionContext.path());
+                }
+            }
+            mappingBuilder.endArray();
+        }
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, Field field) throws IOException {
+        if (!isValidCompletionFieldType(field)) {
+            throw new IllegalArgumentException(
+                    String.format("field type[%s] is invalid type of string.", field.getType()));
+        }
+
+        CompletionField completionField = field.getDeclaredAnnotation(CompletionField.class);
+        mapDataType(mappingBuilder, completionField);
+    }
+}

+ 84 - 0
src/main/java/org/es/mapping/mapper/DateFieldMapper.java

@@ -0,0 +1,84 @@
+package org.es.mapping.mapper;
+
+import org.apache.commons.lang.StringUtils;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.es.mapping.annotations.fieldtype.DateField;
+import org.es.mapping.utils.BeanUtils;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.Date;
+
+public class DateFieldMapper {
+
+    public static boolean isValidDateType(Field field) {
+        if (BeanUtils.isCollectionType(field)) {
+            if (!BeanUtils.isValidCollectionType(field)) {
+                throw new IllegalArgumentException(
+                        String.format("Unsupported list class type, name[%s].", field.getName()));
+            }
+
+            Class genericTypeClass = (Class) BeanUtils.getCollectionGenericType(field);
+            return Date.class.isAssignableFrom(genericTypeClass);
+        }
+
+        Class<?> fieldClass = field.getType();
+        return Date.class.isAssignableFrom(fieldClass);
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, DateField dateField) throws IOException {
+        mappingBuilder.field("type", "date");
+        mappingBuilder.field("format", dateField.format());
+
+        if (dateField.boost() != 1.0f) {
+            mappingBuilder.field("boost", dateField.boost());
+        }
+
+        if (!dateField.doc_values()) {
+            mappingBuilder.field("doc_values", dateField.doc_values());
+        }
+
+        if (dateField.ignore_malformed()) {
+            mappingBuilder.field("ignore_malformed", dateField.ignore_malformed());
+        }
+
+        if (!dateField.include_in_all()) {
+            mappingBuilder.field("include_in_all", dateField.include_in_all());
+        }
+        else if (!dateField.index()) {
+            mappingBuilder.field("include_in_all", false);
+        }
+
+        if (!dateField.index()) {
+            mappingBuilder.field("index", dateField.index());
+        }
+
+        if (StringUtils.isNotBlank(dateField.null_value())) {
+            mappingBuilder.field("null_value", dateField.null_value());
+        }
+
+        if (dateField.store()) {
+            mappingBuilder.field("store", dateField.store());
+        }
+
+        if (!"ROOT".equalsIgnoreCase(dateField.locale())) {
+            mappingBuilder.field("locale", dateField.locale());
+        }
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, Field field) throws IOException {
+        if (!isValidDateType(field)) {
+            throw new IllegalArgumentException(
+                    String.format("field type[%s] is invalid type of date.", field.getType()));
+        }
+
+        if (field.isAnnotationPresent(DateField.class)) {
+            DateField dateField = field.getDeclaredAnnotation(DateField.class);
+            mapDataType(mappingBuilder, dateField);
+            return;
+        }
+
+        mappingBuilder.field("type", "date");
+        mappingBuilder.field("format", "strict_date_optional_time||epoch_millis");
+    }
+}

+ 32 - 0
src/main/java/org/es/mapping/mapper/GeoPointFieldMapper.java

@@ -0,0 +1,32 @@
+package org.es.mapping.mapper;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.es.mapping.annotations.fieldtype.GeoPointField;
+import org.es.mapping.bean.GeoPoint;
+
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+public class GeoPointFieldMapper {
+
+    public static boolean isValidGeoPointFieldType(Field field) {
+        Class<?> fieldClass = field.getType();
+
+        if (!field.isAnnotationPresent(GeoPointField.class)) {
+            return false;
+        }
+
+        return String.class == fieldClass || fieldClass == GeoPoint.class;
+
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, Field field) throws IOException {
+        if (!isValidGeoPointFieldType(field)) {
+            throw new IllegalArgumentException(
+                    String.format("field type[%s] is invalid type of percolator.", field.getType()));
+        }
+
+        mappingBuilder.field("type", "geo_point");
+    }
+}

+ 58 - 0
src/main/java/org/es/mapping/mapper/IPFieldMapper.java

@@ -0,0 +1,58 @@
+package org.es.mapping.mapper;
+
+import org.apache.commons.lang.StringUtils;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.es.mapping.annotations.fieldtype.IPField;
+
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+public class IPFieldMapper {
+
+    public static boolean isValidIPFieldType(Field field) {
+        Class<?> fieldClass = field.getType();
+        return String.class.isAssignableFrom(fieldClass) && field.isAnnotationPresent(IPField.class);
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, IPField ipField) throws IOException {
+        mappingBuilder.field("type", "ip");
+
+        if (ipField.boost() != 1.0f) {
+            mappingBuilder.field("boost", ipField.boost());
+        }
+
+        if (!ipField.doc_values()) {
+            mappingBuilder.field("doc_values", ipField.doc_values());
+        }
+
+        if (!ipField.include_in_all()) {
+            mappingBuilder.field("include_in_all", ipField.include_in_all());
+        }
+        else if (!ipField.index()) {
+            mappingBuilder.field("include_in_all", false);
+        }
+
+        if (!ipField.index()) {
+            mappingBuilder.field("index", ipField.index());
+        }
+
+        if (StringUtils.isNotBlank(ipField.null_value())) {
+            mappingBuilder.field("null_value", ipField.null_value());
+        }
+
+        if (ipField.store()) {
+            mappingBuilder.field("store", ipField.store());
+        }
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, Field field) throws IOException {
+        if (!isValidIPFieldType(field)) {
+            throw new IllegalArgumentException(
+                    String.format("field type[%s] is invalid type of ip.", field.getType()));
+        }
+
+        IPField ipField = field.getDeclaredAnnotation(IPField.class);
+        mapDataType(mappingBuilder, ipField);
+    }
+}

+ 192 - 0
src/main/java/org/es/mapping/mapper/MappingBuilder.java

@@ -0,0 +1,192 @@
+package org.es.mapping.mapper;
+
+
+import com.google.common.collect.Maps;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.es.mapping.annotations.IgnoreField;
+import org.es.mapping.annotations.TypeSetting;
+import org.es.mapping.utils.BeanUtils;
+
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.util.Map;
+
+public class MappingBuilder {
+
+    public Map<String, String> buildMappingAsString(Class<?> documentClazz) throws IOException {
+        Map<String, XContentBuilder> mappingMap = buildMapping(documentClazz);
+        Map<String, String> stringMappingMap = Maps.newLinkedHashMap();
+        for (String key : mappingMap.keySet()) {
+            stringMappingMap.put(key, mappingMap.get(key).string());
+        }
+        return stringMappingMap;
+    }
+
+    public Map<String, XContentBuilder> buildMapping(Class<?> documentClazz) throws IOException {
+        if (documentClazz == null) {
+            throw new IllegalArgumentException("param[documentClazz] can not be null!");
+        }
+
+        if (!documentClazz.isAnnotationPresent(TypeSetting.class)) {
+            throw new IllegalArgumentException(
+                    String.format("Can't find annotation[@TypeSetting] at class[%s]", documentClazz.getName()));
+        }
+
+        Map<String, XContentBuilder> mappingMap = Maps.newLinkedHashMap();
+        buildMapping(documentClazz, mappingMap);
+        return mappingMap;
+    }
+
+    private void buildMapping(Class<?> documentClazz, Map<String, XContentBuilder> mappingMap) throws IOException {
+        XContentBuilder mappingBuilder = XContentFactory.jsonBuilder().prettyPrint().startObject();
+        TypeSetting typeSetting = documentClazz.getAnnotation(TypeSetting.class);
+
+        if (typeSetting == null) {
+            throw new IllegalStateException(
+                    String.format("Can't find annotation[@TypeSetting] at class[%s]", documentClazz.getName()));
+        }
+
+        if (typeSetting._parent().parentClass().length > 0) {
+            Class<?> parentClass = typeSetting._parent().parentClass()[0];
+            buildMapping(parentClass, mappingMap);
+        }
+
+        String indexType = typeSetting._type();
+        mappingMap.put(indexType, mappingBuilder);
+
+        mappingBuilder.startObject(indexType);
+
+        buildTypeSetting(mappingBuilder, documentClazz);
+        buildTypeProperty(mappingBuilder, documentClazz);
+
+        mappingBuilder.endObject();
+        mappingBuilder.endObject();
+    }
+
+    private void buildTypeSetting(XContentBuilder mapping, Class clazz) throws IOException {
+        TypeSetting typeSetting = (TypeSetting) clazz.getAnnotation(TypeSetting.class);
+
+        if (!typeSetting._all().enabled()) {
+            mapping.startObject("_all").field("enabled", false).endObject();
+        }
+
+        if (typeSetting._routing().required()) {
+            mapping.startObject("_routing").field("required", true).endObject();
+        }
+
+        if (!typeSetting.dynamic()) {
+            mapping.field("dynamic", false);
+        }
+
+        if (typeSetting._parent().parentClass().length > 0) {
+            Class<?> parentClass = typeSetting._parent().parentClass()[0];
+            TypeSetting parentTypeSetting = parentClass.getAnnotation(TypeSetting.class);
+            mapping.startObject("_parent")
+                    .field("type", parentTypeSetting._type());
+
+            if (!typeSetting._parent().eager_global_ordinals()) {
+                mapping.field("eager_global_ordinals", false);
+            }
+            mapping.endObject();
+        }
+    }
+
+    private XContentBuilder buildTypeProperty(XContentBuilder mappingBuilder, Class clazz) throws IOException {
+        mappingBuilder.startObject("properties");
+
+        Field[] classFields = BeanUtils.retrieveFields(clazz);
+        for (Field classField : classFields) {
+            String fieldName = classField.getName();
+
+            if (Modifier.isTransient(classField.getModifiers())
+                    || Modifier.isStatic(classField.getModifiers())
+                    || fieldName.equals("$VRc") || fieldName.equals("serialVersionUID")) {
+                continue;
+            }
+
+            if (classField.getAnnotation(IgnoreField.class) != null) {
+                continue;
+            }
+
+            buildFieldProperty(mappingBuilder, classField);
+        }
+
+        mappingBuilder.endObject();
+
+        return mappingBuilder;
+    }
+
+    private XContentBuilder buildFieldProperty(XContentBuilder mappingBuilder, Field field) throws IOException {
+        mappingBuilder.startObject(field.getName());
+
+        // Geo point  field
+        if (GeoPointFieldMapper.isValidGeoPointFieldType(field)) {
+            GeoPointFieldMapper.mapDataType(mappingBuilder, field);
+        }
+        // Percolator field
+        else if (PercolatorFieldMapper.isValidPercolatorFieldType(field)) {
+            PercolatorFieldMapper.mapDataType(mappingBuilder, field);
+        }
+        // IP field
+        else if (IPFieldMapper.isValidIPFieldType(field)) {
+            IPFieldMapper.mapDataType(mappingBuilder, field);
+        }
+        // Range field
+        else if (RangeFieldMapper.isValidRangeFieldType(field)) {
+            RangeFieldMapper.mapDataType(mappingBuilder, field);
+        }
+        // Number
+        else if (NumericFieldMapper.isValidNumberType(field)) {
+            NumericFieldMapper.mapDataType(mappingBuilder, field);
+        }
+        // Boolean
+        else if (BooleanFieldMapper.isValidBooleanType(field)) {
+            BooleanFieldMapper.mapDataType(mappingBuilder, field);
+        }
+        // Binary field
+        else if (BinaryFieldMapper.isValidBinaryType(field)) {
+            BinaryFieldMapper.mapDataType(mappingBuilder, field);
+        }
+        // Multi field
+        else if (MultiFieldMapper.isValidMultiFieldType(field)) {
+            MultiFieldMapper.mapDataType(mappingBuilder, field);
+        }
+        // Completion Field
+        else if (CompletionFieldMapper.isValidCompletionFieldType(field)) {
+            CompletionFieldMapper.mapDataType(mappingBuilder, field);
+        }
+        // String field
+        else if (StringFieldMapper.isValidStringFieldType(field)) {
+            StringFieldMapper.mapDataType(mappingBuilder, field);
+        }
+        // Date field
+        else if (DateFieldMapper.isValidDateType(field)) {
+            DateFieldMapper.mapDataType(mappingBuilder, field);
+        }
+        // Collection type field
+        else if (BeanUtils.isCollectionType(field)) {
+            if (!BeanUtils.isValidCollectionType(field)) {
+                throw new IllegalArgumentException(
+                        String.format("Unsupported list class type, name[%s].", field.getName()));
+            }
+            Type genericType = BeanUtils.getCollectionGenericType(field);
+
+            //Nested Doc Type
+            mappingBuilder.field("type", "nested");
+            buildTypeProperty(mappingBuilder, (Class) genericType);
+        }
+        //Inner Doc Type
+        else {
+            mappingBuilder.field("type", "object");
+            buildTypeProperty(mappingBuilder, field.getType());
+        }
+        mappingBuilder.endObject();
+
+        return mappingBuilder;
+    }
+
+}

+ 42 - 0
src/main/java/org/es/mapping/mapper/MultiFieldMapper.java

@@ -0,0 +1,42 @@
+package org.es.mapping.mapper;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.es.mapping.annotations.fieldtype.MultiField;
+import org.es.mapping.annotations.fieldtype.MultiNestedField;
+import org.es.mapping.annotations.fieldtype.TokenCountField;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+public class MultiFieldMapper {
+
+    public static boolean isValidMultiFieldType(Field field) {
+        Class<?> fieldClass = field.getType();
+        return String.class.isAssignableFrom(fieldClass) && field.isAnnotationPresent(MultiField.class);
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, Field field) throws IOException {
+        if (!isValidMultiFieldType(field)) {
+            throw new IllegalArgumentException(
+                    String.format("field type[%s] is invalid type of string.", field.getType()));
+        }
+        MultiField multiField = field.getDeclaredAnnotation(MultiField.class);
+        StringFieldMapper.mapDataType(mappingBuilder, multiField.mainField());
+
+        mappingBuilder.startObject("fields");
+
+        for (MultiNestedField otherField : multiField.fields()) {
+            mappingBuilder.startObject(otherField.name());
+            StringFieldMapper.mapDataType(mappingBuilder, otherField.field());
+            mappingBuilder.endObject();
+        }
+
+        for (TokenCountField tokenCountField : multiField.tokenFields()) {
+            mappingBuilder.startObject(tokenCountField.name());
+            TokenCountFieldMapper.mapDataType(mappingBuilder, tokenCountField);
+            mappingBuilder.endObject();
+        }
+
+        mappingBuilder.endObject();
+    }
+}

+ 118 - 0
src/main/java/org/es/mapping/mapper/NumericFieldMapper.java

@@ -0,0 +1,118 @@
+package org.es.mapping.mapper;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.commons.lang.StringUtils;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.es.mapping.annotations.enums.NumberType;
+import org.es.mapping.annotations.fieldtype.NumberField;
+import org.es.mapping.utils.BeanUtils;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Map;
+
+public class NumericFieldMapper {
+
+    private static final Map<Class, NumberType> validNumericFieldClassMap = ImmutableMap.<Class, NumberType>builder()
+            .put(Double.class, NumberType.Double)
+            .put(BigDecimal.class, NumberType.Double)
+            .put(Float.class, NumberType.Float)
+            .put(Long.class, NumberType.Long)
+            .put(BigInteger.class, NumberType.Long)
+            .put(Integer.class, NumberType.Integer)
+            .put(Short.class, NumberType.Short)
+            .put(Byte.class, NumberType.Byte)
+
+            .put(double.class, NumberType.Double)
+            .put(float.class, NumberType.Float)
+            .put(long.class, NumberType.Long)
+            .put(int.class, NumberType.Integer)
+            .put(short.class, NumberType.Short)
+            .put(byte.class, NumberType.Byte)
+            .build();
+
+    public static boolean isValidNumberType(Field field) {
+        if (BeanUtils.isCollectionType(field)) {
+            if (!BeanUtils.isValidCollectionType(field)) {
+                throw new IllegalArgumentException(
+                        String.format("Unsupported more than one collection generic type, name[%s].", field.getName()));
+            }
+
+            Class genericTypeClass = (Class) BeanUtils.getCollectionGenericType(field);
+            return validNumericFieldClassMap.keySet().contains(genericTypeClass);
+        }
+
+
+        Class<?> fieldClass = field.getType();
+        return validNumericFieldClassMap.keySet().contains(fieldClass);
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, NumberField numberField) throws IOException {
+        mappingBuilder.field("type", numberField.type().code());
+
+        if (!numberField.coerce()) {
+            mappingBuilder.field("coerce", numberField.coerce());
+        }
+
+        if (numberField.boost() != 1.0f) {
+            mappingBuilder.field("boost", numberField.boost());
+        }
+
+        if (!numberField.doc_values()) {
+            mappingBuilder.field("doc_values", numberField.doc_values());
+        }
+
+        if (numberField.ignore_malformed()) {
+            mappingBuilder.field("ignore_malformed", numberField.ignore_malformed());
+        }
+
+        if (!numberField.include_in_all()) {
+            mappingBuilder.field("include_in_all", numberField.include_in_all());
+        }
+        else if (!numberField.index()) {
+            mappingBuilder.field("include_in_all", false);
+        }
+
+        if (!numberField.index()) {
+            mappingBuilder.field("index", numberField.index());
+        }
+
+        if (StringUtils.isNotBlank(numberField.null_value())) {
+            mappingBuilder.field("null_value", numberField.null_value());
+        }
+
+        if (numberField.store()) {
+            mappingBuilder.field("store", numberField.store());
+        }
+
+        if (numberField.type() == NumberType.ScaledFloat && numberField.scaling_factor() != 1) {
+            mappingBuilder.field("scaling_factor", numberField.scaling_factor());
+        }
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, Field field) throws IOException {
+        if (!isValidNumberType(field)) {
+            throw new IllegalArgumentException(
+                    String.format("field type[%s] is invalid type of number.", field.getType()));
+        }
+
+        if (field.isAnnotationPresent(NumberField.class)) {
+            NumberField numberField = field.getDeclaredAnnotation(NumberField.class);
+            mapDataType(mappingBuilder, numberField);
+            return;
+        }
+
+        if (BeanUtils.isCollectionType(field)) {
+            Class genericTypeClass = (Class) BeanUtils.getCollectionGenericType(field);
+            NumberType numberType = validNumericFieldClassMap.get(genericTypeClass);
+            mappingBuilder.field("type", numberType.code());
+        }
+        else {
+            NumberType numberType = validNumericFieldClassMap.get(field.getType());
+            mappingBuilder.field("type", numberType.code());
+        }
+
+    }
+}

+ 24 - 0
src/main/java/org/es/mapping/mapper/PercolatorFieldMapper.java

@@ -0,0 +1,24 @@
+package org.es.mapping.mapper;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.es.mapping.annotations.fieldtype.PercolatorField;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+public class PercolatorFieldMapper {
+
+    public static boolean isValidPercolatorFieldType(Field field) {
+        Class<?> fieldClass = field.getType();
+        return String.class.isAssignableFrom(fieldClass) && field.isAnnotationPresent(PercolatorField.class);
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, Field field) throws IOException {
+        if (!isValidPercolatorFieldType(field)) {
+            throw new IllegalArgumentException(
+                    String.format("field type[%s] is invalid type of percolator.", field.getType()));
+        }
+
+        mappingBuilder.field("type", "percolator");
+    }
+}

+ 56 - 0
src/main/java/org/es/mapping/mapper/RangeFieldMapper.java

@@ -0,0 +1,56 @@
+package org.es.mapping.mapper;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.es.mapping.annotations.enums.RangeType;
+import org.es.mapping.annotations.fieldtype.RangeField;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+public class RangeFieldMapper {
+
+    public static boolean isValidRangeFieldType(Field field) {
+        return field.isAnnotationPresent(RangeField.class);
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, RangeField rangeField) throws IOException {
+        mappingBuilder.field("type", rangeField.type().code());
+
+        if (rangeField.type() == RangeType.DateRange) {
+            mappingBuilder.field("format", rangeField.format());
+        }
+
+        if (!rangeField.coerce()) {
+            mappingBuilder.field("coerce", rangeField.coerce());
+        }
+
+        if (rangeField.boost() != 1.0f) {
+            mappingBuilder.field("boost", rangeField.boost());
+        }
+
+        if (!rangeField.include_in_all()) {
+            mappingBuilder.field("include_in_all", rangeField.include_in_all());
+        }
+        else if (!rangeField.index()) {
+            mappingBuilder.field("include_in_all", false);
+        }
+
+        if (!rangeField.index()) {
+            mappingBuilder.field("index", rangeField.index());
+        }
+
+        if (rangeField.store()) {
+            mappingBuilder.field("store", rangeField.store());
+        }
+
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, Field field) throws IOException {
+        if (!isValidRangeFieldType(field)) {
+            throw new IllegalArgumentException(
+                    String.format("field type[%s] is invalid type of range.", field.getType()));
+        }
+        RangeField stringField = field.getDeclaredAnnotation(RangeField.class);
+        mapDataType(mappingBuilder, stringField);
+    }
+}

+ 154 - 0
src/main/java/org/es/mapping/mapper/StringFieldMapper.java

@@ -0,0 +1,154 @@
+package org.es.mapping.mapper;
+
+import org.apache.commons.lang.StringUtils;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import org.es.mapping.annotations.enums.IndexOptions;
+import org.es.mapping.annotations.enums.SimilarityAlgorithm;
+import org.es.mapping.annotations.enums.StringType;
+import org.es.mapping.annotations.enums.TermVector;
+import org.es.mapping.annotations.fieldtype.StringField;
+import org.es.mapping.utils.BeanUtils;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+public class StringFieldMapper {
+
+    public static boolean isValidStringFieldType(Field field) {
+        if (BeanUtils.isCollectionType(field)) {
+            if (!BeanUtils.isValidCollectionType(field)) {
+                throw new IllegalArgumentException(
+                        String.format("Unsupported list class type, name[%s].", field.getName()));
+            }
+
+            Class genericTypeClass = (Class) BeanUtils.getCollectionGenericType(field);
+            return String.class.isAssignableFrom(genericTypeClass);
+        }
+
+        Class<?> fieldClass = field.getType();
+        return String.class.isAssignableFrom(fieldClass);
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, StringField stringField) throws IOException {
+        mappingBuilder.field("type", stringField.type().code());
+
+        if (stringField.boost() != 1.0f) {
+            mappingBuilder.field("boost", stringField.boost());
+        }
+
+        if (!stringField.doc_values()) {
+            mappingBuilder.field("doc_values", stringField.doc_values());
+        }
+
+        if (stringField.copy_to().length > 0) {
+            if (stringField.copy_to().length == 1) {
+                mappingBuilder.field("copy_to", stringField.copy_to()[0]);
+            }
+            else {
+                mappingBuilder.array("copy_to", stringField.copy_to());
+            }
+        }
+
+        if (stringField.eager_global_ordinals()) {
+            mappingBuilder.field("eager_global_ordinals", stringField.eager_global_ordinals());
+        }
+
+        if (stringField.ignore_above() != 2147483647) {
+            mappingBuilder.field("ignore_above", stringField.ignore_above());
+        }
+
+        if (!stringField.include_in_all()) {
+            mappingBuilder.field("include_in_all", stringField.include_in_all());
+        }
+        else if (!stringField.index()) {
+            mappingBuilder.field("include_in_all", false);
+        }
+
+        if (!stringField.index()) {
+            mappingBuilder.field("index", stringField.index());
+        }
+
+        if (stringField.index_options() != IndexOptions.Default) {
+            mappingBuilder.field("index_options", stringField.index_options().code());
+        }
+
+        if (StringUtils.isNotBlank(stringField.null_value())) {
+            mappingBuilder.field("null_value", stringField.null_value());
+        }
+
+        if (stringField.store()) {
+            mappingBuilder.field("store", stringField.store());
+        }
+
+        if (stringField.similarity() != SimilarityAlgorithm.Default) {
+            mappingBuilder.field("similarity", stringField.similarity().code());
+        }
+
+        // full text setting
+        if (stringField.type() == StringType.Text) {
+
+            if (StringUtils.isNotBlank(stringField.analyzer())) {
+                mappingBuilder.field("analyzer", stringField.analyzer());
+            }
+
+            if (StringUtils.isNotBlank(stringField.search_analyzer())) {
+                mappingBuilder.field("analyzer", stringField.search_analyzer());
+            }
+
+            if (StringUtils.isNotBlank(stringField.search_quote_analyzer())) {
+                mappingBuilder.field("analyzer", stringField.search_quote_analyzer());
+            }
+
+            if (stringField.position_increment_gap() != 100) {
+                mappingBuilder.field("position_increment_gap", stringField.position_increment_gap());
+            }
+
+            if (!stringField.norms()) {
+                mappingBuilder.field("norms", stringField.norms());
+            }
+
+            if (stringField.term_vector() != TermVector.No) {
+                mappingBuilder.field("term_vector", stringField.term_vector().code());
+            }
+
+            if (stringField.fielddata().enable() && stringField.type() == StringType.Text) {
+                mappingBuilder.field("fielddata", stringField.fielddata().enable());
+
+                if (stringField.fielddata().frequency().enable()) {
+
+                    mappingBuilder.startObject("fielddata_frequency_filter");
+
+                    if (stringField.fielddata().frequency().min() > 0) {
+                        mappingBuilder.field("min", stringField.fielddata().frequency().min());
+                    }
+
+                    if (stringField.fielddata().frequency().max() > 0) {
+                        mappingBuilder.field("max", stringField.fielddata().frequency().max());
+                    }
+
+                    if (stringField.fielddata().frequency().min_segment_size() > 0) {
+                        mappingBuilder.field("min_segment_size", stringField.fielddata().frequency().min_segment_size());
+                    }
+
+                    mappingBuilder.endObject();
+                }
+            }
+        }
+    }
+
+    public static void mapDataType(XContentBuilder mappingBuilder, Field field) throws IOException {
+        if (!isValidStringFieldType(field)) {
+            throw new IllegalArgumentException(
+                    String.format("field type[%s] is invalid type of string.", field.getType()));
+        }
+
+        if (field.isAnnotationPresent(StringField.class)) {
+            StringField stringField = field.getDeclaredAnnotation(StringField.class);
+            mapDataType(mappingBuilder, stringField);
+            return;
+        }
+
+        mappingBuilder.field("type", "keyword");
+    }
+}

+ 46 - 0
src/main/java/org/es/mapping/mapper/TokenCountFieldMapper.java

@@ -0,0 +1,46 @@
+package org.es.mapping.mapper;
+
+import org.apache.commons.lang.StringUtils;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.es.mapping.annotations.fieldtype.TokenCountField;
+
+import java.io.IOException;
+
+public class TokenCountFieldMapper {
+
+    public static void mapDataType(XContentBuilder mappingBuilder, TokenCountField tokenCountField) throws IOException {
+        mappingBuilder.field("type", "token_count");
+        mappingBuilder.field("analyzer", tokenCountField.analyzer());
+
+        if (!tokenCountField.enable_position_increments()) {
+            mappingBuilder.field("enable_position_increments", tokenCountField.enable_position_increments());
+        }
+
+        if (tokenCountField.boost() != 1.0f) {
+            mappingBuilder.field("boost", tokenCountField.boost());
+        }
+
+        if (!tokenCountField.doc_values()) {
+            mappingBuilder.field("doc_values", tokenCountField.doc_values());
+        }
+
+        if (!tokenCountField.include_in_all()) {
+            mappingBuilder.field("include_in_all", tokenCountField.include_in_all());
+        }
+        else if (!tokenCountField.index()) {
+            mappingBuilder.field("include_in_all", false);
+        }
+
+        if (!tokenCountField.index()) {
+            mappingBuilder.field("index", tokenCountField.index());
+        }
+
+        if (StringUtils.isNotBlank(tokenCountField.null_value())) {
+            mappingBuilder.field("null_value", tokenCountField.null_value());
+        }
+
+        if (tokenCountField.store()) {
+            mappingBuilder.field("store", tokenCountField.store());
+        }
+    }
+}

+ 49 - 0
src/main/java/org/es/mapping/utils/BeanUtils.java

@@ -0,0 +1,49 @@
+package org.es.mapping.utils;
+
+import com.google.common.collect.Lists;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Set;
+
+public class BeanUtils {
+    public static Field[] retrieveFields(Class clazz) {
+        List<Field> fields = Lists.newArrayList();
+        Class targetClass = clazz;
+        do {
+            fields.addAll(Lists.newArrayList(targetClass.getDeclaredFields()));
+            targetClass = targetClass.getSuperclass();
+        }
+        while (targetClass != null && targetClass != Object.class);
+
+        return fields.toArray(new Field[fields.size()]);
+    }
+
+    public static boolean isCollectionType(Field field) {
+        return field.getType() == List.class
+                || field.getType() == Set.class
+                || field.getType().isArray();
+    }
+
+    public static boolean isValidCollectionType(Field field) {
+        if (field.getType().isArray()) {
+            return true;
+        }
+
+        if (isCollectionType(field)) {
+            Type[] argTypes = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
+            return argTypes.length == 1;
+        }
+        return false;
+
+    }
+
+    public static Type getCollectionGenericType(Field field) {
+        if (field.getType().isArray()) {
+            return ((Class) field.getGenericType()).getComponentType();
+        }
+        return ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
+    }
+}

+ 70 - 0
src/main/java/org/es/mapping/utils/StringUtils.java

@@ -0,0 +1,70 @@
+package org.es.mapping.utils;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class StringUtils {
+
+    public final static String EMPTY = "";
+
+    public static boolean equals(Object arg1, Object arg2) {
+        if (arg1 == null || arg2 == null) {
+            return false;
+        }
+        return arg1.toString().equals(arg2.toString());
+    }
+
+
+    public static boolean isBlank(String str) {
+        int strLen;
+        if (str == null || (strLen = str.length()) == 0) {
+            return true;
+        }
+        for (int i = 0; i < strLen; i++) {
+            if (!Character.isWhitespace(str.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static boolean isNotBlank(String str) {
+        return !StringUtils.isBlank(str);
+    }
+
+    public static boolean isEmpty(String str) {
+        return str == null || str.length() == 0;
+    }
+
+    public static Boolean isNumeric(String... params) {
+        Pattern pattern = Pattern.compile("^[0-9]+$");
+        for (String param : params) {
+            if (StringUtils.isEmpty(param)) {
+                return false;
+            }
+            Matcher matcher = pattern.matcher(param);
+            if (!matcher.matches()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static boolean isAllBlank(String... params) {
+        for (String param : params) {
+            if (!StringUtils.isBlank(param)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static boolean isAllNotBlank(String... params) {
+        for (String param : params) {
+            if (StringUtils.isBlank(param)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}

+ 8 - 3
src/main/java/org/es/sql/bean/ElasticDslContext.java

@@ -6,17 +6,22 @@ public class ElasticDslContext {
     //SQL
     private SQLQueryExpr queryExpr;
     //SQL Args
-    private org.es.sql.bean.SQLArgs SQLArgs;
+    private SQLArgs SQLArgs;
     //Result
     private ElasticSqlParseResult parseResult;
 
-    public ElasticDslContext(SQLQueryExpr queryExpr, org.es.sql.bean.SQLArgs SQLArgs) {
+    public ElasticDslContext(SQLQueryExpr queryExpr, SQLArgs SQLArgs) {
         this.queryExpr = queryExpr;
         this.SQLArgs = SQLArgs;
         parseResult = new ElasticSqlParseResult();
     }
 
-    public org.es.sql.bean.SQLArgs getSQLArgs() {
+    public ElasticDslContext(SQLQueryExpr queryExpr) {
+        this.queryExpr = queryExpr;
+        parseResult = new ElasticSqlParseResult();
+    }
+
+    public SQLArgs getSQLArgs() {
         return SQLArgs;
     }
 

+ 1 - 0
src/main/java/org/es/sql/bean/ElasticSqlParseResult.java

@@ -11,6 +11,7 @@ import org.elasticsearch.search.aggregations.AggregationBuilder;
 import org.elasticsearch.search.sort.SortBuilder;
 import org.es.sql.utils.ElasticMockClient;
 
+
 import java.util.List;
 
 

+ 1 - 0
src/main/java/org/es/sql/bean/ElasticSqlQueryFields.java

@@ -1,5 +1,6 @@
 package org.es.sql.bean;
 
+
 import org.es.sql.enums.QueryFieldType;
 
 public class ElasticSqlQueryFields {

+ 0 - 45
src/main/java/org/es/sql/enums/SortOption.java

@@ -1,45 +0,0 @@
-package org.es.sql.enums;
-
-public enum SortOption {
-    SUM {
-        @Override
-        public String mode() {
-            return "sum";
-        }
-    },
-    MIN {
-        @Override
-        public String mode() {
-            return "min";
-        }
-    },
-    MAX {
-        @Override
-        public String mode() {
-            return "max";
-        }
-    },
-    AVG {
-        @Override
-        public String mode() {
-            return "avg";
-        }
-    };
-
-    public static SortOption get(String mode) {
-        SortOption op = null;
-        for (SortOption option : SortOption.values()) {
-            if (option.mode().equalsIgnoreCase(mode)) {
-                op = option;
-            }
-        }
-        return op;
-    }
-
-    public abstract String mode();
-
-    @Override
-    public String toString() {
-        return mode();
-    }
-}

+ 1 - 0
src/main/java/org/es/sql/helper/ElasticSqlDateParseHelper.java

@@ -4,6 +4,7 @@ import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr;
 import org.es.sql.exception.ElasticSql2DslException;
 import org.es.sql.utils.Constants;
 
+
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;

+ 4 - 6
src/main/java/org/es/sql/parser/ElasticSql2DslParser.java

@@ -15,6 +15,7 @@ import org.es.sql.listener.ParseActionListener;
 import org.es.sql.listener.ParseActionListenerAdapter;
 import org.es.sql.parser.sql.*;
 
+
 import java.lang.reflect.Array;
 import java.util.Collection;
 import java.util.List;
@@ -45,19 +46,16 @@ public class ElasticSql2DslParser {
             throw new ElasticSql2DslException(ex);
         }
 
-        SQLArgs sqlParamValues = null;
-        if (sqlArgs != null && sqlArgs.length > 0) {
-            sqlParamValues = new SQLArgs(sqlArgs);
-        }
-
+        SQLArgs sqlParamValues = (sqlArgs != null && sqlArgs.length > 0) ? new SQLArgs(sqlArgs) : null;
         ElasticDslContext elasticDslContext = new ElasticDslContext(queryExpr, sqlParamValues);
+
         if (queryExpr.getSubQuery().getQuery() instanceof ElasticSqlSelectQueryBlock) {
             for (QueryParser sqlParser : buildSqlParserChain(parseActionListener)) {
                 sqlParser.parse(elasticDslContext);
             }
         }
         else {
-            throw new ElasticSql2DslException("[syntax error] only support Select Sql");
+            throw new ElasticSql2DslException("[syntax error] Sql only support Select Sql");
         }
         return elasticDslContext.getParseResult();
     }

+ 1 - 0
src/main/java/org/es/sql/parser/query/exact/BetweenAndAtomQueryParser.java

@@ -10,6 +10,7 @@ import org.es.sql.exception.ElasticSql2DslException;
 import org.es.sql.helper.ElasticSqlArgConverter;
 import org.es.sql.listener.ParseActionListener;
 
+
 public class BetweenAndAtomQueryParser extends AbstractAtomExactQueryParser {
 
     public BetweenAndAtomQueryParser(ParseActionListener parseActionListener) {

+ 3 - 3
src/main/java/org/es/sql/parser/query/exact/BinaryAtomQueryParser.java

@@ -19,12 +19,12 @@ public class BinaryAtomQueryParser extends AbstractAtomExactQueryParser {
         super(parseActionListener);
     }
 
-    public AtomQuery parseBinaryQuery(SQLBinaryOpExpr binQueryExpr, String queryAs, SQLArgs SQLArgs) {
+    public AtomQuery parseBinaryQuery(SQLBinaryOpExpr binQueryExpr, String queryAs, SQLArgs sqlArgs) {
         SQLBinaryOperator binaryOperator = binQueryExpr.getOperator();
 
         //EQ NEQ
         if (SQLBinaryOperator.Equality == binaryOperator || SQLBinaryOperator.LessThanOrGreater == binaryOperator || SQLBinaryOperator.NotEqual == binaryOperator) {
-            Object targetVal = ElasticSqlArgConverter.convertSqlArg(binQueryExpr.getRight(), SQLArgs);
+            Object targetVal = ElasticSqlArgConverter.convertSqlArg(binQueryExpr.getRight(), sqlArgs);
 
             SQLConditionOperator operator = SQLBinaryOperator.Equality == binaryOperator ? SQLConditionOperator.Equality : SQLConditionOperator.NotEqual;
 
@@ -60,7 +60,7 @@ public class BinaryAtomQueryParser extends AbstractAtomExactQueryParser {
                 operator = SQLConditionOperator.LessThanOrEqual;
             }
 
-            Object targetVal = ElasticSqlArgConverter.convertSqlArg(binQueryExpr.getRight(), SQLArgs);
+            Object targetVal = ElasticSqlArgConverter.convertSqlArg(binQueryExpr.getRight(), sqlArgs);
             return parseCondition(binQueryExpr.getLeft(), operator, new Object[]{targetVal}, queryAs, new IConditionExactQueryBuilder() {
                 @Override
                 public QueryBuilder buildQuery(String queryFieldName, SQLConditionOperator operator, Object[] rightParamValues) {

+ 1 - 0
src/main/java/org/es/sql/parser/query/exact/InListAtomQueryParser.java

@@ -11,6 +11,7 @@ import org.es.sql.exception.ElasticSql2DslException;
 import org.es.sql.helper.ElasticSqlArgConverter;
 import org.es.sql.listener.ParseActionListener;
 
+
 public class InListAtomQueryParser extends AbstractAtomExactQueryParser {
 
     public InListAtomQueryParser(ParseActionListener parseActionListener) {

+ 1 - 1
src/main/java/org/es/sql/parser/query/method/AbstractFieldSpecificMethodQueryParser.java

@@ -48,7 +48,7 @@ public abstract class AbstractFieldSpecificMethodQueryParser extends Parameteriz
                     String.format("[syntax error] query field can not support type[%s]", queryField.getQueryFieldType()));
         }
 
-        parseActionListener.onAtomMethodQueryConditionParse(queryField, invocation.getSQLArgs().getArgs());
+        parseActionListener.onAtomMethodQueryConditionParse(queryField, invocation.getSqlArgs().getArgs());
 
         return atomQuery;
     }

+ 9 - 7
src/main/java/org/es/sql/parser/query/method/MethodInvocation.java

@@ -2,30 +2,32 @@ package org.es.sql.parser.query.method;
 
 import com.alibaba.druid.sql.ast.SQLExpr;
 import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr;
+import org.es.sql.bean.SQLArgs;
 import org.es.sql.helper.ElasticSqlArgConverter;
 
+
 import java.util.List;
 
 public class MethodInvocation {
     private final SQLMethodInvokeExpr methodInvokeExpr;
     private final String queryAs;
-    private final org.es.sql.bean.SQLArgs SQLArgs;
+    private final SQLArgs sqlArgs;
 
-    public MethodInvocation(SQLMethodInvokeExpr methodInvokeExpr, String queryAs, org.es.sql.bean.SQLArgs SQLArgs) {
+    public MethodInvocation(SQLMethodInvokeExpr methodInvokeExpr, String queryAs, SQLArgs sqlArgs) {
         if (methodInvokeExpr == null) {
             throw new IllegalArgumentException("method invoke expression can not be null");
         }
         this.methodInvokeExpr = methodInvokeExpr;
         this.queryAs = queryAs;
-        this.SQLArgs = SQLArgs;
+        this.sqlArgs = sqlArgs;
     }
 
     public String getQueryAs() {
         return queryAs;
     }
 
-    public org.es.sql.bean.SQLArgs getSQLArgs() {
-        return SQLArgs;
+    public SQLArgs getSqlArgs() {
+        return sqlArgs;
     }
 
     public String getMethodName() {
@@ -50,12 +52,12 @@ public class MethodInvocation {
 
     public Object getParameterAsObject(int index) {
         SQLExpr paramExpr = methodInvokeExpr.getParameters().get(index);
-        return ElasticSqlArgConverter.convertSqlArg(paramExpr, SQLArgs, false);
+        return ElasticSqlArgConverter.convertSqlArg(paramExpr, sqlArgs, false);
     }
 
     public String getParameterAsFormatDate(int index) {
         SQLExpr paramExpr = methodInvokeExpr.getParameters().get(index);
-        return ElasticSqlArgConverter.convertSqlArg(paramExpr, SQLArgs, true).toString();
+        return ElasticSqlArgConverter.convertSqlArg(paramExpr, sqlArgs, true).toString();
     }
 
     public String getParameterAsString(int index) {

+ 1 - 0
src/main/java/org/es/sql/parser/query/method/MethodQueryParser.java

@@ -1,5 +1,6 @@
 package org.es.sql.parser.query.method;
 
+
 import org.es.sql.bean.AtomQuery;
 import org.es.sql.exception.ElasticSql2DslException;
 import org.es.sql.parser.query.method.expr.MethodExpression;

+ 1 - 0
src/main/java/org/es/sql/parser/query/method/ParameterizedMethodQueryParser.java

@@ -1,5 +1,6 @@
 package org.es.sql.parser.query.method;
 
+
 import org.es.sql.bean.AtomQuery;
 import org.es.sql.exception.ElasticSql2DslException;
 import org.es.sql.helper.ElasticSqlMethodInvokeHelper;

+ 1 - 0
src/main/java/org/es/sql/parser/query/method/expr/ParameterizedMethodExpression.java

@@ -1,5 +1,6 @@
 package org.es.sql.parser.query.method.expr;
 
+
 import org.es.sql.parser.query.method.MethodInvocation;
 
 import java.util.Map;

+ 72 - 0
src/main/java/org/es/sql/parser/query/method/join/HasChildAtomQueryParser.java

@@ -0,0 +1,72 @@
+package org.es.sql.parser.query.method.join;
+
+import com.alibaba.druid.sql.ast.SQLExpr;
+import com.google.common.collect.ImmutableList;
+import org.apache.lucene.search.join.ScoreMode;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.HasChildQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.es.sql.bean.AtomQuery;
+import org.es.sql.bean.SQLArgs;
+import org.es.sql.exception.ElasticSql2DslException;
+import org.es.sql.helper.ElasticSqlMethodInvokeHelper;
+import org.es.sql.parser.query.method.MethodInvocation;
+import org.es.sql.parser.query.method.MethodQueryParser;
+import org.es.sql.parser.sql.BoolExpressionParser;
+
+import java.util.List;
+
+/**
+ * has_child(childType, filterExpression, minChildren, maxChildren)
+ * <p>
+ * has_child('collection_plan', principal > 1000)
+ * <p>
+ * has_child('collection_plan', principal > 1000, 1, 4)
+ *
+ * @author chennan
+ */
+public class HasChildAtomQueryParser implements MethodQueryParser {
+
+    private static List<String> HAS_CHILD_METHOD = ImmutableList.of("has_child", "hasChild", "has_child_query", "hasChildQuery");
+
+    @Override
+    public List<String> defineMethodNames() {
+        return HAS_CHILD_METHOD;
+    }
+
+    @Override
+    public boolean isMatchMethodInvocation(MethodInvocation invocation) {
+        return ElasticSqlMethodInvokeHelper.isMethodOf(defineMethodNames(), invocation.getMethodName());
+    }
+
+    @Override
+    public void checkMethodInvocation(MethodInvocation invocation) throws ElasticSql2DslException {
+        if (invocation.getParameterCount() != 2 || invocation.getParameterCount() != 4) {
+            throw new ElasticSql2DslException(
+                    String.format("[syntax error] There's no %s args method named [%s].",
+                            invocation.getParameterCount(), invocation.getMethodName()));
+        }
+    }
+
+    @Override
+    public AtomQuery parseAtomMethodQuery(MethodInvocation invocation) throws ElasticSql2DslException {
+        String childType = invocation.getParameterAsString(0);
+        SQLExpr filter = invocation.getParameter(1);
+
+        BoolExpressionParser boolExpressionParser = new BoolExpressionParser();
+        String queryAs = invocation.getQueryAs();
+        SQLArgs sqlArgs = invocation.getSqlArgs();
+
+        BoolQueryBuilder filterBuilder = boolExpressionParser.parseBoolQueryExpr(filter, queryAs, sqlArgs);
+        HasChildQueryBuilder hasChildQueryBuilder = QueryBuilders.hasChildQuery(childType, filterBuilder, ScoreMode.None);
+
+        if (invocation.getParameterCount() == 4) {
+            Long minChildren = invocation.getParameterAsLong(2);
+            Long maxChildren = invocation.getParameterAsLong(3);
+
+            hasChildQueryBuilder.minMaxChildren(minChildren.intValue(), maxChildren.intValue());
+        }
+
+        return new AtomQuery(hasChildQueryBuilder);
+    }
+}

+ 63 - 0
src/main/java/org/es/sql/parser/query/method/join/HasParentAtomQueryParser.java

@@ -0,0 +1,63 @@
+package org.es.sql.parser.query.method.join;
+
+import com.alibaba.druid.sql.ast.SQLExpr;
+import com.google.common.collect.ImmutableList;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.HasParentQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+
+import org.es.sql.bean.AtomQuery;
+import org.es.sql.bean.SQLArgs;
+import org.es.sql.exception.ElasticSql2DslException;
+import org.es.sql.helper.ElasticSqlMethodInvokeHelper;
+import org.es.sql.parser.query.method.MethodInvocation;
+import org.es.sql.parser.query.method.MethodQueryParser;
+import org.es.sql.parser.sql.BoolExpressionParser;
+
+import java.util.List;
+
+/**
+ * has_parent(parentType, filterExpression)
+ * <p>
+ * has_parent('investment', principal > 100 and status='SUCCESS')
+ *
+ * @author chennan
+ */
+public class HasParentAtomQueryParser implements MethodQueryParser {
+
+    private static List<String> HAS_PARENT_METHOD = ImmutableList.of("has_parent", "hasParent", "has_parent_query", "hasParentQuery");
+
+    @Override
+    public List<String> defineMethodNames() {
+        return HAS_PARENT_METHOD;
+    }
+
+    @Override
+    public boolean isMatchMethodInvocation(MethodInvocation invocation) {
+        return ElasticSqlMethodInvokeHelper.isMethodOf(defineMethodNames(), invocation.getMethodName());
+    }
+
+    @Override
+    public void checkMethodInvocation(MethodInvocation invocation) throws ElasticSql2DslException {
+        if (invocation.getParameterCount() != 2) {
+            throw new ElasticSql2DslException(
+                    String.format("[syntax error] There's no %s args method named [%s].",
+                            invocation.getParameterCount(), invocation.getMethodName()));
+        }
+    }
+
+    @Override
+    public AtomQuery parseAtomMethodQuery(MethodInvocation invocation) throws ElasticSql2DslException {
+        String parentType = invocation.getParameterAsString(0);
+        SQLExpr filter = invocation.getParameter(1);
+
+        BoolExpressionParser boolExpressionParser = new BoolExpressionParser();
+        String queryAs = invocation.getQueryAs();
+        SQLArgs sqlArgs = invocation.getSqlArgs();
+
+        BoolQueryBuilder filterBuilder = boolExpressionParser.parseBoolQueryExpr(filter, queryAs, sqlArgs);
+        HasParentQueryBuilder hasParentQueryBuilder = QueryBuilders.hasParentQuery(parentType, filterBuilder, false);
+
+        return new AtomQuery(hasParentQueryBuilder);
+    }
+}

+ 51 - 0
src/main/java/org/es/sql/parser/query/method/join/JoinAtomQueryParser.java

@@ -0,0 +1,51 @@
+package org.es.sql.parser.query.method.join;
+
+import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr;
+import com.google.common.collect.ImmutableList;
+import org.es.sql.bean.AtomQuery;
+import org.es.sql.bean.SQLArgs;
+import org.es.sql.exception.ElasticSql2DslException;
+import org.es.sql.parser.query.method.MethodInvocation;
+import org.es.sql.parser.query.method.MethodQueryParser;
+
+
+import java.util.List;
+import java.util.function.Predicate;
+
+public class JoinAtomQueryParser {
+
+    private final List<MethodQueryParser> joinQueryParsers;
+
+    public JoinAtomQueryParser() {
+        joinQueryParsers = ImmutableList.of(
+                new HasParentAtomQueryParser(),
+                new HasChildAtomQueryParser()
+        );
+    }
+
+    public Boolean isJoinAtomQuery(MethodInvocation invocation) {
+        return joinQueryParsers.stream().anyMatch(new Predicate<MethodQueryParser>() {
+            @Override
+            public boolean test(MethodQueryParser methodQueryParser) {
+                return methodQueryParser.isMatchMethodInvocation(invocation);
+            }
+        });
+    }
+
+    public AtomQuery parseJoinAtomQuery(SQLMethodInvokeExpr methodQueryExpr, String queryAs, SQLArgs SQLArgs) {
+        MethodInvocation methodInvocation = new MethodInvocation(methodQueryExpr, queryAs, SQLArgs);
+        MethodQueryParser joinAtomQueryParser = getQueryParser(methodInvocation);
+        return joinAtomQueryParser.parseAtomMethodQuery(methodInvocation);
+    }
+
+    private MethodQueryParser getQueryParser(MethodInvocation methodInvocation) {
+        for (MethodQueryParser joinQueryParserItem : joinQueryParsers) {
+            if (joinQueryParserItem.isMatchMethodInvocation(methodInvocation)) {
+                return joinQueryParserItem;
+            }
+        }
+        throw new ElasticSql2DslException(
+                String.format("[syntax error] Can not support join query expr[%s] condition",
+                        methodInvocation.getMethodName()));
+    }
+}

+ 3 - 3
src/main/java/org/es/sql/parser/query/method/script/ScriptAtomQueryParser.java

@@ -50,9 +50,9 @@ public class ScriptAtomQueryParser extends ParameterizedMethodQueryParser {
 
         if (MapUtils.isNotEmpty(extraParamMap)) {
             Map<String, Object> scriptParamMap = generateRawTypeParameterMap(invocation);
-            Script scriptObject = new Script(Script.DEFAULT_SCRIPT_TYPE, Script.DEFAULT_SCRIPT_LANG, script, scriptParamMap);
-
-            return new AtomQuery(QueryBuilders.scriptQuery(scriptObject));
+            return new AtomQuery(QueryBuilders.scriptQuery(
+                    new Script(Script.DEFAULT_SCRIPT_TYPE, Script.DEFAULT_SCRIPT_LANG, script, scriptParamMap))
+            );
         }
         return new AtomQuery(QueryBuilders.scriptQuery(new Script(script)));
     }

+ 1 - 0
src/main/java/org/es/sql/parser/query/method/term/FuzzyAtomQueryParser.java

@@ -13,6 +13,7 @@ import org.es.sql.listener.ParseActionListener;
 import org.es.sql.parser.query.method.AbstractFieldSpecificMethodQueryParser;
 import org.es.sql.parser.query.method.MethodInvocation;
 
+
 import java.util.List;
 import java.util.Map;
 

+ 1 - 0
src/main/java/org/es/sql/parser/query/method/term/PrefixAtomQueryParser.java

@@ -12,6 +12,7 @@ import org.es.sql.listener.ParseActionListener;
 import org.es.sql.parser.query.method.AbstractFieldSpecificMethodQueryParser;
 import org.es.sql.parser.query.method.MethodInvocation;
 
+
 import java.util.List;
 import java.util.Map;
 

+ 1 - 0
src/main/java/org/es/sql/parser/query/method/term/RegexpAtomQueryParser.java

@@ -9,6 +9,7 @@ import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.index.query.RegexpFlag;
 import org.elasticsearch.index.query.RegexpQueryBuilder;
+
 import org.es.sql.exception.ElasticSql2DslException;
 import org.es.sql.listener.ParseActionListener;
 import org.es.sql.parser.query.method.AbstractFieldSpecificMethodQueryParser;

+ 1 - 0
src/main/java/org/es/sql/parser/query/method/term/TermAtomQueryParser.java

@@ -12,6 +12,7 @@ import org.es.sql.listener.ParseActionListener;
 import org.es.sql.parser.query.method.AbstractFieldSpecificMethodQueryParser;
 import org.es.sql.parser.query.method.MethodInvocation;
 
+
 import java.util.List;
 import java.util.Map;
 

+ 8 - 7
src/main/java/org/es/sql/parser/query/method/term/TermLevelAtomQueryParser.java

@@ -10,6 +10,7 @@ import org.es.sql.parser.query.method.MethodInvocation;
 import org.es.sql.parser.query.method.MethodQueryParser;
 
 import java.util.List;
+import java.util.function.Predicate;
 
 public class TermLevelAtomQueryParser {
 
@@ -27,16 +28,16 @@ public class TermLevelAtomQueryParser {
     }
 
     public Boolean isTermLevelAtomQuery(MethodInvocation invocation) {
-        for (MethodQueryParser methodQueryParserItem : methodQueryParsers) {
-            if (methodQueryParserItem.isMatchMethodInvocation(invocation)) {
-                return Boolean.TRUE;
+        return methodQueryParsers.stream().anyMatch(new Predicate<MethodQueryParser>() {
+            @Override
+            public boolean test(MethodQueryParser methodQueryParser) {
+                return methodQueryParser.isMatchMethodInvocation(invocation);
             }
-        }
-        return Boolean.TRUE;
+        });
     }
 
-    public AtomQuery parseTermLevelAtomQuery(SQLMethodInvokeExpr methodQueryExpr, String queryAs, SQLArgs SQLArgs) {
-        MethodInvocation methodInvocation = new MethodInvocation(methodQueryExpr, queryAs, SQLArgs);
+    public AtomQuery parseTermLevelAtomQuery(SQLMethodInvokeExpr methodQueryExpr, String queryAs, SQLArgs sqlArgs) {
+        MethodInvocation methodInvocation = new MethodInvocation(methodQueryExpr, queryAs, sqlArgs);
         MethodQueryParser matchAtomQueryParser = getQueryParser(methodInvocation);
         return matchAtomQueryParser.parseAtomMethodQuery(methodInvocation);
     }

+ 1 - 0
src/main/java/org/es/sql/parser/query/method/term/TermsAtomQueryParser.java

@@ -13,6 +13,7 @@ import org.es.sql.listener.ParseActionListener;
 import org.es.sql.parser.query.method.AbstractFieldSpecificMethodQueryParser;
 import org.es.sql.parser.query.method.MethodInvocation;
 
+
 import java.util.List;
 import java.util.Map;
 

+ 29 - 15
src/main/java/org/es/sql/parser/sql/AbstractQueryConditionParser.java

@@ -16,17 +16,19 @@ import org.es.sql.enums.SQLBoolOperator;
 import org.es.sql.enums.SQLConditionType;
 import org.es.sql.exception.ElasticSql2DslException;
 import org.es.sql.listener.ParseActionListener;
+import org.es.sql.listener.ParseActionListenerAdapter;
 import org.es.sql.parser.query.exact.BetweenAndAtomQueryParser;
 import org.es.sql.parser.query.exact.BinaryAtomQueryParser;
 import org.es.sql.parser.query.exact.InListAtomQueryParser;
 import org.es.sql.parser.query.method.MethodInvocation;
 import org.es.sql.parser.query.method.fulltext.FullTextAtomQueryParser;
+import org.es.sql.parser.query.method.join.JoinAtomQueryParser;
 import org.es.sql.parser.query.method.script.ScriptAtomQueryParser;
 import org.es.sql.parser.query.method.term.TermLevelAtomQueryParser;
 
 import java.util.List;
 
-public abstract class AbstractQueryConditionParser implements QueryParser {
+public class BoolExpressionParser {
 
     private final TermLevelAtomQueryParser termLevelAtomQueryParser;
     private final ScriptAtomQueryParser scriptAtomQueryParser;
@@ -35,7 +37,13 @@ public abstract class AbstractQueryConditionParser implements QueryParser {
     private final InListAtomQueryParser inListQueryParser;
     private final BetweenAndAtomQueryParser betweenAndQueryParser;
 
-    public AbstractQueryConditionParser(ParseActionListener parseActionListener) {
+    private final JoinAtomQueryParser joinAtomQueryParser;
+
+    public BoolExpressionParser() {
+        this(new ParseActionListenerAdapter());
+    }
+
+    public BoolExpressionParser(ParseActionListener parseActionListener) {
         termLevelAtomQueryParser = new TermLevelAtomQueryParser(parseActionListener);
         fullTextAtomQueryParser = new FullTextAtomQueryParser(parseActionListener);
         binaryQueryParser = new BinaryAtomQueryParser(parseActionListener);
@@ -43,10 +51,12 @@ public abstract class AbstractQueryConditionParser implements QueryParser {
         betweenAndQueryParser = new BetweenAndAtomQueryParser(parseActionListener);
 
         scriptAtomQueryParser = new ScriptAtomQueryParser();
+        joinAtomQueryParser = new JoinAtomQueryParser();
     }
 
-    protected BoolQueryBuilder parseQueryConditionExpr(SQLExpr conditionExpr, String queryAs, SQLArgs SQLArgs) {
-        SQLCondition SQLCondition = recursiveParseQueryCondition(conditionExpr, queryAs, SQLArgs);
+
+    public BoolQueryBuilder parseBoolQueryExpr(SQLExpr conditionExpr, String queryAs, SQLArgs SQLArgs) {
+        SQLCondition SQLCondition = recursiveParseBoolQueryExpr(conditionExpr, queryAs, SQLArgs);
         SQLBoolOperator operator = SQLCondition.getOperator();
 
         if (SQLConditionType.Atom == SQLCondition.getSQLConditionType()) {
@@ -55,7 +65,7 @@ public abstract class AbstractQueryConditionParser implements QueryParser {
         return mergeAtomQuery(SQLCondition.getQueryList(), operator);
     }
 
-    private SQLCondition recursiveParseQueryCondition(SQLExpr conditionExpr, String queryAs, SQLArgs SQLArgs) {
+    private SQLCondition recursiveParseBoolQueryExpr(SQLExpr conditionExpr, String queryAs, SQLArgs SQLArgs) {
         if (conditionExpr instanceof SQLBinaryOpExpr) {
             SQLBinaryOpExpr binOpExpr = (SQLBinaryOpExpr) conditionExpr;
             SQLBinaryOperator binOperator = binOpExpr.getOperator();
@@ -63,8 +73,8 @@ public abstract class AbstractQueryConditionParser implements QueryParser {
             if (SQLBinaryOperator.BooleanAnd == binOperator || SQLBinaryOperator.BooleanOr == binOperator) {
                 SQLBoolOperator operator = SQLBinaryOperator.BooleanAnd == binOperator ? SQLBoolOperator.AND : SQLBoolOperator.OR;
 
-                SQLCondition leftCondition = recursiveParseQueryCondition(binOpExpr.getLeft(), queryAs, SQLArgs);
-                SQLCondition rightCondition = recursiveParseQueryCondition(binOpExpr.getRight(), queryAs, SQLArgs);
+                SQLCondition leftCondition = recursiveParseBoolQueryExpr(binOpExpr.getLeft(), queryAs, SQLArgs);
+                SQLCondition rightCondition = recursiveParseBoolQueryExpr(binOpExpr.getRight(), queryAs, SQLArgs);
 
                 List<AtomQuery> mergedQueryList = Lists.newArrayList();
                 combineQueryBuilder(mergedQueryList, leftCondition, operator);
@@ -74,7 +84,7 @@ public abstract class AbstractQueryConditionParser implements QueryParser {
             }
         }
         else if (conditionExpr instanceof SQLNotExpr) {
-            SQLCondition innerSQLCondition = recursiveParseQueryCondition(((SQLNotExpr) conditionExpr).getExpr(), queryAs, SQLArgs);
+            SQLCondition innerSQLCondition = recursiveParseBoolQueryExpr(((SQLNotExpr) conditionExpr).getExpr(), queryAs, SQLArgs);
 
             SQLBoolOperator operator = innerSQLCondition.getOperator();
             if (SQLConditionType.Atom == innerSQLCondition.getSQLConditionType()) {
@@ -90,32 +100,36 @@ public abstract class AbstractQueryConditionParser implements QueryParser {
         return new SQLCondition(parseAtomQueryCondition(conditionExpr, queryAs, SQLArgs), SQLConditionType.Atom);
     }
 
-    private AtomQuery parseAtomQueryCondition(SQLExpr sqlConditionExpr, String queryAs, SQLArgs SQLArgs) {
+    private AtomQuery parseAtomQueryCondition(SQLExpr sqlConditionExpr, String queryAs, SQLArgs sqlArgs) {
         if (sqlConditionExpr instanceof SQLMethodInvokeExpr) {
             SQLMethodInvokeExpr methodQueryExpr = (SQLMethodInvokeExpr) sqlConditionExpr;
 
-            MethodInvocation methodInvocation = new MethodInvocation(methodQueryExpr, queryAs, SQLArgs);
+            MethodInvocation methodInvocation = new MethodInvocation(methodQueryExpr, queryAs, sqlArgs);
 
             if (scriptAtomQueryParser.isMatchMethodInvocation(methodInvocation)) {
                 return scriptAtomQueryParser.parseAtomMethodQuery(methodInvocation);
             }
 
             if (fullTextAtomQueryParser.isFulltextAtomQuery(methodInvocation)) {
-                return fullTextAtomQueryParser.parseFullTextAtomQuery(methodQueryExpr, queryAs, SQLArgs);
+                return fullTextAtomQueryParser.parseFullTextAtomQuery(methodQueryExpr, queryAs, sqlArgs);
             }
 
             if (termLevelAtomQueryParser.isTermLevelAtomQuery(methodInvocation)) {
-                return termLevelAtomQueryParser.parseTermLevelAtomQuery(methodQueryExpr, queryAs, SQLArgs);
+                return termLevelAtomQueryParser.parseTermLevelAtomQuery(methodQueryExpr, queryAs, sqlArgs);
+            }
+
+            if (joinAtomQueryParser.isJoinAtomQuery(methodInvocation)) {
+                return joinAtomQueryParser.parseJoinAtomQuery(methodQueryExpr, queryAs, sqlArgs);
             }
         }
         else if (sqlConditionExpr instanceof SQLBinaryOpExpr) {
-            return binaryQueryParser.parseBinaryQuery((SQLBinaryOpExpr) sqlConditionExpr, queryAs, SQLArgs);
+            return binaryQueryParser.parseBinaryQuery((SQLBinaryOpExpr) sqlConditionExpr, queryAs, sqlArgs);
         }
         else if (sqlConditionExpr instanceof SQLInListExpr) {
-            return inListQueryParser.parseInListQuery((SQLInListExpr) sqlConditionExpr, queryAs, SQLArgs);
+            return inListQueryParser.parseInListQuery((SQLInListExpr) sqlConditionExpr, queryAs, sqlArgs);
         }
         else if (sqlConditionExpr instanceof SQLBetweenExpr) {
-            return betweenAndQueryParser.parseBetweenAndQuery((SQLBetweenExpr) sqlConditionExpr, queryAs, SQLArgs);
+            return betweenAndQueryParser.parseBetweenAndQuery((SQLBetweenExpr) sqlConditionExpr, queryAs, sqlArgs);
         }
         throw new ElasticSql2DslException(String.format("[syntax error] Can not support query condition type[%s]", sqlConditionExpr.toString()));
     }

+ 1 - 0
src/main/java/org/es/sql/parser/sql/QueryFromParser.java

@@ -9,6 +9,7 @@ import org.es.sql.druid.ElasticSqlSelectQueryBlock;
 import org.es.sql.exception.ElasticSql2DslException;
 import org.es.sql.listener.ParseActionListener;
 
+
 public class QueryFromParser implements QueryParser {
 
     private ParseActionListener parseActionListener;

+ 1 - 0
src/main/java/org/es/sql/parser/sql/QueryGroupByParser.java

@@ -12,6 +12,7 @@ import org.elasticsearch.search.aggregations.bucket.range.RangeAggregationBuilde
 import org.elasticsearch.search.aggregations.bucket.range.date.DateRangeAggregationBuilder;
 import org.elasticsearch.search.aggregations.bucket.terms.Terms;
 import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
+
 import org.es.sql.bean.ElasticDslContext;
 import org.es.sql.bean.ElasticSqlQueryField;
 import org.es.sql.bean.RangeSegment;

+ 2 - 2
src/main/java/org/es/sql/parser/sql/QueryMatchConditionParser.java

@@ -5,7 +5,7 @@ import org.es.sql.bean.ElasticDslContext;
 import org.es.sql.druid.ElasticSqlSelectQueryBlock;
 import org.es.sql.listener.ParseActionListener;
 
-public class QueryMatchConditionParser extends AbstractQueryConditionParser {
+public class QueryMatchConditionParser extends BoolExpressionParser implements QueryParser{
 
     public QueryMatchConditionParser(ParseActionListener parseActionListener) {
         super(parseActionListener);
@@ -18,7 +18,7 @@ public class QueryMatchConditionParser extends AbstractQueryConditionParser {
         if (queryBlock.getMatchQuery() != null) {
             String queryAs = dslContext.getParseResult().getQueryAs();
 
-            BoolQueryBuilder matchQuery = parseQueryConditionExpr(queryBlock.getMatchQuery(), queryAs, dslContext.getSQLArgs());
+            BoolQueryBuilder matchQuery = parseBoolQueryExpr(queryBlock.getMatchQuery(), queryAs, dslContext.getSQLArgs());
 
             dslContext.getParseResult().setMatchCondition(matchQuery);
         }

+ 8 - 2
src/main/java/org/es/sql/parser/sql/QueryOrderConditionParser.java

@@ -20,6 +20,7 @@ import org.es.sql.listener.ParseActionListener;
 import org.es.sql.parser.query.method.MethodInvocation;
 import org.es.sql.parser.sql.sort.*;
 
+
 import java.util.List;
 
 public class QueryOrderConditionParser implements QueryParser {
@@ -33,7 +34,8 @@ public class QueryOrderConditionParser implements QueryParser {
 
         methodSortParsers = ImmutableList.of(
                 new NvlMethodSortParser(),
-                new ScriptMethodSortParser()
+                new ScriptMethodSortParser(),
+                new NestedSortMethodParser()
         );
     }
 
@@ -43,8 +45,12 @@ public class QueryOrderConditionParser implements QueryParser {
         SQLOrderBy sqlOrderBy = queryBlock.getOrderBy();
         if (sqlOrderBy != null && CollectionUtils.isNotEmpty(sqlOrderBy.getItems())) {
             List<SortBuilder> orderByList = Lists.newLinkedList();
+
+            String queryAs = dslContext.getParseResult().getQueryAs();
+            SQLArgs sqlArgs = dslContext.getSQLArgs();
+
             for (SQLSelectOrderByItem orderByItem : sqlOrderBy.getItems()) {
-                SortBuilder orderBy = parseOrderCondition(orderByItem, dslContext.getParseResult().getQueryAs(), dslContext.getSQLArgs());
+                SortBuilder orderBy = parseOrderCondition(orderByItem, queryAs, sqlArgs);
                 if (orderBy != null) {
                     orderByList.add(orderBy);
                 }

+ 1 - 0
src/main/java/org/es/sql/parser/sql/QueryParser.java

@@ -1,5 +1,6 @@
 package org.es.sql.parser.sql;
 
+
 import org.es.sql.bean.ElasticDslContext;
 
 @FunctionalInterface

+ 1 - 4
src/main/java/org/es/sql/parser/sql/QueryRoutingValParser.java

@@ -32,10 +32,7 @@ public class QueryRoutingValParser implements QueryParser {
                 }
                 else if (routingVal instanceof SQLVariantRefExpr) {
                     Object targetVal = ElasticSqlArgConverter.convertSqlArg(routingVal, dslContext.getSQLArgs());
-                    if (!(targetVal instanceof String)) {
-                        throw new ElasticSql2DslException("[syntax error] Index routing val must be a string");
-                    }
-                    routingStringValues.add((String) targetVal);
+                    routingStringValues.add(targetVal.toString());
                 }
                 else {
                     throw new ElasticSql2DslException("[syntax error] Index routing val must be a string");

+ 1 - 0
src/main/java/org/es/sql/parser/sql/QuerySelectFieldListParser.java

@@ -16,6 +16,7 @@ import org.es.sql.exception.ElasticSql2DslException;
 import org.es.sql.helper.ElasticSqlMethodInvokeHelper;
 import org.es.sql.listener.ParseActionListener;
 
+
 import java.util.List;
 
 public class QuerySelectFieldListParser implements QueryParser {

+ 2 - 2
src/main/java/org/es/sql/parser/sql/QueryWhereConditionParser.java

@@ -5,7 +5,7 @@ import org.es.sql.bean.ElasticDslContext;
 import org.es.sql.druid.ElasticSqlSelectQueryBlock;
 import org.es.sql.listener.ParseActionListener;
 
-public class QueryWhereConditionParser extends AbstractQueryConditionParser {
+public class QueryWhereConditionParser extends BoolExpressionParser implements QueryParser{
 
     public QueryWhereConditionParser(ParseActionListener parseActionListener) {
         super(parseActionListener);
@@ -18,7 +18,7 @@ public class QueryWhereConditionParser extends AbstractQueryConditionParser {
         if (queryBlock.getWhere() != null) {
             String queryAs = dslContext.getParseResult().getQueryAs();
 
-            BoolQueryBuilder whereQuery = parseQueryConditionExpr(queryBlock.getWhere(), queryAs, dslContext.getSQLArgs());
+            BoolQueryBuilder whereQuery = parseBoolQueryExpr(queryBlock.getWhere(), queryAs, dslContext.getSQLArgs());
 
             dslContext.getParseResult().setWhereCondition(whereQuery);
         }

+ 2 - 2
src/main/java/org/es/sql/parser/sql/sort/AbstractMethodSortParser.java

@@ -12,7 +12,7 @@ import java.util.Map;
 
 public abstract class AbstractMethodSortParser extends AbstractParameterizedMethodExpression implements MethodSortParser {
 
-    protected abstract SortBuilder parseMethodSortBuilderWithExtraParams(
+    protected abstract SortBuilder parseMethodSortBuilder(
             MethodInvocation invocation, SortOrder order, Map<String, Object> extraParamMap) throws ElasticSql2DslException;
 
     @Override
@@ -40,6 +40,6 @@ public abstract class AbstractMethodSortParser extends AbstractParameterizedMeth
         checkMethodInvocation(invocation);
 
         Map<String, Object> extraParamMap = generateRawTypeParameterMap(invocation);
-        return parseMethodSortBuilderWithExtraParams(invocation, order, extraParamMap);
+        return parseMethodSortBuilder(invocation, order, extraParamMap);
     }
 }

+ 1 - 0
src/main/java/org/es/sql/parser/sql/sort/MethodSortParser.java

@@ -6,6 +6,7 @@ import org.es.sql.exception.ElasticSql2DslException;
 import org.es.sql.parser.query.method.MethodInvocation;
 import org.es.sql.parser.query.method.expr.MethodExpression;
 
+
 public interface MethodSortParser extends MethodExpression {
     SortBuilder parseMethodSortBuilder(MethodInvocation invocation, SortOrder order) throws ElasticSql2DslException;
 }

+ 86 - 0
src/main/java/org/es/sql/parser/sql/sort/NestedSortMethodParser.java

@@ -0,0 +1,86 @@
+package org.es.sql.parser.sql.sort;
+
+import com.alibaba.druid.sql.ast.SQLExpr;
+import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
+import com.google.common.collect.ImmutableList;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.search.sort.*;
+
+import org.es.sql.bean.ElasticSqlQueryField;
+import org.es.sql.bean.SQLArgs;
+import org.es.sql.exception.ElasticSql2DslException;
+import org.es.sql.parser.query.method.MethodInvocation;
+import org.es.sql.parser.sql.BoolExpressionParser;
+import org.es.sql.parser.sql.QueryFieldParser;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * nested sort(nestedField, sortMode, missingValue, defaultValue, filterExpression)
+ * <p>
+ * order by nested_sort($repaymentRecords.principal, 'min', 0, repaymentRecords.status='DONE') asc
+ *
+ * @author chennan
+ */
+public class NestedSortMethodParser extends AbstractMethodSortParser {
+
+    public static final List<String> NESTED_SORT_METHOD = ImmutableList.of("nested_sort", "nestedSort");
+
+    @Override
+    public List<String> defineMethodNames() {
+        return NESTED_SORT_METHOD;
+    }
+
+    @Override
+    public void checkMethodInvocation(MethodInvocation nestedSortMethodInvocation) throws ElasticSql2DslException {
+        if (!isMatchMethodInvocation(nestedSortMethodInvocation)) {
+            throw new ElasticSql2DslException(
+                    String.format("[syntax error] No suck sort method[%s]", nestedSortMethodInvocation.getMethodName()));
+        }
+
+        if (nestedSortMethodInvocation.getParameterCount() > 4) {
+            throw new ElasticSql2DslException(
+                    String.format("[syntax error] There is no %s args method named nested_sort",
+                            nestedSortMethodInvocation.getParameterCount()));
+        }
+
+        SQLExpr sortModArg = nestedSortMethodInvocation.getParameter(1);
+        if (!(sortModArg instanceof SQLCharExpr)) {
+            throw new ElasticSql2DslException("[syntax error] The second arg of nested_sort method should be string");
+        }
+
+        String sortModeText = ((SQLCharExpr) sortModArg).getText();
+        SortMode.fromString(sortModeText);
+    }
+
+    @Override
+    protected SortBuilder parseMethodSortBuilder(MethodInvocation invocation, SortOrder order, Map<String, Object> extraParamMap) throws ElasticSql2DslException {
+        String sortMode = invocation.getParameterAsString(1);
+        Object defaultSortVal = invocation.getParameterAsObject(2);
+
+        boolean hasFilterExpr = invocation.getParameterCount() == 4;
+
+        QueryFieldParser queryFieldParser = new QueryFieldParser();
+        ElasticSqlQueryField sortField = queryFieldParser.parseConditionQueryField(invocation.getParameter(0), invocation.getQueryAs());
+
+        return ParseSortBuilderHelper.parseBasedOnFieldSortBuilder(sortField, new ConditionSortBuilder() {
+            @Override
+            public FieldSortBuilder buildSort(String nestedFieldName) {
+                BoolQueryBuilder filter = null;
+                if (hasFilterExpr) {
+                    SQLExpr filterExpr = invocation.getParameter(3);
+                    BoolExpressionParser boolExpressionParser = new BoolExpressionParser();
+
+                    String queryAs = invocation.getQueryAs();
+                    SQLArgs sqlArgs = invocation.getSqlArgs();
+
+                    filter = boolExpressionParser.parseBoolQueryExpr(filterExpr, queryAs, sqlArgs);
+                }
+                return SortBuilders.fieldSort(nestedFieldName)
+                        .missing(defaultSortVal).sortMode(SortMode.fromString(sortMode))
+                        .setNestedFilter(hasFilterExpr ? filter : null).order(order);
+            }
+        });
+    }
+}

+ 10 - 15
src/main/java/org/es/sql/parser/sql/sort/NvlMethodSortParser.java

@@ -5,14 +5,21 @@ import com.alibaba.druid.sql.ast.expr.*;
 import com.google.common.collect.ImmutableList;
 import org.elasticsearch.search.sort.*;
 import org.es.sql.bean.ElasticSqlQueryField;
-import org.es.sql.enums.SortOption;
 import org.es.sql.exception.ElasticSql2DslException;
 import org.es.sql.parser.query.method.MethodInvocation;
 import org.es.sql.parser.sql.QueryFieldParser;
 
+
 import java.util.List;
 import java.util.Map;
 
+/**
+ * nvl(rootDocField, defaultValue)
+ * <p>
+ * order by nvl(price, 0) asc
+ *
+ * @author chennan
+ */
 public class NvlMethodSortParser extends AbstractMethodSortParser {
 
     public static final List<String> NVL_METHOD = ImmutableList.of("nvl", "is_null", "isnull");
@@ -29,7 +36,7 @@ public class NvlMethodSortParser extends AbstractMethodSortParser {
         }
 
         int methodParameterCount = nvlMethodInvocation.getParameterCount();
-        if (methodParameterCount == 0 || methodParameterCount > 3) {
+        if (methodParameterCount == 0 || methodParameterCount >= 3) {
             throw new ElasticSql2DslException(String.format("[syntax error] There is no %s args method named nvl", methodParameterCount));
         }
 
@@ -43,22 +50,10 @@ public class NvlMethodSortParser extends AbstractMethodSortParser {
         if (!(valueArg instanceof SQLCharExpr) && !(valueArg instanceof SQLIntegerExpr) && !(valueArg instanceof SQLNumberExpr)) {
             throw new ElasticSql2DslException("[syntax error] The second arg of nvl method should be number or string");
         }
-
-        if (methodParameterCount == 3) {
-            SQLExpr sortModArg = nvlMethodInvocation.getParameter(2);
-            if (!(sortModArg instanceof SQLCharExpr)) {
-                throw new ElasticSql2DslException("[syntax error] The third arg of nvl method should be string");
-            }
-            String sortModeText = ((SQLCharExpr) sortModArg).getText();
-            if (!SortOption.AVG.mode().equalsIgnoreCase(sortModeText) && !SortOption.MIN.mode().equalsIgnoreCase(sortModeText)
-                    && !SortOption.MAX.mode().equalsIgnoreCase(sortModeText) && !SortOption.SUM.mode().equalsIgnoreCase(sortModeText)) {
-                throw new ElasticSql2DslException("[syntax error] The third arg of nvl method should be one of the string[min,max,avg,sum]");
-            }
-        }
     }
 
     @Override
-    protected SortBuilder parseMethodSortBuilderWithExtraParams(
+    protected SortBuilder parseMethodSortBuilder(
             MethodInvocation sortMethodInvocation, SortOrder order, Map<String, Object> extraParamMap) throws ElasticSql2DslException {
 
         String queryAs = sortMethodInvocation.getQueryAs();

+ 1 - 0
src/main/java/org/es/sql/parser/sql/sort/ParseSortBuilderHelper.java

@@ -10,6 +10,7 @@ import org.es.sql.bean.ElasticSqlQueryField;
 import org.es.sql.enums.QueryFieldType;
 import org.es.sql.exception.ElasticSql2DslException;
 
+
 public class ParseSortBuilderHelper {
 
     public static boolean isFieldExpr(SQLExpr expr) {

+ 5 - 5
src/main/java/org/es/sql/parser/sql/sort/ScriptMethodSortParser.java

@@ -11,6 +11,7 @@ import org.elasticsearch.search.sort.SortOrder;
 import org.es.sql.exception.ElasticSql2DslException;
 import org.es.sql.parser.query.method.MethodInvocation;
 
+
 import java.util.List;
 import java.util.Map;
 
@@ -44,20 +45,19 @@ public class ScriptMethodSortParser extends AbstractMethodSortParser {
     }
 
     @Override
-    protected SortBuilder parseMethodSortBuilderWithExtraParams(
+    protected SortBuilder parseMethodSortBuilder(
             MethodInvocation scriptSortMethodInvocation, SortOrder order, Map<String, Object> extraParamMap) throws ElasticSql2DslException {
 
-        String script = scriptSortMethodInvocation.getParameterAsString(0);
+        String strScript = scriptSortMethodInvocation.getParameterAsString(0);
         String type = scriptSortMethodInvocation.getParameterAsString(1);
         ScriptSortBuilder.ScriptSortType scriptSortType = ScriptSortBuilder.ScriptSortType.fromString(type);
 
         if (MapUtils.isNotEmpty(extraParamMap)) {
             Map<String, Object> scriptParamMap = generateRawTypeParameterMap(scriptSortMethodInvocation);
-            Script scriptObject = new Script(Script.DEFAULT_SCRIPT_TYPE, Script.DEFAULT_SCRIPT_LANG, script, scriptParamMap);
-
+            Script scriptObject = new Script(Script.DEFAULT_SCRIPT_TYPE, Script.DEFAULT_SCRIPT_LANG, strScript, scriptParamMap);
             return SortBuilders.scriptSort(scriptObject, scriptSortType).order(order);
         }
 
-        return SortBuilders.scriptSort(new Script(script), scriptSortType).order(order);
+        return SortBuilders.scriptSort(new Script(strScript), scriptSortType).order(order);
     }
 }

+ 0 - 18
src/main/java/org/es/sql/utils/ElasticMockClient.java

@@ -10,9 +10,6 @@ import org.elasticsearch.action.delete.DeleteResponse;
 import org.elasticsearch.action.explain.ExplainRequest;
 import org.elasticsearch.action.explain.ExplainRequestBuilder;
 import org.elasticsearch.action.explain.ExplainResponse;
-import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest;
-import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequestBuilder;
-import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse;
 import org.elasticsearch.action.fieldstats.FieldStatsRequest;
 import org.elasticsearch.action.fieldstats.FieldStatsRequestBuilder;
 import org.elasticsearch.action.fieldstats.FieldStatsResponse;
@@ -306,21 +303,6 @@ public class ElasticMockClient implements Client {
     }
 
     @Override
-    public FieldCapabilitiesRequestBuilder prepareFieldCaps() {
-        return null;
-    }
-
-    @Override
-    public ActionFuture<FieldCapabilitiesResponse> fieldCaps(FieldCapabilitiesRequest fieldCapabilitiesRequest) {
-        return null;
-    }
-
-    @Override
-    public void fieldCaps(FieldCapabilitiesRequest fieldCapabilitiesRequest, ActionListener<FieldCapabilitiesResponse> actionListener) {
-
-    }
-
-    @Override
     public Settings settings() {
         return null;
     }

+ 1 - 1
src/main/java/org/es/sql/utils/PersistLogger.java

@@ -4,7 +4,7 @@ package org.es.sql.utils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-public class PersistLogger {
+public class EsPersistLogger {
     public static void warn(Object obj, String message, Throwable e) {
 
         Logger log = LogManager.getLogger(obj.getClass().getName());

BIN
src/main/resources/crack/LicenseVerifier.class.bin


+ 0 - 180
src/main/resources/crack/_xpack_info.js

@@ -1,180 +0,0 @@
-import { createHash } from 'crypto';
-import moment from 'moment';
-import { get, set, includes, forIn } from 'lodash';
-import Poller from './poller';
-import { LICENSE_EXPIRY_SOON_DURATION_IN_DAYS } from './constants';
-
-export default function _xpackInfo(server, pollFrequencyInMillis, clusterSource = 'data') {
-  if(!pollFrequencyInMillis) {
-    const config = server.config();
-    pollFrequencyInMillis = config.get('xpack.xpack_main.xpack_api_polling_frequency_millis');
-  }
-
-  let _cachedResponseFromElasticsearch;
-
-  const _licenseCheckResultsGenerators = {};
-  const _licenseCheckResults = {};
-  let _cachedXPackInfoJSON;
-  let _cachedXPackInfoJSONSignature;
-
-  const poller = new Poller({
-    functionToPoll: _callElasticsearchXPackAPI,
-    successFunction: _handleResponseFromElasticsearch,
-    errorFunction: _handleErrorFromElasticsearch,
-    pollFrequencyInMillis,
-    continuePollingOnError: true
-  });
-
-  const xpackInfoObject = {
-    license: {
-      getUid: function () {
-        return get(_cachedResponseFromElasticsearch, 'license.uid');
-      },
-      isActive: function () {
-          return true;        
-	//return get(_cachedResponseFromElasticsearch, 'license.status') === 'active';
-      },
-      expiresSoon: function () {
-        //const expiryDateMillis = get(_cachedResponseFromElasticsearch, 'license.expiry_date_in_millis');
-        //const expirySoonDate = moment.utc(expiryDateMillis).subtract(moment.duration(LICENSE_EXPIRY_SOON_DURATION_IN_DAYS, 'days'));
-        //return moment.utc().isAfter(expirySoonDate);
-          return false;
-      },
-      getExpiryDateInMillis: function () {
-        return get(_cachedResponseFromElasticsearch, 'license.expiry_date_in_millis');
-      },
-      isOneOf: function (candidateLicenses) {
-        if (!Array.isArray(candidateLicenses)) {
-          candidateLicenses = [ candidateLicenses ];
-        }
-        return includes(candidateLicenses, get(_cachedResponseFromElasticsearch, 'license.mode'));
-      },
-      getType: function () {
-        return get(_cachedResponseFromElasticsearch, 'license.type');
-      }
-    },
-    feature: function (feature) {
-      return {
-        isAvailable: function () {
-          //return get(_cachedResponseFromElasticsearch, 'features.' + feature + '.available');
-            return true;
-        },
-        isEnabled: function () {
-          //return get(_cachedResponseFromElasticsearch, 'features.' + feature + '.enabled');
-            return true;
-        },
-        registerLicenseCheckResultsGenerator: function (generator) {
-          _licenseCheckResultsGenerators[feature] = generator;
-          _updateXPackInfoJSON();
-        },
-        getLicenseCheckResults: function () {
-          return _licenseCheckResults[feature];
-        }
-      };
-    },
-    isAvailable: function () {
-      //return !!_cachedResponseFromElasticsearch && !!get(_cachedResponseFromElasticsearch, 'license');
-        return true;
-    },
-    getSignature: function () {
-      return _cachedXPackInfoJSONSignature;
-    },
-    refreshNow: function () {
-      const self = this;
-      return _callElasticsearchXPackAPI()
-      .then(_handleResponseFromElasticsearch)
-      .catch(_handleErrorFromElasticsearch)
-      .then(() => self);
-    },
-    stopPolling: function () {
-      // This method exists primarily for unit testing
-      poller.stop();
-    },
-    toJSON: function () {
-      return _cachedXPackInfoJSON;
-    }
-  };
-
-  const cluster = server.plugins.elasticsearch.getCluster(clusterSource);
-
-  function _callElasticsearchXPackAPI() {
-    server.log([ 'license', 'debug', 'xpack' ], 'Calling Elasticsearch _xpack API');
-    return cluster.callWithInternalUser('transport.request', {
-      method: 'GET',
-      path: '/_xpack'
-    });
-  };
-
-  function _updateXPackInfoJSON() {
-    const json = {};
-
-    // Set response elements common to all features
-    set(json, 'license.type', xpackInfoObject.license.getType());
-    set(json, 'license.isActive', xpackInfoObject.license.isActive());
-    set(json, 'license.expiryDateInMillis', xpackInfoObject.license.getExpiryDateInMillis());
-
-    // Set response elements specific to each feature. To do this,
-    // call the license check results generator for each feature, passing them
-    // the xpack info object
-    forIn(_licenseCheckResultsGenerators, (generator, feature) => {
-      _licenseCheckResults[feature] = generator(xpackInfoObject); // return value expected to be a dictionary object
-    });
-    set(json, 'features', _licenseCheckResults);
-
-    _cachedXPackInfoJSON = json;
-    _cachedXPackInfoJSONSignature = createHash('md5')
-    .update(JSON.stringify(json))
-    .digest('hex');
-  }
-
-  function _hasLicenseInfoFromElasticsearchChanged(response) {
-    const cachedResponse = _cachedResponseFromElasticsearch;
-    return (get(response, 'license.mode') !== get(cachedResponse, 'license.mode')
-      || get(response, 'license.status') !== get(cachedResponse, 'license.status')
-      || get(response, 'license.expiry_date_in_millis') !== get(cachedResponse, 'license.expiry_date_in_millis'));
-  }
-
-  function _getLicenseInfoForLog(response) {
-    const mode = get(response, 'license.mode');
-    const status = get(response, 'license.status');
-    const expiryDateInMillis = get(response, 'license.expiry_date_in_millis');
-
-    return [
-      'mode: ' + mode,
-      'status: ' + status,
-      'expiry date: ' + moment(expiryDateInMillis, 'x').format()
-    ].join(' | ');
-  }
-
-  function _handleResponseFromElasticsearch(response) {
-
-    if (_hasLicenseInfoFromElasticsearchChanged(response)) {
-      let changed = '';
-      if (_cachedResponseFromElasticsearch) {
-        changed = 'changed ';
-      }
-
-      const licenseInfo = _getLicenseInfoForLog(response);
-      const logMessage = `Imported ${changed}license information from Elasticsearch for [${clusterSource}] cluster: ${licenseInfo}`;
-      server.log([ 'license', 'info', 'xpack'  ], logMessage);
-    }
-
-    _cachedResponseFromElasticsearch = response;
-    _updateXPackInfoJSON();
-  }
-
-  function _handleErrorFromElasticsearch(error) {
-    server.log([ 'license', 'warning', 'xpack' ], 'License information could not be obtained from Elasticsearch. ' + error);
-    _cachedResponseFromElasticsearch = null;
-    _updateXPackInfoJSON();
-
-    // allow tests to shutdown
-    error.info = xpackInfoObject;
-
-    throw error;
-  }
-
-  // Start polling for changes
-  return poller.start()
-  .then(() => xpackInfoObject);
-}

File diff suppressed because it is too large
+ 0 - 1
src/main/resources/crack/license.json


+ 0 - 10
src/main/resources/crack/read-me.txt

@@ -1,10 +0,0 @@
-【使用声明】
-1. 该破解文件仅供学习使用,禁止用于商业用途
-2. 该破解文件只破解了elasticsearch和kibana
-
-【安装步骤】
-1. 执行./bin/elasticsearch-plugin install file:///home/superx/share/x-pack-5.4.1-crack.zip,安装x-pack
-2. 到官网注册basic license并下载保存为license.json
-3. 修改license.json内容,其中"type":"platinum","expiry_date_in_millis":1806237751991,type改为platinum表示可以使用所有功能 ; expiry_date_in_millis  我这里改了10年
-4. 执行put license:curl -XPUT -u elastic:changeme 'http://192.168.100.50:9200/_xpack/license?acknowledge=true' -d @license.json
-5. 验证curl -XGET -u elastic:changeme 'http://192.168.100.50:9200/_license'

+ 75 - 12
src/test/java/org/es/test/ProductIndexQueryTest.java

@@ -1,35 +1,96 @@
 package org.es.test;
 
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.gson.Gson;
+import org.elasticsearch.client.Client;
+import org.es.jdbc.es.ElasticClientProvider;
+import org.es.jdbc.es.ElasticClientProxyProviderImpl;
+import org.es.mapping.mapper.MappingBuilder;
 import org.es.spring.ElasticSqlMapClientTemplate;
-import org.es.test.bean.Product;
+import org.es.test.jdbc.BaseJdbcTest;
+import org.es.test.jdbc.bean.Buyer;
+import org.es.test.jdbc.bean.Product;
+import org.es.test.jdbc.bean.Provider;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.beans.factory.BeanFactory;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 
+import java.math.BigDecimal;
 import java.sql.SQLException;
 import java.util.List;
 import java.util.Map;
 
-public class ProductIndexQueryTest {
+public class ProductIndexQueryTest extends BaseJdbcTest {
 
     private ElasticSqlMapClientTemplate sqlMapClientTemplate;
 
+    private static MappingBuilder mappingBuilder = new MappingBuilder();
+
+    private static Client client;
+
+    private static String setting = "{\n" +
+            "    \"number_of_shards\": \"1\",\n" +
+            "    \"number_of_replicas\": \"0\"\n" +
+            "}";
+
+    static {
+        ElasticClientProvider clientProxyProvider = new ElasticClientProxyProviderImpl();
+        client = clientProxyProvider.createElasticClientFromUrl(url);
+    }
+
     @Before
-    public void initSpringContext() {
+    public void initForQueryTest() throws Exception {
         BeanFactory factory = new ClassPathXmlApplicationContext("application-context.xml");
         sqlMapClientTemplate = factory.getBean(ElasticSqlMapClientTemplate.class);
+
+        if (client.admin().indices().prepareExists("index").execute().actionGet().isExists()) {
+            client.admin().indices().prepareDelete("index").execute().actionGet();
+        }
+
+        client.admin().indices()
+                .prepareCreate("index")
+                .addMapping("product", mappingBuilder.buildMapping(Product.class).get("product").string())
+                .setSettings(setting).execute().actionGet();
+
+        Product product = new Product();
+        product.setAdvicePrice(BigDecimal.valueOf(1000));
+        product.setMinPrice(BigDecimal.valueOf(500));
+        product.setProductCode("IP_6S");
+        product.setProductName("iphone 6s");
+
+        Provider provider = new Provider();
+        provider.setProviderLevel(1);
+        provider.setProviderName("FSK");
+
+        product.setProvider(provider);
+
+        Buyer china = new Buyer();
+        china.setBuyerName("china");
+        china.setProductPrice(2000);
+
+        Buyer japan = new Buyer();
+        japan.setBuyerName("japan");
+        japan.setProductPrice(1800);
+
+        product.setBuyers(Lists.newArrayList(china, japan));
+
+        client.prepareIndex("index", "product", "1").setSource(new Gson().toJson(product, Product.class)).execute().actionGet();
+
+        client.admin().indices().prepareRefresh("index").execute().actionGet();
     }
 
-    public List<Product> getProductByCodeAndMatchWord(String matchWord, String productCode) throws SQLException {
+    @Test
+    public void test_buildProductMapping() throws Exception {
+        System.out.println(mappingBuilder.buildMappingAsString(Product.class));
+    }
+
+    public List<Product> getProductByCode(String productCode) throws SQLException {
         Map<String, Object> paramMap = Maps.newHashMap();
         paramMap.put("productCode", productCode);
-        paramMap.put("advicePrice", 1000);
-        paramMap.put("routingVal", "A");
-        paramMap.put("matchWord", matchWord);
-        paramMap.put("prefixWord", matchWord);
-        return sqlMapClientTemplate.queryForList("PRODUCT.getProductByCodeAndMatchWord", paramMap, Product.class);
+        paramMap.put("advicePrice", 999);
+        return sqlMapClientTemplate.queryForList("PRODUCT.getProductByCode", paramMap, Product.class);
 
     }
 
@@ -40,16 +101,18 @@ public class ProductIndexQueryTest {
 
     @Test
     public void testProductQuery() throws Exception {
-        List<Product> productList = getProductByCodeAndMatchWord("iphone 6s", "IP_6S");
+        Gson gson = new Gson();
+
+        List<Product> productList = getProductByCode("IP_6S");
         for (Product product : productList) {
-            System.out.println(product.getProductName());
+            System.out.println(gson.toJson(product));
         }
 
         System.out.println("============================>");
 
         productList = getAllProduct();
         for (Product product : productList) {
-            System.out.println(product.getProductName());
+            System.out.println(gson.toJson(product));
         }
     }
 }

+ 0 - 23
src/test/java/org/es/test/bean/Buyer.java

@@ -1,23 +0,0 @@
-package org.es.test.bean;
-
-public class Buyer {
-    private String buyerName;
-
-    private Integer productPrice;
-
-    public String getBuyerName() {
-        return buyerName;
-    }
-
-    public void setBuyerName(String buyerName) {
-        this.buyerName = buyerName;
-    }
-
-    public Integer getProductPrice() {
-        return productPrice;
-    }
-
-    public void setProductPrice(Integer productPrice) {
-        this.productPrice = productPrice;
-    }
-}

+ 0 - 66
src/test/java/org/es/test/bean/Product.java

@@ -1,66 +0,0 @@
-package org.es.test.bean;
-
-import java.math.BigDecimal;
-import java.util.List;
-
-public class Product {
-    private String productName;
-
-    private String productCode;
-
-    private BigDecimal minPrice;
-
-    private BigDecimal advicePrice;
-
-    private Provider provider;
-
-    private List<Buyer> buyers;
-
-    public String getProductName() {
-        return productName;
-    }
-
-    public void setProductName(String productName) {
-        this.productName = productName;
-    }
-
-    public String getProductCode() {
-        return productCode;
-    }
-
-    public void setProductCode(String productCode) {
-        this.productCode = productCode;
-    }
-
-    public BigDecimal getMinPrice() {
-        return minPrice;
-    }
-
-    public void setMinPrice(BigDecimal minPrice) {
-        this.minPrice = minPrice;
-    }
-
-    public BigDecimal getAdvicePrice() {
-        return advicePrice;
-    }
-
-    public void setAdvicePrice(BigDecimal advicePrice) {
-        this.advicePrice = advicePrice;
-    }
-
-    public Provider getProvider() {
-        return provider;
-    }
-
-    public void setProvider(Provider provider) {
-        this.provider = provider;
-    }
-
-    public List<Buyer> getBuyers() {
-        return buyers;
-    }
-
-    public void setBuyers(List<Buyer> buyers) {
-        this.buyers = buyers;
-    }
-}

+ 0 - 23
src/test/java/org/es/test/bean/Provider.java

@@ -1,23 +0,0 @@
-package org.es.test.bean;
-
-public class Provider {
-    private String providerName;
-
-    private Integer providerLevel;
-
-    public String getProviderName() {
-        return providerName;
-    }
-
-    public void setProviderName(String providerName) {
-        this.providerName = providerName;
-    }
-
-    public Integer getProviderLevel() {
-        return providerLevel;
-    }
-
-    public void setProviderLevel(Integer providerLevel) {
-        this.providerLevel = providerLevel;
-    }
-}

+ 0 - 0
src/test/java/org/es/test/jdbc/BaseJdbcTest.java


Some files were not shown because too many files changed in this diff