├── .gitignore ├── .travis.yml ├── CONTRIBUTORS.md ├── LICENSE.md ├── README.md ├── pho.png ├── pom.xml └── src ├── main ├── config │ └── log4j.properties ├── java │ └── com │ │ └── eharmony │ │ └── pho │ │ ├── api │ │ ├── DataStoreApi.java │ │ └── DataStoreException.java │ │ ├── hbase │ │ ├── PhoenixHBaseDataStoreApiImpl.java │ │ ├── mapper │ │ │ └── PhoenixProjectedResultMapper.java │ │ ├── query │ │ │ └── PhoenixHBaseQueryExecutor.java │ │ ├── translator │ │ │ ├── MorphiaEntityResolver.java │ │ │ ├── PhoenixHBaseAggregate.java │ │ │ ├── PhoenixHBaseClauses.java │ │ │ ├── PhoenixHBaseOperator.java │ │ │ └── PhoenixHBaseQueryTranslator.java │ │ └── util │ │ │ ├── PhoenixConnectionManager.java │ │ │ └── PhoenixDateFormatUtil.java │ │ ├── mapper │ │ ├── EntityPropertiesMappingContext.java │ │ ├── EntityPropertiesResolver.java │ │ ├── EntityPropertyBinding.java │ │ ├── EntityPropertyValueBinding.java │ │ └── ProjectedResultMapper.java │ │ ├── query │ │ ├── QueryOperationType.java │ │ ├── QuerySelect.java │ │ ├── QuerySelectImpl.java │ │ ├── QueryUpdate.java │ │ ├── QueryUpdateImpl.java │ │ ├── builder │ │ │ ├── QueryBuilder.java │ │ │ └── QueryUpdateBuilder.java │ │ └── criterion │ │ │ ├── Aggregate.java │ │ │ ├── Criterion.java │ │ │ ├── GroupCriterion.java │ │ │ ├── GroupRestrictions.java │ │ │ ├── Operator.java │ │ │ ├── Ordering.java │ │ │ ├── Orderings.java │ │ │ ├── Projections.java │ │ │ ├── Restrictions.java │ │ │ ├── Symbolic.java │ │ │ ├── SymbolicLookup.java │ │ │ ├── WithAggregateFunction.java │ │ │ ├── WithOperator.java │ │ │ ├── WithProperty.java │ │ │ ├── expression │ │ │ ├── EqualityExpression.java │ │ │ ├── Expression.java │ │ │ ├── NativeExpression.java │ │ │ ├── RangeExpression.java │ │ │ ├── SetExpression.java │ │ │ └── UnaryExpression.java │ │ │ ├── junction │ │ │ ├── Conjunction.java │ │ │ ├── Disjunction.java │ │ │ └── Junction.java │ │ │ └── projection │ │ │ ├── AggregateProjection.java │ │ │ ├── AvgProjection.java │ │ │ ├── CountProjection.java │ │ │ ├── GroupProjection.java │ │ │ ├── MaxProjection.java │ │ │ ├── MinProjection.java │ │ │ └── Projection.java │ │ └── translator │ │ ├── AbstractQueryTranslator.java │ │ ├── EntityResolver.java │ │ ├── PropertyResolver.java │ │ ├── QueryTranslator.java │ │ └── SimpleEntityResolver.java └── resources │ ├── META-INF │ └── spring │ │ └── application-context.xml │ └── hbase.properties └── test └── java └── com └── eharmony └── pho └── hbase ├── mapper ├── EntityPropertiesResolverTest.java └── PhoenixProjectedResultMapperTest.java ├── model ├── EmbededEntityExample.java ├── NestedEntity.java └── TranslationTestClass.java └── translator └── PhoenixHBaseQueryTranslatorTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | # ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | # 10 | # # Packages # 11 | # ############ 12 | # # it's better to unpack these files and commit the raw source 13 | # # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | # 23 | # Logs and databases # 24 | *.log 25 | *.sql 26 | *.sqlite 27 | # 28 | # OS generated files # 29 | .DS_Store* 30 | ehthumbs.db 31 | Icon? 32 | Thumbs.db 33 | 34 | # java generated stuff 35 | .classpath 36 | .groovy/ 37 | .project 38 | .settings/ 39 | .idea/ 40 | *.iml 41 | logs/ 42 | target/ 43 | /bin/ 44 | .springBeans 45 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | 5 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Vijay Vangapandu 2 | Adam Richeimer -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2016 eHarmony, Inc 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | PHO (Phoenix-HBase ORM) 5 | ======================= 6 | 7 | [![Build Status](https://travis-ci.org/eHarmony/pho.svg?branch=master)](https://travis-ci.org/eHarmony/pho) 8 | 9 | PHO is a library for building and executing queries on HBase using Apache Phoenix. 10 | It provides ORM-like mappings and DSL-style query building. 11 | 12 | Its Interfaces and generic annotations allows the ability to switch the data store api in the future without changing the queries. 13 | Currently, it only supports Hbase integration using Apache Phoenix. However, it's very easy to plugin other implementations if need be. 14 | 15 | # Entity Class 16 | Suppose we have the following TestClass we want to query against in our data store: 17 | 18 | ```java 19 | //class must be annotated with Entity 20 | import com.google.code.morphia.annotations.Embedded; 21 | import com.google.code.morphia.annotations.Entity; 22 | @Entity(value="user_matches") 23 | public class MatchDataFeedItemDto { 24 | @Embedded 25 | private MatchCommunicationElement communication; 26 | @Embedded 27 | private MatchElement match; 28 | @Embedded 29 | private MatchProfileElement matchedUser; 30 | } 31 | 32 | public class MatchElement { 33 | // row key 34 | @Property(value = "UID") 35 | private long userId; 36 | @Property(value = "MID") 37 | private long matchId; 38 | @Property(value = "DLVRYDT") 39 | private Date deliveredDate; 40 | @Property(value = "STATUS") 41 | private int status; 42 | } 43 | ``` 44 | 45 | # Query Building 46 | 47 | Query building can be done in DSL style. More advanced query building is under development but, for now, we will use a combination of the QueryBuilder and the static, Hibernate-style Restrictions methods to construct our queries. 48 | 49 | ### Simple Queries 50 | 51 | Construct a query to find all user matches which are delivered in past 2 days and not in closed state 52 | 53 | ```java 54 | import com.eharmony.datastore.api.DataStoreApi; 55 | import com.eharmony.datastore.model.MatchDataFeedItemDto; 56 | import com.eharmony.datastore.query.QuerySelect; 57 | import com.eharmony.datastore.query.builder.QueryBuilder; 58 | import com.eharmony.datastore.query.criterion.Restrictions; 59 | @Repository 60 | public class MatchStoreQueryRepositoryImpl implements MatchStoreQueryRepository { 61 | final QuerySelect query = QueryBuilder 62 | .builderFor(MatchDataFeedItemDto.class) 63 | .select() 64 | .add(Restrictions.eq("userId", userId)) 65 | .add(Restrictions.eq("status", 2)) 66 | .add(Restrictions.gt("deliveredDate", timeThreshold.getTime())).build(); 67 | Iterable feedItems = dataStoreApi.findAll(query); 68 | ``` 69 | 70 | ### Compound Queries 71 | 72 | Construct a more complex query where not only do we want to find items with a date older than a day ago, but also find the matches in different status and order the results by deliveryDate and limit the results size to 10: 73 | 74 | ```java 75 | //provided 76 | List statusFilters = request.getMatchStatusFilters(); 77 | String sortBy = request.getSortBy() 78 | Disjunction disjunction = new Disjunction(); 79 | for (Integer statusFilter : statusFilters) { 80 | disjunction.add(Restrictions.eq("status", statusFilter)); 81 | } 82 | final QuerySelect query = QueryBuilder 83 | .builderFor(MatchDataFeedItemDto.class) 84 | .select() 85 | .add(Restrictions.eq("userId", userId)) 86 | .add(Restrictions.gt("deliveredDate", timeThreshold.getTime())); 87 | .add(disjunction); 88 | .addOrder(new Ordering(sortBy, Order.DESCENDING)).build(); 89 | Iterable feedItems = dataStoreApi.findAll(query); 90 | ``` 91 | 92 | *Note:* by default, expressions will be ANDed together when added separately. 93 | 94 | ### Query Interface 95 | The following query components are supported: 96 | 97 | ```java 98 | // equals 99 | EqualityExpression eq(String propertyName, Object value); 100 | 101 | // does not equal (not equals); 102 | EqualityExpression ne(String propertyName, Object value); 103 | 104 | // less than 105 | EqualityExpression lt(String propertyName, Object value); 106 | 107 | // less than or equal 108 | EqualityExpression lte(String propertyName, Object value); 109 | 110 | // greater than 111 | EqualityExpression gt(String propertyName, Object value); 112 | 113 | // greater than or equal 114 | EqualityExpression gte(String propertyName, Object value); 115 | 116 | // between from and to (inclusive) 117 | RangeExpression between(String propertyName, Object from, Object to); 118 | 119 | // and - takes a variable list of expressions as arguments 120 | Conjunction and(Criterion... criteria); 121 | 122 | // or - takes a variable list of expressions as arguments 123 | Disjunction or(Criterion... criteria); 124 | 125 | ``` 126 | 127 | ### Resolving Entity and Property Names 128 | 129 | Always use the property names of your Java objects in your queries. 130 | If these names differ from those used in your datastore you will use annotations to provide the mappings. 131 | Entity Resolvers are configured to map the entity classes to table/collection names. 132 | Property Resolvers are configured to map the names of your object variables to column/field names. 133 | 134 | The following annotations are currently supported for the indicated data store type. 135 | Custom EntityResolvers and PropertyResolvers are easy to configure and create. 136 | 137 | see [Morphia Annotations](https://code.google.com/p/morphia/wiki/AllAnnotations) for entity class annotation mappings 138 | 139 | 140 | ## Query Execution 141 | 142 | The QueryExecutor interface supports the following operations: 143 | 144 | ```java 145 | // return an iterable of type R from the query against type T (R and T will often be the same type) 146 | Iterable findAll(QuerySelect query); 147 | 148 | // return aone R from the query against type T 149 | R findOne(QuerySelect query) 150 | 151 | // save the entity of type T to the data store 152 | T save(T entity); 153 | 154 | // save all of the entities in the provided iterable to data store 155 | Iterable save(Iterable entities); 156 | 157 | // saves all the entities in batches with configured batch size 158 | int[] saveBatch(Iterable entities); 159 | ``` 160 | 161 | ## Configuration 162 | 163 | Here are some example Spring configuration files for Hbase using apache phoenix. 164 | 165 | ### HBase 166 | configuration proeprties 167 | hbase.connection.url=jdbc:phoenix:zkhost:2181 168 | 169 | ```xml 170 | 171 | 172 | com.eharmony.datastore.model.MatchDataFeedItemDto 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | ``` 201 | 202 | -------------------------------------------------------------------------------- /pho.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eHarmony/pho/40b50640a9c80f7e6342bb8046eecb402ad44165/pho.png -------------------------------------------------------------------------------- /src/main/config/log4j.properties: -------------------------------------------------------------------------------- 1 | # Direct log messages to stdout 2 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 3 | log4j.appender.stdout.Target=System.out 4 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %40.40c:%4L - %m%n 6 | 7 | # Root logger option 8 | log4j.rootLogger=INFO, stdout 9 | 10 | #log4j.logger.org.springframework.data.hadoop=DEBUG 11 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/api/DataStoreApi.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.api; 2 | 3 | import java.util.List; 4 | 5 | import com.eharmony.pho.query.QuerySelect; 6 | /** 7 | * Provides methods to operate with datastore. 8 | * @author vvangapandu 9 | * 10 | */ 11 | public interface DataStoreApi { 12 | 13 | /** 14 | * Saves given item to store. 15 | * 16 | * @param 17 | * class type 18 | * @param entity 19 | * T 20 | * @return T 21 | */ 22 | 23 | T save(T entity); 24 | 25 | /** 26 | * Saves all items in iterable, saves will be issues item by item 27 | * 28 | * @param 29 | * class type 30 | * @param entities 31 | * Iterable 32 | * @return Iterable 33 | */ 34 | Iterable save(Iterable entities); 35 | 36 | /** 37 | * Saves all items in iterable in batches, batch size is same as number of items in the Iterable. 38 | * 39 | * @param 40 | * entity class 41 | * @param entities 42 | * Iterable 43 | * @return Iterable 44 | */ 45 | int[] saveBatch(Iterable entities); 46 | 47 | /** 48 | * Find records that satisfy the provided query. 49 | * 50 | * @param 51 | * class type 52 | * @param return param type 53 | * @param query 54 | * Query 55 | * @return an {@link Iterable} of entity type T 56 | * 57 | * @throws DataStoreException 58 | * if an error occurs accessing the underlying data store 59 | */ 60 | Iterable findAll(QuerySelect query); 61 | 62 | /** 63 | * Find one record that satisfies the provided query. 64 | * 65 | * @param 66 | * class type 67 | * @param return param type 68 | * @param query 69 | * Query 70 | * @return an object of entity type T 71 | * 72 | * @throws DataStoreException 73 | * if an error occurs accessing the underlying data store 74 | */ 75 | R findOne(QuerySelect query); 76 | 77 | /** 78 | * Updates an existing entity, but only for the selected fields. 79 | * @param 80 | * class type 81 | * @param entity existing entity whose fields need to be updated. 82 | * @param selectedFields list of property names that need to be udpated. 83 | * @return updated entity. 84 | */ 85 | T save(T entity, List selectedFields); 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/api/DataStoreException.java: -------------------------------------------------------------------------------- 1 | 2 | package com.eharmony.pho.api; 3 | 4 | /** 5 | * An exception that indicates a problem loading from or storing to a data 6 | * store. It presents a uniform abstraction across things like 7 | * {@link java.sql.SQLException}. 8 | */ 9 | public class DataStoreException extends RuntimeException { 10 | 11 | private static final long serialVersionUID = -1363433871972493184L; 12 | 13 | public DataStoreException() { 14 | super(); 15 | } 16 | 17 | public DataStoreException(String message) { 18 | super(message); 19 | } 20 | 21 | public DataStoreException(Throwable cause) { 22 | super(cause); 23 | } 24 | 25 | public DataStoreException(String message, Throwable cause) { 26 | super(message, cause); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/hbase/PhoenixHBaseDataStoreApiImpl.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.hbase; 2 | 3 | import java.sql.Connection; 4 | import java.util.List; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import com.eharmony.pho.api.DataStoreApi; 10 | import com.eharmony.pho.hbase.query.PhoenixHBaseQueryExecutor; 11 | import com.eharmony.pho.hbase.util.PhoenixConnectionManager; 12 | import com.eharmony.pho.query.QuerySelect; 13 | import com.eharmony.pho.query.builder.QueryBuilder; 14 | import com.eharmony.pho.query.builder.QueryUpdateBuilder; 15 | import com.google.common.base.Preconditions; 16 | 17 | /** 18 | * Datastore api implementation for HBase store. Using apache phoenix (http://phoenix.apache.org/) as sql layer to hbase 19 | * 20 | * @author vvangapandu 21 | * 22 | */ 23 | public class PhoenixHBaseDataStoreApiImpl implements DataStoreApi { 24 | 25 | private final PhoenixHBaseQueryExecutor queryExecutor; 26 | private final String connectionUrl; 27 | private static final Logger logger = LoggerFactory.getLogger(PhoenixHBaseDataStoreApiImpl.class); 28 | 29 | public PhoenixHBaseDataStoreApiImpl(final String connectionUrl, final PhoenixHBaseQueryExecutor queryExecutor) 30 | throws Exception { 31 | this(connectionUrl, queryExecutor, false); 32 | } 33 | 34 | public PhoenixHBaseDataStoreApiImpl(final String connectionUrl, final PhoenixHBaseQueryExecutor queryExecutor, final boolean testConnection) 35 | throws Exception { 36 | this.connectionUrl = connectionUrl; 37 | this.queryExecutor = Preconditions.checkNotNull(queryExecutor); 38 | 39 | // Below code will ensure that connection string is valid, if not will stop the context loading 40 | if(testConnection) { 41 | Connection conn = PhoenixConnectionManager.getConnection(connectionUrl); 42 | if (conn == null) { 43 | throw new IllegalStateException("unable to create phoenix connection with given url :" + connectionUrl); 44 | } else { 45 | closeConnectionSafe(conn); 46 | } 47 | } 48 | } 49 | 50 | @Override 51 | public T save(T entity) { 52 | Connection conn = null; 53 | try { 54 | conn = PhoenixConnectionManager.getConnection(connectionUrl); 55 | T returnEntity = queryExecutor.save(entity, conn); 56 | conn.commit(); 57 | return returnEntity; 58 | } catch (Exception ex) { 59 | throw new RuntimeException(ex); 60 | } finally { 61 | closeConnectionSafe(conn); 62 | } 63 | } 64 | 65 | private void closeConnectionSafe(Connection conn) { 66 | try { 67 | if (conn != null) { 68 | conn.close(); 69 | } 70 | } catch (Exception ex) { 71 | logger.warn("Exception while closing the connection...", ex.getMessage()); 72 | } 73 | } 74 | 75 | @Override 76 | public Iterable save(Iterable entities) { 77 | Connection conn = null; 78 | try { 79 | conn = PhoenixConnectionManager.getConnection(connectionUrl); 80 | Iterable results = queryExecutor.save(entities, conn); 81 | conn.commit(); 82 | return results; 83 | } catch (Exception ex) { 84 | throw new RuntimeException(ex); 85 | } finally { 86 | closeConnectionSafe(conn); 87 | } 88 | } 89 | 90 | @Override 91 | public int[] saveBatch(Iterable entities) { 92 | Connection conn = null; 93 | try { 94 | conn = PhoenixConnectionManager.getConnection(connectionUrl); 95 | int[] results = queryExecutor.saveBatch(entities, conn); 96 | conn.commit(); 97 | return results; 98 | } catch (Exception ex) { 99 | throw new RuntimeException(ex); 100 | } finally { 101 | closeConnectionSafe(conn); 102 | } 103 | } 104 | 105 | @Override 106 | public Iterable findAll(QuerySelect query) { 107 | Connection conn = null; 108 | try { 109 | conn = PhoenixConnectionManager.getConnection(connectionUrl); 110 | return queryExecutor.find(query, conn); 111 | } catch (Exception ex) { 112 | throw new RuntimeException(ex); 113 | } finally { 114 | closeConnectionSafe(conn); 115 | } 116 | } 117 | 118 | @Override 119 | public R findOne(QuerySelect query) { 120 | Connection conn = null; 121 | try { 122 | conn = PhoenixConnectionManager.getConnection(connectionUrl); 123 | return queryExecutor.findOne(query, conn); 124 | } catch (Exception ex) { 125 | throw new RuntimeException(ex); 126 | } finally { 127 | closeConnectionSafe(conn); 128 | } 129 | } 130 | 131 | public Iterable findAllEntities(String key, Class clz, String[] projection) throws Exception { 132 | Connection conn = null; 133 | try { 134 | conn = PhoenixConnectionManager.getConnection(connectionUrl); 135 | QueryBuilder builder = new QueryBuilder(clz, clz); 136 | builder.setReturnFields(projection); 137 | QuerySelect query = builder.build(); 138 | return queryExecutor.find(query, conn); 139 | } catch (Exception ex) { 140 | throw new RuntimeException(ex); 141 | } finally { 142 | closeConnectionSafe(conn); 143 | } 144 | } 145 | 146 | @Override 147 | public T save(T entity, List selectedFields) { 148 | Connection conn = null; 149 | try { 150 | conn = PhoenixConnectionManager.getConnection(connectionUrl); 151 | QueryUpdateBuilder updateBuilder = QueryUpdateBuilder.builderFor(entity).update(selectedFields); 152 | T returnEntity = (T) queryExecutor.save(updateBuilder.build(), conn); 153 | conn.commit(); 154 | return returnEntity; 155 | } catch (Exception ex) { 156 | throw new RuntimeException(ex); 157 | } finally { 158 | closeConnectionSafe(conn); 159 | } 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/hbase/mapper/PhoenixProjectedResultMapper.java: -------------------------------------------------------------------------------- 1 | 2 | package com.eharmony.pho.hbase.mapper; 3 | 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.sql.ResultSet; 6 | import java.sql.ResultSetMetaData; 7 | import java.sql.SQLException; 8 | import java.util.ArrayList; 9 | import java.util.HashSet; 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | import org.apache.commons.beanutils.BeanUtils; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import com.eharmony.pho.mapper.EntityPropertiesResolver; 18 | import com.eharmony.pho.mapper.EntityPropertyBinding; 19 | import com.eharmony.pho.mapper.ProjectedResultMapper; 20 | import com.eharmony.pho.query.QuerySelect; 21 | 22 | /** 23 | * Maps the results from hbase to entity object based on entity annotations. 24 | * 25 | * @author vvangapandu 26 | * 27 | */ 28 | public class PhoenixProjectedResultMapper { 29 | 30 | private final ProjectedResultMapper mapper; 31 | private final EntityPropertiesResolver entityPropertiesResolver; 32 | 33 | private static final Logger log = LoggerFactory.getLogger(PhoenixProjectedResultMapper.class); 34 | 35 | public PhoenixProjectedResultMapper(final EntityPropertiesResolver entityPropertiesResolver) { 36 | this.mapper = new ProjectedResultMapper(); 37 | this.entityPropertiesResolver = entityPropertiesResolver; 38 | } 39 | 40 | protected String[] returnFields(QuerySelect query) { 41 | List list = query.getReturnFields(); 42 | String[] array = new String[list.size()]; 43 | return list.toArray(array); 44 | } 45 | 46 | public R mapResult(Object o, QuerySelect query, String[] returnFields) { 47 | return query.getEntityClass().equals(query.getReturnType()) 48 | ? query.getReturnType().cast(o) 49 | : mapper.mapTo(query.getReturnType(), o, returnFields); 50 | } 51 | 52 | public R mapResult(Object o, QuerySelect query) { 53 | return mapResult(o, query, returnFields(query)); 54 | } 55 | 56 | public List mapResults(List objects, final QuerySelect query) { 57 | final String[] returnFields = returnFields(query); 58 | List returnList = new ArrayList(); 59 | for(Object o: objects) { 60 | returnList.add(mapResult(o, query, returnFields)); 61 | } 62 | return returnList; 63 | } 64 | 65 | public Iterable mapResults(ResultSet resultSet, final QuerySelect query) throws SQLException, 66 | InstantiationException, IllegalAccessException, InvocationTargetException { 67 | return mapResults(resultSet, query.getReturnType()); 68 | } 69 | 70 | @SuppressWarnings("unchecked") 71 | public Iterable mapResults(ResultSet resultSet, final Class clz) throws SQLException, 72 | InstantiationException, IllegalAccessException, InvocationTargetException { 73 | Set metadataColumns = extractColumnNames(resultSet); 74 | List resultsList = new ArrayList(); 75 | boolean resultIsNumber = Number.class.isAssignableFrom(clz); 76 | while (resultSet.next()) { 77 | R instance = null; 78 | if (!resultIsNumber) { 79 | instance = clz.newInstance(); 80 | } 81 | for (String columnName : metadataColumns) { 82 | Object value = resultSet.getObject(columnName); 83 | if (value != null) { 84 | log.debug(value.toString()); 85 | if (resultIsNumber) { 86 | instance = (R) value; 87 | break; 88 | } 89 | 90 | EntityPropertyBinding entityProperty = entityPropertiesResolver.resolveEntityPropertyBindingByStoreMappingName(columnName, clz); 91 | if (entityProperty != null) { 92 | BeanUtils.copyProperty(instance, entityProperty.getNameFullPath(), value); 93 | } 94 | 95 | } 96 | } 97 | resultsList.add(instance); 98 | } 99 | 100 | return resultsList; 101 | } 102 | 103 | private Set extractColumnNames(ResultSet resultSet) throws SQLException { 104 | ResultSetMetaData metedata = resultSet.getMetaData(); 105 | int columnCount = metedata.getColumnCount(); 106 | Set metadataColumns = new HashSet(); 107 | for(int i=0;i< columnCount;i++) { 108 | try { 109 | metadataColumns.add(metedata.getColumnName(i + 1)); 110 | } catch(Exception ex) { 111 | log.warn("Exception while reading the metadata for class..."); 112 | } 113 | } 114 | return metadataColumns; 115 | } 116 | 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/hbase/query/PhoenixHBaseQueryExecutor.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.hbase.query; 2 | 3 | import java.sql.Connection; 4 | import java.sql.PreparedStatement; 5 | import java.sql.ResultSet; 6 | import java.sql.SQLException; 7 | import java.sql.Statement; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import com.eharmony.pho.api.DataStoreException; 17 | import com.eharmony.pho.hbase.mapper.PhoenixProjectedResultMapper; 18 | import com.eharmony.pho.hbase.translator.PhoenixHBaseQueryTranslator; 19 | import com.eharmony.pho.query.QuerySelect; 20 | import com.eharmony.pho.query.QueryUpdate; 21 | import com.eharmony.pho.query.builder.QueryUpdateBuilder; 22 | import com.google.common.base.Preconditions; 23 | 24 | /** 25 | * Executes the query on hbase through phoenix query server after translation using query translator. Select query 26 | * results will be mapped back to result object using result mapper. 27 | * 28 | * @author vvangapandu 29 | * 30 | */ 31 | public class PhoenixHBaseQueryExecutor { 32 | 33 | private static final Logger log = LoggerFactory.getLogger(PhoenixHBaseQueryExecutor.class); 34 | 35 | private final PhoenixHBaseQueryTranslator queryTranslator; 36 | private final PhoenixProjectedResultMapper resultMapper; 37 | private boolean showSQL = true; 38 | //Holder for statement properties like queryTimeOut. 39 | private final Map statementProperties; 40 | private static final String QUERY_TIMEOUT_SEC = "queryTimeoutSec"; 41 | 42 | public PhoenixHBaseQueryExecutor(final PhoenixHBaseQueryTranslator queryTranslator, 43 | final PhoenixProjectedResultMapper resultMapper) { 44 | this(queryTranslator, resultMapper, new HashMap()); 45 | } 46 | 47 | public PhoenixHBaseQueryExecutor(final PhoenixHBaseQueryTranslator queryTranslator, 48 | final PhoenixProjectedResultMapper resultMapper, final Map statementProperties) { 49 | this.queryTranslator = Preconditions.checkNotNull(queryTranslator); 50 | this.resultMapper = Preconditions.checkNotNull(resultMapper); 51 | this.statementProperties = Preconditions.checkNotNull(statementProperties); 52 | } 53 | 54 | public Iterable find(QuerySelect query, Connection conn) throws SQLException { 55 | ResultSet resultSet = null; 56 | Statement statement = null; 57 | try { 58 | String queryStr = queryTranslator.translate(query); 59 | if (showSQL) { 60 | log.info("Query String: {}", queryStr); 61 | } 62 | statement = createStatement(conn); 63 | resultSet = statement.executeQuery(queryStr); 64 | return resultMapper.mapResults(resultSet, query.getReturnType()); 65 | } catch (final Exception hx) { 66 | throw new DataStoreException(hx.getMessage(), hx); 67 | } finally { 68 | if (resultSet != null) { 69 | resultSet.close(); 70 | } 71 | if (statement != null) { 72 | statement.close(); 73 | } 74 | } 75 | } 76 | 77 | private void closeStatementSafe(PreparedStatement ps) { 78 | if (ps != null) { 79 | try { 80 | ps.close(); 81 | } catch (Exception ex) { 82 | log.warn("Exception while closing the PreparedStatement...", ex); 83 | } 84 | } 85 | } 86 | 87 | public R findOne(QuerySelect query, Connection conn) { 88 | try { 89 | Iterable results = find(query, conn); 90 | if (results != null && results.iterator() != null && results.iterator().hasNext()) { 91 | return results.iterator().next(); 92 | } 93 | } catch (final Exception hx) { 94 | throw new DataStoreException(hx.getMessage(), hx); 95 | } 96 | return null; 97 | } 98 | 99 | public T save(QueryUpdate query, Connection conn) { 100 | PreparedStatement ps = null; 101 | try { 102 | String queryStr = queryTranslator.translate(query); 103 | if (showSQL) { 104 | log.info("Query String {}", queryStr); 105 | } 106 | ps = createPreparedStatement(conn, queryStr); 107 | 108 | int result = ps.executeUpdate(); 109 | if (result == 0) { 110 | throw new DataStoreException("Save Failed for query..."); 111 | } 112 | return null; 113 | } catch (final Exception hx) { 114 | throw new DataStoreException(hx.getMessage(), hx); 115 | } finally { 116 | closeStatementSafe(ps); 117 | } 118 | } 119 | 120 | public T save(T entity, Connection conn) { 121 | PreparedStatement ps = null; 122 | try { 123 | QueryUpdate query = QueryUpdateBuilder.builderFor(entity).build(); 124 | String queryStr = queryTranslator.translate(query); 125 | if (showSQL) { 126 | log.info("Query String {}", queryStr); 127 | } 128 | ps = createPreparedStatement(conn, queryStr); 129 | int result = ps.executeUpdate(); 130 | if (result == 0) { 131 | throw new DataStoreException("Save Failed for query..."); 132 | } 133 | return entity; 134 | } catch (final Exception hx) { 135 | throw new DataStoreException(hx.getMessage(), hx); 136 | } finally { 137 | closeStatementSafe(ps); 138 | } 139 | } 140 | 141 | public Iterable save(Iterable entities, Connection conn) { 142 | try { 143 | final List saved = new ArrayList(); 144 | for (final T entity : entities) { 145 | saved.add(this.save(entity, conn)); 146 | } 147 | return saved; 148 | } catch (final Exception hx) { 149 | throw new DataStoreException(hx.getMessage(), hx); 150 | } 151 | } 152 | 153 | public int[] saveBatch(Iterable entities, Connection conn) { 154 | PreparedStatement ps = null; 155 | try { 156 | ps = buildStatementWithBatch(entities, conn); 157 | return ps.executeBatch(); 158 | } catch (final Exception hx) { 159 | throw new DataStoreException(hx.getMessage(), hx); 160 | } finally { 161 | closeStatementSafe(ps); 162 | } 163 | } 164 | 165 | public PreparedStatement buildStatementWithBatch(Iterable entities, Connection conn) throws SQLException { 166 | PreparedStatement preparedStatement = null; 167 | for (final T entity : entities) { 168 | QueryUpdate query = QueryUpdateBuilder.builderFor(entity).build(); 169 | String queryStr = queryTranslator.translate(query); 170 | if (showSQL) { 171 | log.info("Query String {}", queryStr); 172 | } 173 | if (preparedStatement == null) { 174 | preparedStatement = createPreparedStatement(conn, queryStr); 175 | } 176 | preparedStatement.addBatch(queryStr); 177 | } 178 | return preparedStatement; 179 | } 180 | 181 | private Statement createStatement(final Connection conn) throws SQLException { 182 | Statement statement = conn.createStatement(); 183 | if(statementProperties.containsKey(QUERY_TIMEOUT_SEC)) { 184 | String queryTimeOutValue = statementProperties.get(QUERY_TIMEOUT_SEC); 185 | try { 186 | statement.setQueryTimeout(Integer.valueOf(queryTimeOutValue)); 187 | } catch(Exception ig) { 188 | log.warn("Ignoring invalid queryTimeout {}", queryTimeOutValue, ig); 189 | } 190 | } 191 | return statement; 192 | 193 | } 194 | 195 | private PreparedStatement createPreparedStatement(final Connection conn, final String queryStr) throws SQLException { 196 | PreparedStatement statement = conn.prepareStatement(queryStr); 197 | if(statementProperties.containsKey(QUERY_TIMEOUT_SEC)) { 198 | String queryTimeOutValue = statementProperties.get(QUERY_TIMEOUT_SEC); 199 | try { 200 | statement.setQueryTimeout(Integer.valueOf(queryTimeOutValue)); 201 | } catch(Exception ig) { 202 | log.warn("Ignoring invalid queryTimeout {}", queryTimeOutValue, ig); 203 | } 204 | } 205 | return statement; 206 | 207 | } 208 | 209 | protected PhoenixProjectedResultMapper getMapper() { 210 | return resultMapper; 211 | } 212 | 213 | public boolean isShowSQL() { 214 | return showSQL; 215 | } 216 | 217 | public void setShowSQL(boolean showSQL) { 218 | this.showSQL = showSQL; 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/hbase/translator/MorphiaEntityResolver.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.hbase.translator; 2 | 3 | import org.apache.commons.lang.StringUtils; 4 | 5 | import com.eharmony.pho.translator.EntityResolver; 6 | import com.google.code.morphia.annotations.Entity; 7 | 8 | /** 9 | * Resolve the collection name of an entity class using Morphia's mapper. 10 | */ 11 | public class MorphiaEntityResolver implements EntityResolver { 12 | 13 | @Override 14 | public String resolve(Class entityClass) { 15 | Entity entity = entityClass.getAnnotation(Entity.class); 16 | String mappedName = entityClass.getSimpleName(); 17 | if (entity != null && StringUtils.isNotBlank(entity.value())) { 18 | mappedName = entity.value(); 19 | } 20 | return mappedName; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/hbase/translator/PhoenixHBaseAggregate.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.hbase.translator; 2 | 3 | import com.eharmony.pho.query.criterion.Symbolic; 4 | 5 | public enum PhoenixHBaseAggregate implements Symbolic { 6 | 7 | GROUP_BY("GROUP BY"), 8 | COUNT("COUNT"), 9 | AVG("AVG"), 10 | SUM("SUM"), 11 | MAX("MAX"), 12 | MIN("MIN"); 13 | 14 | private final String symbol; 15 | 16 | PhoenixHBaseAggregate(String symbol) { 17 | this.symbol = symbol; 18 | } 19 | 20 | @Override 21 | public String symbol() { 22 | return symbol; 23 | } 24 | 25 | 26 | @Override 27 | public String toString() { 28 | return symbol(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/hbase/translator/PhoenixHBaseClauses.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.hbase.translator; 2 | 3 | import com.eharmony.pho.query.criterion.Symbolic; 4 | 5 | /** 6 | * Mappings for Apache Phoenix HBase Clauses 7 | */ 8 | public enum PhoenixHBaseClauses implements Symbolic { 9 | FROM("FROM"), 10 | WHERE("WHERE"), 11 | HAVING("HAVING"), 12 | ORDER_BY("ORDER BY"), 13 | LIMIT("LIMIT"); 14 | 15 | private final String symbol; 16 | 17 | private PhoenixHBaseClauses(String symbol) { 18 | this.symbol = symbol; 19 | } 20 | 21 | @Override 22 | public String symbol() { 23 | return symbol; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return symbol(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/hbase/translator/PhoenixHBaseOperator.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.hbase.translator; 2 | 3 | import com.eharmony.pho.query.criterion.Symbolic; 4 | 5 | /** 6 | * Mappings for Apache Phoenix HBase query operators 7 | */ 8 | public enum PhoenixHBaseOperator implements Symbolic { 9 | EQUAL("="), 10 | NOT_EQUAL("!="), 11 | GREATER_THAN(">"), 12 | GREATER_THAN_OR_EQUAL(">="), 13 | LESS_THAN("<"), 14 | LESS_THAN_OR_EQUAL("<="), 15 | 16 | OR("OR"), 17 | AND("AND"), 18 | 19 | LIKE("LIKE"), 20 | LIKE_CASE_INSENSITIVE("ILIKE"), 21 | 22 | IS_NULL("IS NULL"), 23 | IS_NOT_NULL("IS NOT NULL"); 24 | 25 | private final String symbol; 26 | 27 | private PhoenixHBaseOperator(String symbol) { 28 | this.symbol = symbol; 29 | } 30 | 31 | @Override 32 | public String symbol() { 33 | return symbol; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return symbol(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/hbase/translator/PhoenixHBaseQueryTranslator.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.hbase.translator; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.Date; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | import com.eharmony.pho.query.criterion.expression.Expression; 13 | import com.eharmony.pho.query.criterion.projection.AggregateProjection; 14 | import com.eharmony.pho.query.criterion.projection.GroupProjection; 15 | import com.eharmony.pho.query.criterion.projection.Projection; 16 | import com.google.common.base.Strings; 17 | import org.apache.commons.beanutils.PropertyUtils; 18 | import org.apache.commons.collections.CollectionUtils; 19 | import org.apache.commons.collections.MapUtils; 20 | import org.apache.commons.lang.StringUtils; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import com.eharmony.pho.api.DataStoreException; 25 | import com.eharmony.pho.hbase.util.PhoenixDateFormatUtil; 26 | import com.eharmony.pho.mapper.EntityPropertiesResolver; 27 | import com.eharmony.pho.mapper.EntityPropertyBinding; 28 | import com.eharmony.pho.mapper.EntityPropertyValueBinding; 29 | import com.eharmony.pho.query.QuerySelect; 30 | import com.eharmony.pho.query.QueryUpdate; 31 | import com.eharmony.pho.query.criterion.Criterion; 32 | import com.eharmony.pho.query.criterion.Ordering; 33 | import com.eharmony.pho.query.criterion.Orderings; 34 | import com.eharmony.pho.query.criterion.Ordering.NullOrdering; 35 | import com.eharmony.pho.query.criterion.Ordering.Order; 36 | import com.eharmony.pho.query.criterion.expression.NativeExpression; 37 | import com.eharmony.pho.translator.AbstractQueryTranslator; 38 | import com.eharmony.pho.translator.QueryTranslator; 39 | import com.google.common.base.Function; 40 | import com.google.common.base.Joiner; 41 | import com.google.common.collect.Lists; 42 | 43 | import static com.eharmony.pho.hbase.translator.PhoenixHBaseAggregate.*; 44 | 45 | /** 46 | * Translates the entity to Phoenix query to execute on HBase. 47 | *

48 | * 1. Entity classes must be registered through constructor. 2. SELECT, DELETE and UPSERT queries supported. 3. 49 | * provided support to escape the special characters while querying and inserting the data ("'", "/" etc..) 4. No 50 | * support for joins and sub-queries are not tested 51 | * 52 | * @author vvangapandu 53 | */ 54 | public class PhoenixHBaseQueryTranslator extends AbstractQueryTranslator implements 55 | QueryTranslator { 56 | 57 | private final MorphiaEntityResolver entityResolver = new MorphiaEntityResolver(); 58 | private EntityPropertiesResolver entityPropertiesResolver; 59 | private static final String PROJECTION_ALL = "*"; 60 | private static final String SELECT = "SELECT"; 61 | private static final String STRING_OPERAND_WITH_WILDCARD = "%%%s%%"; 62 | 63 | private static final Logger logger = LoggerFactory.getLogger(PhoenixHBaseQueryTranslator.class); 64 | 65 | private static final int ORDER_EXPRESSION_SUFFIX_MAX_LENGTH = "DESC NULLS FIRST".length(); 66 | 67 | public PhoenixHBaseQueryTranslator(Class queryClass, Class orderClass, 68 | EntityPropertiesResolver propertyResolver) { 69 | super(queryClass, orderClass, propertyResolver); 70 | this.entityPropertiesResolver = propertyResolver; 71 | } 72 | 73 | public PhoenixHBaseQueryTranslator(final EntityPropertiesResolver propertyResolver) { 74 | super(String.class, String.class, propertyResolver); 75 | this.entityPropertiesResolver = propertyResolver; 76 | } 77 | 78 | /** 79 | * translates given QuerySelect object to select query string 80 | * 81 | * @param query QuerySelect 82 | * @return String 83 | */ 84 | @Override 85 | public String translate(QuerySelect query) { 86 | return translateSelectQuery(query); 87 | } 88 | 89 | private String translateSelectQuery(QuerySelect query) { 90 | List fields = query.getReturnFields(); 91 | Criterion rootCriterion = query.getCriteria(); 92 | Criterion groupCriterion = query.getGroupCriteria(); 93 | Orderings orders = query.getOrder(); 94 | Integer maxResults = query.getMaxResults(); 95 | Class entityClass = query.getEntityClass(); 96 | Joiner spaceJoiner = Joiner.on(" "); 97 | 98 | List projections = query.getProjection(); 99 | 100 | String projection = PROJECTION_ALL; 101 | if (CollectionUtils.isNotEmpty(fields)) { 102 | projection = Joiner.on(", ").join( 103 | entityPropertiesResolver.resolveEntityMappingPropertyNames(fields, entityClass)); 104 | } 105 | 106 | if (projections != null && CollectionUtils.isNotEmpty(projections)) { 107 | List properties = new ArrayList<>(); 108 | for (Projection p : projections) { 109 | if (p instanceof AggregateProjection) { 110 | properties.add(translate((AggregateProjection) p, 111 | entityPropertiesResolver.resolve(p.getPropertyNames().get(0), entityClass))); 112 | } else { 113 | properties.addAll(p.getPropertyNames()); 114 | } 115 | projection = Joiner.on(", ").join( 116 | entityPropertiesResolver.resolveEntityMappingPropertyNames(properties, entityClass)); 117 | } 118 | } 119 | //Add query hint if available 120 | projection = Strings.isNullOrEmpty(query.getQueryHint()) ? projection : spaceJoiner.join(query.getQueryHint(), PROJECTION_ALL); 121 | String queryString = spaceJoiner.join(new String[]{SELECT, projection, PhoenixHBaseClauses.FROM.symbol(), 122 | entityResolver.resolve(entityClass)}); 123 | 124 | if (rootCriterion != null) { 125 | queryString = spaceJoiner.join(queryString, PhoenixHBaseClauses.WHERE.symbol(), translate(rootCriterion, entityClass)); 126 | } 127 | 128 | if (orders != null && CollectionUtils.isNotEmpty(orders.get())) { 129 | queryString = spaceJoiner.join(queryString, PhoenixHBaseClauses.ORDER_BY.symbol(), translateOrder(query)); 130 | } 131 | 132 | if (maxResults != null && maxResults > 0) { 133 | queryString = spaceJoiner.join(queryString, PhoenixHBaseClauses.LIMIT.symbol(), maxResults); 134 | } 135 | if (projections != null && CollectionUtils.isNotEmpty(projections)) { 136 | for (Projection p : projections) { 137 | if (p instanceof GroupProjection) { 138 | queryString = spaceJoiner.join(queryString, translate(p, entityClass)); 139 | } 140 | } 141 | } 142 | if (groupCriterion != null) { 143 | if (groupCriterion instanceof Expression) { 144 | queryString = spaceJoiner.join(queryString, PhoenixHBaseClauses.HAVING.symbol(), 145 | translate(groupCriterion, entityClass, ((Expression) groupCriterion).getAggregateProjection())); 146 | } else { 147 | queryString = spaceJoiner.join(queryString, PhoenixHBaseClauses.HAVING.symbol(), 148 | translate(groupCriterion, entityClass)); 149 | } 150 | } 151 | return queryString; 152 | } 153 | 154 | private String resolveMappingName(String fieldName) { 155 | return fieldName; 156 | } 157 | 158 | private String resolveMappingNames(String... fieldNames) { 159 | return Joiner.on(", ").join(Arrays.asList(fieldNames)); 160 | } 161 | 162 | @Override 163 | public String eq(String fieldName, Object value) { 164 | 165 | return join(resolveMappingName(fieldName), PhoenixHBaseOperator.EQUAL, value); 166 | } 167 | 168 | @Override 169 | public String ne(String fieldName, Object value) { 170 | return join(resolveMappingName(fieldName), PhoenixHBaseOperator.NOT_EQUAL, value); 171 | } 172 | 173 | @Override 174 | public String lt(String fieldName, Object value) { 175 | return join(resolveMappingName(fieldName), PhoenixHBaseOperator.LESS_THAN, value); 176 | } 177 | 178 | @Override 179 | public String lte(String fieldName, Object value) { 180 | return join(resolveMappingName(fieldName), PhoenixHBaseOperator.LESS_THAN_OR_EQUAL, value); 181 | } 182 | 183 | @Override 184 | public String gt(String fieldName, Object value) { 185 | return join(resolveMappingName(fieldName), PhoenixHBaseOperator.GREATER_THAN, value); 186 | } 187 | 188 | @Override 189 | public String gte(String fieldName, Object value) { 190 | return join(resolveMappingName(fieldName), PhoenixHBaseOperator.GREATER_THAN_OR_EQUAL, value); 191 | } 192 | 193 | @Override 194 | public String insensitiveLike(String fieldName, Object value) { 195 | return join(resolveMappingName(fieldName), 196 | PhoenixHBaseOperator.LIKE_CASE_INSENSITIVE, 197 | String.format(STRING_OPERAND_WITH_WILDCARD, value)); 198 | } 199 | 200 | @Override 201 | public String like(String fieldName, Object value) { 202 | return join(resolveMappingName(fieldName), 203 | PhoenixHBaseOperator.LIKE, 204 | String.format(STRING_OPERAND_WITH_WILDCARD, value)); 205 | } 206 | 207 | @Override 208 | public String between(String fieldName, Object from, Object to) { 209 | throw new UnsupportedOperationException("BETWEEN operator is not supported in phoenix hbase library..."); 210 | } 211 | 212 | @Override 213 | public String in(String fieldName, Object[] values) { 214 | throw new UnsupportedOperationException("IN operator is not supported in phoenix hbase library..."); 215 | } 216 | 217 | @Override 218 | public String notIn(String fieldName, Object[] values) { 219 | throw new UnsupportedOperationException("NOTIN operator is not supported in phoenix hbase library..."); 220 | } 221 | 222 | @Override 223 | public String contains(String fieldName, Object[] values) { 224 | throw new UnsupportedOperationException("CONTAINS operator is not supported in phoenix hbase library..."); 225 | } 226 | 227 | @Override 228 | public String isNull(String fieldName) { 229 | return join(resolveMappingName(fieldName), PhoenixHBaseOperator.IS_NULL); 230 | } 231 | 232 | @Override 233 | public String notNull(String fieldName) { 234 | return join(resolveMappingName(fieldName), PhoenixHBaseOperator.IS_NOT_NULL); 235 | } 236 | 237 | @Override 238 | public String isEmpty(String fieldName) { 239 | throw new UnsupportedOperationException("ISEMPTY operator is not supported in phoenix hbase library..."); 240 | } 241 | 242 | @Override 243 | public String notEmpty(String fieldName) { 244 | throw new UnsupportedOperationException("NOTEMPTY operator is not supported in phoenix hbase library..."); 245 | } 246 | 247 | @Override 248 | protected String translate(NativeExpression e, Class entityClass) { 249 | return e.getExpression().toString(); 250 | } 251 | 252 | @Override 253 | public String order(String... orders) { 254 | return Joiner.on(", ").join(orders); 255 | } 256 | 257 | @Override 258 | public String order(String fieldName, Ordering ordering) { 259 | if (ordering == null || StringUtils.isBlank(ordering.getPropertyName())) { 260 | return StringUtils.EMPTY; 261 | } 262 | StringBuilder orderExpressionBuilder = new StringBuilder(fieldName.length() + ORDER_EXPRESSION_SUFFIX_MAX_LENGTH); 263 | orderExpressionBuilder.append(fieldName); 264 | Order order = ordering.getOrder(); 265 | if (order != null) { 266 | orderExpressionBuilder.append(' '); 267 | orderExpressionBuilder 268 | .append(Order.ASCENDING.equals(order) ? "ASC" 269 | : "DESC"); 270 | NullOrdering nullOrdering = ordering.getNullOrdering(); 271 | if (nullOrdering != null) { 272 | orderExpressionBuilder.append(" NULLS "); 273 | orderExpressionBuilder.append(nullOrdering.name()); 274 | } 275 | } 276 | return orderExpressionBuilder.toString(); 277 | 278 | } 279 | 280 | @Override 281 | public String translateProjection(QuerySelect query) { 282 | return query.getReturnFields().size() > 0 ? Joiner.on(",").join(query.getReturnFields()) : null; 283 | } 284 | 285 | protected String join(String fieldName, Object... parts) { 286 | return fieldName + " " + Joiner.on(" ").join(Lists.transform(Arrays.asList(parts), toString)); 287 | } 288 | 289 | protected String joinAggregateFunc(PhoenixHBaseAggregate function, String... fieldNames) { 290 | return function + "(" + resolveMappingNames(fieldNames) + ")"; 291 | } 292 | 293 | private final Function toString = new Function() { 294 | @Override 295 | public String apply(Object o) { 296 | return string(o); 297 | } 298 | }; 299 | 300 | protected String string(Object o) { 301 | if (o instanceof Object[]) { 302 | return "[" + Joiner.on(",").join(Lists.transform(Arrays.asList((Object[]) o), toString)) + "]"; 303 | } else if (o instanceof String) { 304 | return "'" + sanitizeString((String) o) + "'"; 305 | } else if (o instanceof Character) { 306 | logger.debug("Converting char type, value {}", o); 307 | return "'" + sanitizeString(o.toString()) + "'"; 308 | } else if (o instanceof Date) { 309 | return PhoenixDateFormatUtil.formatDate((Date) o); 310 | } else if (o != null) { 311 | return o.toString(); 312 | } 313 | logger.warn("Can't convert null object to String"); 314 | return null; 315 | 316 | } 317 | 318 | private String sanitizeString(String stringValue) { 319 | if (stringValue.contains("'")) { 320 | stringValue = stringValue.replace("'", "''"); 321 | } 322 | if (stringValue.contains("\\")) { 323 | stringValue = stringValue.replace("\\", "\\\\"); 324 | } 325 | return stringValue; 326 | } 327 | 328 | @Override 329 | public String translate(QueryUpdate updateQuery) { 330 | T entity = updateQuery.getEntity(); 331 | List selectedFields = updateQuery.getSelectedFields(); 332 | try { 333 | List entityPropertyValueBindings = buildParameterBindings(entity, 334 | selectedFields); 335 | 336 | if (CollectionUtils.isEmpty(entityPropertyValueBindings)) { 337 | throw new DataStoreException("Invalid Entity to save :" 338 | + (updateQuery.getEntity() != null ? updateQuery.getEntity().getClass() : "")); 339 | } 340 | String columnsList = ""; 341 | String valuesList = ""; 342 | Joiner joiner = Joiner.on(", "); 343 | for (EntityPropertyValueBinding propertyValueBinding : entityPropertyValueBindings) { 344 | if (propertyValueBinding.getValue() != null) { 345 | try { 346 | if (StringUtils.isEmpty(columnsList)) { 347 | columnsList = propertyValueBinding.getEntityPropertyBinding().getStoreFieldName(); 348 | valuesList = string(propertyValueBinding.getValue()); 349 | } else { 350 | columnsList = joiner.join(columnsList, propertyValueBinding.getEntityPropertyBinding() 351 | .getStoreFieldName()); 352 | valuesList = joiner.join(valuesList, string(propertyValueBinding.getValue())); 353 | } 354 | } catch (Exception ex) { 355 | logger.warn("Exception while translating the update query for property {} and value {}", 356 | propertyValueBinding.getEntityPropertyBinding().getName(), 357 | propertyValueBinding.getValue(), ex); 358 | throw new RuntimeException(ex); 359 | } 360 | 361 | } else { 362 | logger.info("There is no binding value for property {} and skipping the translation.", 363 | propertyValueBinding.getEntityPropertyBinding().getNameFullPath()); 364 | continue; 365 | } 366 | } 367 | String tableName = entityResolver.resolve(entity.getClass()); 368 | StringBuilder finalQuery = new StringBuilder("UPSERT INTO "); 369 | finalQuery.append(tableName).append("(").append(columnsList).append(")").append(" values ").append("(") 370 | .append(valuesList).append(")"); 371 | 372 | return finalQuery.toString(); 373 | 374 | } catch (Exception ex) { 375 | throw new RuntimeException(ex); 376 | } 377 | 378 | } 379 | 380 | private List buildParameterBindings(T entity, List selectedFields) 381 | throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { 382 | 383 | try { 384 | 385 | Map entityPropertyBindings = entityPropertiesResolver 386 | .getEntityPropertyNamePropertyBindingMap(entity.getClass()); 387 | if (MapUtils.isEmpty(entityPropertyBindings)) { 388 | throw new DataStoreException("Invalid Entity class " + entity.getClass().getSimpleName()); 389 | } 390 | List entityPropertyValueBindings = new LinkedList(); 391 | int position = 1; 392 | Set entityFields = entityPropertyBindings.keySet(); 393 | if (CollectionUtils.isEmpty(selectedFields)) { 394 | selectedFields = new ArrayList(entityFields); 395 | } 396 | for (String beanPropertyName : selectedFields) { 397 | EntityPropertyBinding entityPropertyBinding = entityPropertyBindings.get(beanPropertyName); 398 | if (entityPropertyBinding == null) { 399 | String errorMessage = "Invalid bean property " + beanPropertyName + " for bean " 400 | + entity.getClass().getSimpleName(); 401 | throw new DataStoreException(errorMessage); 402 | } 403 | Object value = PropertyUtils.getProperty(entity, entityPropertyBinding.getNameFullPath()); 404 | EntityPropertyValueBinding entityPropertyValueBinding = new EntityPropertyValueBinding( 405 | entityPropertyBinding); 406 | entityPropertyValueBinding.setValue(value); 407 | entityPropertyValueBinding.setPosition(position++); 408 | entityPropertyValueBindings.add(entityPropertyValueBinding); 409 | } 410 | return entityPropertyValueBindings; 411 | } catch (Exception ex) { 412 | logger.warn("Exceotion while building parameter bindings for entity {}", entity.getClass(), ex); 413 | throw new DataStoreException(ex); 414 | } 415 | 416 | } 417 | 418 | @Override 419 | public String and(String... subqueries) { 420 | return junction(PhoenixHBaseOperator.AND, subqueries); 421 | } 422 | 423 | @Override 424 | public String or(String... subqueries) { 425 | return junction(PhoenixHBaseOperator.OR, subqueries); 426 | } 427 | 428 | protected String junction(PhoenixHBaseOperator operator, String... subqueries) { 429 | if (subqueries.length < 1) { 430 | return ""; 431 | } else if (subqueries.length == 1) { 432 | return subqueries[0]; 433 | } else { 434 | return "(" + Joiner.on(") " + operator.symbol() + " (").join(subqueries) + ")"; 435 | } 436 | } 437 | 438 | @Override 439 | public String limit(Integer value) { 440 | 441 | if (value != null && value > 0) { 442 | return "LIMIT " + value; 443 | } 444 | return ""; 445 | 446 | } 447 | 448 | @Override 449 | public String groupBy(String... fieldNames) { 450 | return joinAggregateFunc(GROUP_BY, fieldNames); 451 | } 452 | 453 | @Override 454 | public String count(String fieldName) { 455 | return joinAggregateFunc(COUNT, fieldName); 456 | } 457 | 458 | @Override 459 | public String countAll() { 460 | return joinAggregateFunc(COUNT, "*"); 461 | } 462 | 463 | @Override 464 | public String sum(String fieldName) { 465 | return joinAggregateFunc(SUM, fieldName); 466 | } 467 | 468 | @Override 469 | public String avg(String fieldName) { 470 | return joinAggregateFunc(AVG, fieldName); 471 | } 472 | 473 | @Override 474 | public String max(String fieldName) { 475 | return joinAggregateFunc(MAX, resolveMappingName(fieldName)); 476 | } 477 | 478 | @Override 479 | public String min(String fieldName) { 480 | return joinAggregateFunc(MIN, fieldName); 481 | } 482 | 483 | } -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/hbase/util/PhoenixConnectionManager.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.hbase.util; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DriverManager; 5 | /** 6 | * Manages the phoenix connections based on JDBC driver 7 | * 8 | * @author vvangapandu 9 | * 10 | */ 11 | public class PhoenixConnectionManager { 12 | 13 | public static Connection getConnection(final String connectionString) throws Exception { 14 | Class.forName("org.apache.phoenix.jdbc.PhoenixDriver"); 15 | return DriverManager.getConnection(connectionString); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/hbase/util/PhoenixDateFormatUtil.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.hbase.util; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | import com.google.common.base.Preconditions; 7 | /** 8 | * Utility class to convert the java.util.Date to apache phoenix date format to insert into hbase. 9 | * 10 | * Note: this class is using simpledateformat, but should be 11 | * switched to org.apache.commons.lang3.time.FastDateFormat if there are any performance issues 12 | * 13 | * @author vvangapandu 14 | * 15 | */ 16 | public class PhoenixDateFormatUtil { 17 | 18 | public static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss z"; 19 | 20 | public static final String formatDate(Date date) { 21 | Preconditions.checkNotNull(date, "date must not be null"); 22 | String formattedDate = new SimpleDateFormat(TIMESTAMP_FORMAT).format(date); 23 | StringBuffer dateFormatBuilder = new StringBuffer("TO_DATE('"); 24 | dateFormatBuilder.append(formattedDate).append("', 'yyyy-MM-dd HH:mm:ss z')"); 25 | return dateFormatBuilder.toString(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/mapper/EntityPropertiesMappingContext.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.mapper; 2 | 3 | import java.lang.reflect.Field; 4 | import java.util.HashMap; 5 | import java.util.HashSet; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | import org.apache.commons.lang.StringUtils; 12 | 13 | import com.google.code.morphia.annotations.Embedded; 14 | import com.google.code.morphia.annotations.Property; 15 | 16 | /** 17 | * Provides the mappings between entity properties and datastore mapping columns. 18 | * 19 | * Entity mappings will be resolved for configured classes on instantiation of this class. 20 | * 21 | * @author vvangapandu 22 | * 23 | */ 24 | public class EntityPropertiesMappingContext { 25 | 26 | private Map> storeFieldToEntityPropertyBindingMap = new HashMap>(); 27 | private Map> entityPropertyToStoreFieldBindingMap = new HashMap>(); 28 | 29 | private static final String PROPERTY_SEPARATOR = "."; 30 | 31 | public EntityPropertiesMappingContext(List classNames) throws ClassNotFoundException { 32 | if (classNames != null) { 33 | for (String clzName : classNames) { 34 | Class clz = getClass().getClassLoader().loadClass(clzName); 35 | populateMappings(clz); 36 | } 37 | 38 | } 39 | } 40 | 41 | public void populateMappings(Class clz) { 42 | 43 | String className = clz.getSimpleName(); 44 | Set entityPropertiesSet = buildEntityPropertiesSet(clz, null); 45 | Map entityMappingPropertiesMap = populateEntityMappingPropertiesMap( 46 | entityPropertiesSet, clz); 47 | storeFieldToEntityPropertyBindingMap.put(className, entityMappingPropertiesMap); 48 | 49 | Map entityFieldPropertiesMap = populateEntityFieldPropertiesMap( 50 | entityPropertiesSet, clz); 51 | entityPropertyToStoreFieldBindingMap.put(className, entityFieldPropertiesMap); 52 | 53 | } 54 | 55 | private Map populateEntityMappingPropertiesMap( 56 | Set entityPropertiesSetInternal, Class clz) { 57 | Map propertiesMap = new HashMap(); 58 | for (EntityPropertyBinding entityProperty : entityPropertiesSetInternal) { 59 | propertiesMap.put(entityProperty.getStoreFieldName().toUpperCase(), entityProperty); 60 | } 61 | return propertiesMap; 62 | } 63 | 64 | private Map populateEntityFieldPropertiesMap( 65 | Set entityPropertiesSetInternal, Class clz) { 66 | Map propertiesMap = new HashMap(); 67 | for (EntityPropertyBinding entityProperty : entityPropertiesSetInternal) { 68 | propertiesMap.put(entityProperty.getName(), entityProperty); 69 | // should the property resolved by complete path? 70 | // propertiesMap.put(entityProperty.getMappingNameFullPath(), entityProperty); 71 | } 72 | return propertiesMap; 73 | } 74 | 75 | public EntityPropertyBinding resolveEntityPropertyBindingByStoreMappingName(Class clz, String mappingName) { 76 | Map entityProperties = storeFieldToEntityPropertyBindingMap.get(clz 77 | .getSimpleName()); 78 | 79 | if (entityProperties == null || entityProperties.size() == 0) { 80 | return null; 81 | } 82 | return entityProperties.get(mappingName.toUpperCase()); 83 | } 84 | 85 | public EntityPropertyBinding resolveEntityPropertyBindingByEntityFieldName(Class clz, String fieldName) { 86 | Map entityProperties = storeFieldToEntityPropertyBindingMap.get(clz 87 | .getSimpleName()); 88 | if (entityProperties == null || entityProperties.size() == 0) { 89 | return null; 90 | } 91 | EntityPropertyBinding entityProperty = entityProperties.get(fieldName); 92 | if (entityProperty != null && StringUtils.isNotBlank(entityProperty.getStoreFieldName())) { 93 | return entityProperty; 94 | } 95 | return null; 96 | } 97 | 98 | public String resolveEntityMappingPropertyName(Class clz, String fieldName) { 99 | Map entityProperties = entityPropertyToStoreFieldBindingMap.get(clz 100 | .getSimpleName()); 101 | if (entityProperties == null || entityProperties.size() == 0) { 102 | return fieldName; 103 | } 104 | EntityPropertyBinding entityProperty = entityProperties.get(fieldName); 105 | if (entityProperty != null && StringUtils.isNotBlank(entityProperty.getStoreFieldName())) { 106 | return entityProperty.getStoreFieldName(); 107 | } 108 | return fieldName; 109 | } 110 | 111 | public List resolveEntityMappingPropertyNames(Class clz, List fieldPropertyNames) { 112 | Map entityProperties = entityPropertyToStoreFieldBindingMap.get(clz 113 | .getSimpleName()); 114 | 115 | if (entityProperties == null || entityProperties.size() == 0) { 116 | throw new IllegalArgumentException("Invalid Entity Class:" + clz.getSimpleName()); 117 | } 118 | List mappingProperties = new LinkedList(); 119 | for (String fieldPropertyName : fieldPropertyNames) { 120 | EntityPropertyBinding entityProperty = entityProperties.get(fieldPropertyName); 121 | String mappingName = fieldPropertyName; 122 | if (entityProperty != null && StringUtils.isNotBlank(entityProperty.getStoreFieldName())) { 123 | mappingName = entityProperty.getStoreFieldName(); 124 | } 125 | mappingProperties.add(mappingName); 126 | } 127 | return mappingProperties; 128 | } 129 | 130 | private Set buildEntityPropertiesSet(Class clz, String parentProperty) { 131 | 132 | Set entityPropertiesSet = new HashSet(); 133 | Field[] fields = clz.getDeclaredFields(); 134 | if (fields == null || fields.length == 0) { 135 | return entityPropertiesSet; 136 | } 137 | for (Field field : fields) { 138 | if (field.isAnnotationPresent(Embedded.class)) { 139 | StringBuilder nameBuilder = new StringBuilder(); 140 | if (StringUtils.isNotEmpty(parentProperty)) { 141 | nameBuilder.append(parentProperty).append(PROPERTY_SEPARATOR); 142 | } 143 | nameBuilder.append(field.getName()); 144 | entityPropertiesSet.addAll(buildEntityPropertiesSet(field.getType(), nameBuilder.toString())); 145 | } else { 146 | EntityPropertyBinding entityProperty = buildEntityProperty(field, parentProperty); 147 | if (entityProperty != null) { 148 | entityPropertiesSet.add(entityProperty); 149 | } 150 | } 151 | 152 | } 153 | return entityPropertiesSet; 154 | } 155 | 156 | private EntityPropertyBinding buildEntityProperty(Field simpleField, String parentProperty) { 157 | 158 | EntityPropertyBinding entityProperty = new EntityPropertyBinding(); 159 | Property propertyAnnotation = simpleField.getAnnotation(Property.class); 160 | if (propertyAnnotation == null) { 161 | // Mapping store column doesn't exist for field, consider it as non store property. 162 | // should we consider property name as store field name in absence of annotation???? 163 | // entityProperty.setStoreFieldName(simpleField.getName()); 164 | return null; 165 | } 166 | 167 | entityProperty.setStoreFieldName(propertyAnnotation.value()); 168 | entityProperty.setName(simpleField.getName()); 169 | entityProperty.setType(simpleField.getType()); 170 | StringBuilder nameBuilder = new StringBuilder(); 171 | if (StringUtils.isNotEmpty(parentProperty)) { 172 | nameBuilder.append(parentProperty).append(PROPERTY_SEPARATOR); 173 | } 174 | nameBuilder.append(simpleField.getName()); 175 | entityProperty.setNameFullPath(nameBuilder.toString()); 176 | return entityProperty; 177 | } 178 | 179 | public String resolve(String fieldName, Class entityClass) { 180 | Map propertiesMap = entityPropertyToStoreFieldBindingMap.get(entityClass 181 | .getSimpleName()); 182 | 183 | if (propertiesMap == null) { 184 | throw new IllegalArgumentException("Invalid Entity class:" + entityClass.getSimpleName()); 185 | } 186 | 187 | EntityPropertyBinding entityProperty = propertiesMap.get(fieldName); 188 | if (entityProperty != null && StringUtils.isNotBlank(entityProperty.getStoreFieldName())) { 189 | return entityProperty.getStoreFieldName(); 190 | } 191 | return fieldName; 192 | } 193 | 194 | public Map getStoreFieldNamePropertyBindingMap(Class clz) { 195 | return storeFieldToEntityPropertyBindingMap.get(clz.getSimpleName()); 196 | } 197 | 198 | public Map getEntityPropertyNamePropertyBindingMap(Class clz) { 199 | return entityPropertyToStoreFieldBindingMap.get(clz.getSimpleName()); 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/mapper/EntityPropertiesResolver.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.mapper; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import com.eharmony.pho.translator.PropertyResolver; 7 | 8 | public class EntityPropertiesResolver implements PropertyResolver { 9 | 10 | private final EntityPropertiesMappingContext entityPropertiesMappingContext; 11 | 12 | public EntityPropertiesResolver(final EntityPropertiesMappingContext entityPropertiesMappingContext) { 13 | this.entityPropertiesMappingContext = entityPropertiesMappingContext; 14 | } 15 | 16 | @Override 17 | public EntityPropertyBinding resolveEntityPropertyBindingByStoreMappingName(String mappingName, Class clz) { 18 | return entityPropertiesMappingContext.resolveEntityPropertyBindingByStoreMappingName(clz, mappingName); 19 | } 20 | 21 | @Override 22 | public EntityPropertyBinding resolveEntityPropertyBindingByEntityFieldName(String entityFieldName, 23 | Class entityClass) { 24 | return entityPropertiesMappingContext.resolveEntityPropertyBindingByEntityFieldName(entityClass, 25 | entityFieldName); 26 | } 27 | 28 | @Override 29 | public List resolveEntityMappingPropertyNames(List fieldPropertyNames, Class clz) { 30 | return entityPropertiesMappingContext.resolveEntityMappingPropertyNames(clz, fieldPropertyNames); 31 | } 32 | 33 | @Override 34 | public String resolve(String fieldPropertyName, Class entityClass) { 35 | return entityPropertiesMappingContext.resolveEntityMappingPropertyName(entityClass, fieldPropertyName); 36 | } 37 | 38 | @Override 39 | public Map getStoreFieldNamePropertyBindingMap(Class clz) { 40 | return entityPropertiesMappingContext.getStoreFieldNamePropertyBindingMap(clz); 41 | } 42 | 43 | @Override 44 | public Map getEntityPropertyNamePropertyBindingMap(Class clz) { 45 | return entityPropertiesMappingContext.getEntityPropertyNamePropertyBindingMap(clz); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/mapper/EntityPropertyBinding.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.mapper; 2 | 3 | import java.lang.reflect.Type; 4 | 5 | /** 6 | * Contains the information about entity property to store property mapping 7 | * 8 | * @author vvangapandu 9 | * 10 | */ 11 | public class EntityPropertyBinding { 12 | 13 | private String name; 14 | private Type type; 15 | private String storeFieldName; 16 | private String nameFullPath; 17 | 18 | public String getName() { 19 | return name; 20 | } 21 | 22 | public void setName(String name) { 23 | this.name = name; 24 | } 25 | 26 | public Type getType() { 27 | return type; 28 | } 29 | 30 | public void setType(Type type) { 31 | this.type = type; 32 | } 33 | 34 | public String getStoreFieldName() { 35 | return storeFieldName; 36 | } 37 | 38 | public void setStoreFieldName(String storeFieldName) { 39 | this.storeFieldName = storeFieldName; 40 | } 41 | 42 | public String getNameFullPath() { 43 | return nameFullPath; 44 | } 45 | 46 | public void setNameFullPath(String nameFullPath) { 47 | this.nameFullPath = nameFullPath; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/mapper/EntityPropertyValueBinding.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.mapper; 2 | 3 | /** 4 | * Contains the information about entity property to store property mapping and value of the property 5 | * 6 | * @author vvangapandu 7 | * 8 | */ 9 | public class EntityPropertyValueBinding { 10 | 11 | private final EntityPropertyBinding entityPropertyBinding; 12 | private int position; 13 | private Object value; 14 | 15 | public EntityPropertyValueBinding(EntityPropertyBinding entityPropertyBinding) { 16 | this.entityPropertyBinding = entityPropertyBinding; 17 | } 18 | 19 | public EntityPropertyBinding getEntityPropertyBinding() { 20 | return entityPropertyBinding; 21 | } 22 | 23 | public int getPosition() { 24 | return position; 25 | } 26 | 27 | public void setPosition(int position) { 28 | this.position = position; 29 | } 30 | 31 | public Object getValue() { 32 | return value; 33 | } 34 | 35 | public void setValue(Object value) { 36 | this.value = value; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/mapper/ProjectedResultMapper.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.mapper; 2 | 3 | import java.util.Map; 4 | 5 | import org.codehaus.jackson.map.ObjectMapper; 6 | 7 | import com.google.common.collect.Maps; 8 | 9 | /** 10 | * Map an array of properties and an array of associated property names to an 11 | * Object of a specified type. 12 | */ 13 | public class ProjectedResultMapper { 14 | 15 | private final ObjectMapper mapper; 16 | 17 | public ProjectedResultMapper(ObjectMapper mapper) { 18 | this.mapper = mapper; 19 | } 20 | 21 | public ProjectedResultMapper() { 22 | this(new ObjectMapper()); 23 | } 24 | 25 | protected Map propertyMap(Object[] properties, 26 | String[] propertyNames) { 27 | if (properties.length != propertyNames.length) { 28 | throw new IllegalArgumentException("The number of properties (" 29 | + properties.length 30 | + ") must match the number of property names (" 31 | + propertyNames.length + ")"); 32 | } 33 | int n = properties.length; 34 | Map map = Maps.newHashMapWithExpectedSize(n); 35 | for (int i = 0; i < n; i++) { 36 | map.put(propertyNames[i], properties[i]); 37 | } 38 | return map; 39 | } 40 | 41 | /** 42 | * Map an Object or an Object array with an associated array of property 43 | * names to the provided type. 44 | * @param return type 45 | * @param resultClass 46 | * the desired mapped type 47 | * @param properties 48 | * the property or properties to be mapped 49 | * @param propertyNames 50 | * an array of associated property names 51 | * @return the mapped object of type R 52 | */ 53 | public R mapTo(Class resultClass, Object properties, 54 | String[] propertyNames) { 55 | return propertyNames.length == 1 56 | ? resultClass.cast(properties) 57 | : mapper.convertValue(propertyMap((Object[]) properties, propertyNames), resultClass); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/QueryOperationType.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query; 2 | 3 | 4 | public enum QueryOperationType { 5 | 6 | SELECT, UPDATE, INSERT, DELETE; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/QuerySelect.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query; 2 | 3 | import java.util.List; 4 | 5 | import com.eharmony.pho.query.criterion.Criterion; 6 | import com.eharmony.pho.query.criterion.GroupCriterion; 7 | import com.eharmony.pho.query.criterion.Orderings; 8 | import com.eharmony.pho.query.criterion.projection.Projection; 9 | 10 | /** 11 | * A generic, object oriented representation of a query 12 | * 13 | * @param 14 | * the entity type being queried 15 | * @param 16 | * the desired return type 17 | */ 18 | public interface QuerySelect { 19 | 20 | /** 21 | * Get the queried entity type. 22 | * 23 | * @return the entity class being queried 24 | */ 25 | public Class getEntityClass(); 26 | 27 | /** 28 | * Get the return entity type. 29 | * 30 | * @return the class of the desired return type 31 | */ 32 | public Class getReturnType(); 33 | 34 | /** 35 | * The list of properties to return. An empty collection means all properties. 36 | * 37 | * @return the fields to be returned from the query 38 | */ 39 | public List getReturnFields(); 40 | 41 | /** 42 | * Get the query criteria. The top level object can either be a single Criterion or a Junction of multiple nested 43 | * Criterion objects. 44 | * 45 | * @return the root criterion node 46 | */ 47 | public Criterion getCriteria(); 48 | 49 | Criterion getGroupCriteria(); 50 | 51 | /** 52 | * Get the Order clauses. 53 | * 54 | * @return the ordering clauses 55 | */ 56 | public Orderings getOrder(); 57 | 58 | public List getProjection(); 59 | 60 | /** 61 | * Get the max desired results. Null signifies no maximum. 62 | * 63 | * @return the maximum number of results or null if no maximum 64 | * 65 | */ 66 | public Integer getMaxResults(); 67 | 68 | /** 69 | * Getter method for Query Operation type 70 | * 71 | * @return QueryOperationType 72 | */ 73 | public QueryOperationType getQueryOperationType(); 74 | 75 | /** 76 | * Getter method for Query Hint 77 | * 78 | * @return String 79 | */ 80 | public String getQueryHint(); 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/QuerySelectImpl.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query; 2 | 3 | import java.util.List; 4 | 5 | import com.eharmony.pho.query.criterion.Criterion; 6 | import com.eharmony.pho.query.criterion.GroupCriterion; 7 | import com.eharmony.pho.query.criterion.Orderings; 8 | import com.eharmony.pho.query.criterion.projection.Projection; 9 | 10 | /** 11 | * The default implementation of the generic Query interface 12 | * 13 | * @param 14 | * the entity type being queried 15 | * @param 16 | * the desired return type 17 | */ 18 | public class QuerySelectImpl implements QuerySelect { 19 | 20 | private final Class entityClass; 21 | private final Class returnType; 22 | private final Criterion criteria; 23 | private final Criterion groupCriterion; 24 | private final Orderings orderings; 25 | private final Integer maxResults; 26 | private final List returnFields; 27 | private final List projections; 28 | private final QueryOperationType queryOperationType; 29 | private final String queryHint; 30 | 31 | public QuerySelectImpl(Class entityClass, Class returnType, Criterion criteria, Criterion groupCriterion, Orderings orderings, 32 | Integer maxResults, List returnFields, List projections, QueryOperationType queryOperationType, String queryHint) { 33 | this.entityClass = entityClass; 34 | this.returnType = returnType; 35 | this.criteria = criteria; 36 | this.groupCriterion = groupCriterion; 37 | this.returnFields = returnFields; 38 | this.orderings = orderings; 39 | this.maxResults = maxResults; 40 | this.projections = projections; 41 | this.queryOperationType = queryOperationType; 42 | this.queryHint = queryHint; 43 | } 44 | 45 | @Override 46 | public QueryOperationType getQueryOperationType() { 47 | return queryOperationType; 48 | } 49 | 50 | @Override 51 | public String getQueryHint() { 52 | return queryHint; 53 | } 54 | 55 | /* 56 | * (non-Javadoc) 57 | * 58 | * @see com.eharmony.matching.seeking.query.Query#getEntityClass() 59 | */ 60 | @Override 61 | public Class getEntityClass() { 62 | return entityClass; 63 | } 64 | 65 | /* 66 | * (non-Javadoc) 67 | * 68 | * @see com.eharmony.matching.seeking.query.Query#getReturnType() 69 | */ 70 | @Override 71 | public Class getReturnType() { 72 | return returnType; 73 | } 74 | 75 | /* 76 | * (non-Javadoc) 77 | * 78 | * @see com.eharmony.matching.seeking.query.Query#getReturnFields() 79 | */ 80 | @Override 81 | public List getReturnFields() { 82 | return returnFields; 83 | } 84 | 85 | /* 86 | * (non-Javadoc) 87 | * 88 | * @see com.eharmony.matching.seeking.query.Query#getCriteria() 89 | */ 90 | @Override 91 | public Criterion getCriteria() { 92 | return criteria; 93 | } 94 | 95 | @Override 96 | public Criterion getGroupCriteria() { 97 | return groupCriterion; 98 | } 99 | 100 | /* 101 | * (non-Javadoc) 102 | * 103 | * @see com.eharmony.matching.seeking.query.Query#getOrder() 104 | */ 105 | @Override 106 | public Orderings getOrder() { 107 | return orderings; 108 | } 109 | 110 | @Override 111 | public List getProjection() { 112 | return this.projections; 113 | } 114 | 115 | /* 116 | * (non-Javadoc) 117 | * 118 | * @see com.eharmony.matching.seeking.query.Query#getMaxResults() 119 | */ 120 | @Override 121 | public Integer getMaxResults() { 122 | return maxResults; 123 | } 124 | 125 | /* 126 | * (non-Javadoc) 127 | * 128 | * @see java.lang.Object#toString() 129 | */ 130 | @Override 131 | public String toString() { 132 | return "QueryImpl [entityClass=" + entityClass + ", criteria=" + criteria + ", orderings=" + orderings 133 | + ", maxResults=" + maxResults + "]"; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/QueryUpdate.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query; 2 | 3 | import java.util.List; 4 | 5 | import com.eharmony.pho.query.criterion.Criterion; 6 | 7 | /** 8 | * A generic, object oriented representation of update query 9 | * 10 | * @param 11 | * the entity type being saved 12 | */ 13 | public interface QueryUpdate { 14 | 15 | /** 16 | * The list of properties to return. An empty collection means all properties. 17 | * 18 | * @return the fields to be updated 19 | */ 20 | public List getSelectedFields(); 21 | 22 | /** 23 | * Get the query criteria. The top level object can either be a single Criterion or a Junction of multiple nested 24 | * Criterion objects. 25 | * 26 | * @return the root criterion node 27 | */ 28 | public Criterion getCriteria(); 29 | 30 | /** 31 | * Getter method for Query Operation type for update 32 | * 33 | * @return QueryOperationType 34 | */ 35 | public QueryOperationType getQueryOperationType(); 36 | 37 | /** 38 | * Get the entity, which is set to persist 39 | * @return T 40 | */ 41 | public T getEntity(); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/QueryUpdateImpl.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query; 2 | 3 | import java.util.List; 4 | 5 | import com.eharmony.pho.query.criterion.Criterion; 6 | 7 | /** 8 | * The default implementation of the generic Query interface 9 | * 10 | * @param 11 | * the entity type being queried 12 | */ 13 | public class QueryUpdateImpl implements QueryUpdate { 14 | 15 | private final T entity; 16 | private Criterion criteria; 17 | private final List selectedFields; 18 | private final QueryOperationType queryOperationType; 19 | 20 | public QueryUpdateImpl(final T entity, Criterion criteria, List selectedFields, 21 | QueryOperationType queryOperationType) { 22 | this.entity = entity; 23 | this.criteria = criteria; 24 | this.selectedFields = selectedFields; 25 | this.queryOperationType = queryOperationType; 26 | } 27 | 28 | @Override 29 | public QueryOperationType getQueryOperationType() { 30 | return queryOperationType; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "QueryImpl [entityClass=" + entity.getClass() + ", criteria=" + criteria + "]"; 36 | } 37 | 38 | @Override 39 | public List getSelectedFields() { 40 | return this.selectedFields; 41 | } 42 | 43 | @Override 44 | public Criterion getCriteria() { 45 | return this.criteria; 46 | } 47 | 48 | @Override 49 | public T getEntity() { 50 | return this.entity; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/builder/QueryBuilder.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.builder; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | import com.eharmony.pho.query.QueryOperationType; 9 | import com.eharmony.pho.query.QuerySelect; 10 | import com.eharmony.pho.query.QuerySelectImpl; 11 | import com.eharmony.pho.query.criterion.*; 12 | import com.eharmony.pho.query.criterion.projection.AggregateProjection; 13 | import com.eharmony.pho.query.criterion.projection.Projection; 14 | 15 | /** 16 | * Builder for Query objects 17 | * 18 | * @param the entity type being queried 19 | * @param the desired return type 20 | */ 21 | public class QueryBuilder { 22 | 23 | private final Class entityClass; 24 | private final Class returnType; 25 | private List criteria = new ArrayList<>(); 26 | private List groupCriteria = new ArrayList<>(); 27 | private Orderings orderings = new Orderings(); 28 | private List projections = new ArrayList<>(); 29 | private Integer maxResults; 30 | private List returnFields = Collections.emptyList(); 31 | private QueryOperationType queryOperationType; 32 | private String queryHint; 33 | 34 | public QueryBuilder(Class entityClass, Class returnType) { 35 | this.entityClass = entityClass; 36 | this.returnType = returnType; 37 | } 38 | 39 | public static QueryBuilder builderFor(Class entityClass, Class returnType, 40 | String... returnFields) { 41 | return new QueryBuilder(entityClass, returnType).setReturnFields(returnFields); 42 | } 43 | 44 | public static QueryBuilder builderFor(Class entityClass) { 45 | return new QueryBuilder(entityClass, entityClass); 46 | } 47 | 48 | public QueryBuilder select() { 49 | queryOperationType = QueryOperationType.SELECT; 50 | return this; 51 | } 52 | 53 | public QueryBuilder select(List returnFields) { 54 | queryOperationType = QueryOperationType.SELECT; 55 | this.returnFields = returnFields; 56 | return this; 57 | } 58 | 59 | public QueryBuilder update() { 60 | queryOperationType = QueryOperationType.UPDATE; 61 | return this; 62 | } 63 | 64 | public QueryBuilder update(List selectedFields) { 65 | queryOperationType = QueryOperationType.UPDATE; 66 | this.returnFields = selectedFields; 67 | return this; 68 | } 69 | 70 | public QueryBuilder create() { 71 | queryOperationType = QueryOperationType.INSERT; 72 | return this; 73 | } 74 | 75 | public QueryBuilder create(List fields) { 76 | queryOperationType = QueryOperationType.INSERT; 77 | this.returnFields = fields; 78 | return this; 79 | } 80 | 81 | public QueryBuilder delete() { 82 | queryOperationType = QueryOperationType.DELETE; 83 | return this; 84 | } 85 | 86 | public QueryBuilder add(Criterion criterion) { 87 | criteria.add(criterion); 88 | return this; 89 | } 90 | 91 | /** 92 | * Add an ordering to the list of orderings 93 | * 94 | * @param orders the list of orderings to add 95 | * @return the builder 96 | */ 97 | public QueryBuilder addOrder(Ordering... orders) { 98 | 99 | return addOrder(Arrays.asList(orders)); 100 | 101 | } 102 | 103 | /** 104 | * Add an ordering to the list of orderings 105 | * 106 | * @param orders the list of orderings to add 107 | * @return the builder 108 | */ 109 | public QueryBuilder addOrder(Iterable orders) { 110 | for (Ordering order : orders) { 111 | orderings.add(order); 112 | } 113 | return this; 114 | } 115 | 116 | public QueryBuilder addProjection(Projection... projections) { 117 | this.projections.addAll(Arrays.asList(projections)); 118 | return this; 119 | } 120 | 121 | public QueryBuilder addGroupCriterion(Criterion groupCriterion) { 122 | groupCriteria.add(groupCriterion); 123 | return this; 124 | } 125 | 126 | public QueryBuilder setReturnFields(String... returnFields) { 127 | this.returnFields = Arrays.asList(returnFields); 128 | return this; 129 | } 130 | 131 | public QueryBuilder setMaxResults(int maxResults) { 132 | this.maxResults = maxResults; 133 | return this; 134 | } 135 | 136 | public QueryBuilder setQueryOperationType(QueryOperationType queryOperationType) { 137 | this.queryOperationType = queryOperationType; 138 | return this; 139 | } 140 | 141 | public QueryBuilder setQueryHint(String queryHint) { 142 | this.queryHint = queryHint; 143 | return this; 144 | } 145 | 146 | public QuerySelect build() { 147 | // if criteria.size == 0, rootCriterion = null 148 | Criterion rootCriterion = bindCriterion(this.criteria); 149 | Criterion groupCriterion = bindCriterion(this.groupCriteria); 150 | return new QuerySelectImpl(entityClass, returnType, rootCriterion, groupCriterion, orderings, maxResults, returnFields, 151 | projections, queryOperationType, queryHint); 152 | } 153 | 154 | private Criterion bindCriterion(List criteria) { 155 | Criterion criterion = null; 156 | if (criteria.size() == 1) { 157 | criterion = criteria.get(0); 158 | } else if (criteria.size() > 1) { 159 | criterion = Restrictions.and(criteria.toArray(new Criterion[criteria.size()])); 160 | } 161 | return criterion; 162 | } 163 | 164 | @Override 165 | public String toString() { 166 | return "QueryBuilder [entityClass=" + entityClass + ", criteria=" + criteria + ", orderings=" + orderings 167 | + ", maxResults=" + maxResults + "]"; 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/builder/QueryUpdateBuilder.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.builder; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | import com.eharmony.pho.query.QueryOperationType; 9 | import com.eharmony.pho.query.QueryUpdate; 10 | import com.eharmony.pho.query.QueryUpdateImpl; 11 | import com.eharmony.pho.query.criterion.Criterion; 12 | import com.eharmony.pho.query.criterion.Restrictions; 13 | 14 | /** 15 | * Builder for saving objects 16 | * 17 | * @param 18 | * the entity type being saved 19 | */ 20 | public class QueryUpdateBuilder { 21 | 22 | private final T entity; 23 | private List criteria = new ArrayList(); 24 | private List selectedFields = Collections.emptyList(); 25 | private QueryOperationType queryOperationType = QueryOperationType.UPDATE; 26 | 27 | public QueryUpdateBuilder(final T entity) { 28 | this.entity = entity; 29 | } 30 | 31 | public static QueryUpdateBuilder builderFor(final T entity) { 32 | return new QueryUpdateBuilder(entity); 33 | } 34 | 35 | public QueryUpdateBuilder create() { 36 | queryOperationType = QueryOperationType.INSERT; 37 | return this; 38 | } 39 | 40 | public QueryUpdateBuilder create(List selectedFields) { 41 | queryOperationType = QueryOperationType.INSERT; 42 | this.selectedFields = selectedFields; 43 | return this; 44 | } 45 | 46 | public QueryUpdateBuilder update() { 47 | queryOperationType = QueryOperationType.UPDATE; 48 | return this; 49 | } 50 | 51 | public QueryUpdateBuilder update(List selectedFields) { 52 | queryOperationType = QueryOperationType.UPDATE; 53 | this.selectedFields = selectedFields; 54 | return this; 55 | } 56 | 57 | public QueryUpdateBuilder add(Criterion criterion) { 58 | criteria.add(criterion); 59 | return this; 60 | } 61 | 62 | public QueryUpdateBuilder setSelectedFields(String... selectedFields) { 63 | this.selectedFields = Arrays.asList(selectedFields); 64 | return this; 65 | } 66 | 67 | public QueryUpdate build() { 68 | // if criteria.size == 0, rootCriterion = null 69 | Criterion rootCriterion = null; 70 | if (criteria.size() == 1) { 71 | rootCriterion = criteria.get(0); 72 | } else if (criteria.size() > 1) { 73 | rootCriterion = Restrictions.and(criteria.toArray(new Criterion[criteria.size()])); 74 | } 75 | return new QueryUpdateImpl(entity, rootCriterion, selectedFields, queryOperationType); 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return "QueryBuilder [entityClass=" + entity.getClass() + ", criteria=" + criteria + ", selectedFields=" 81 | + selectedFields + "]"; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/Aggregate.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | 5 | /** 6 | * Query operator type enumeration 7 | */ 8 | public enum Aggregate implements Symbolic { 9 | 10 | GROUP_BY("group by"), 11 | COUNT("count"), 12 | AVG("avg"), 13 | SUM("sum"), 14 | MAX("max"), 15 | MIN("min"); 16 | 17 | private final String symbol; 18 | private static final ImmutableMap map = SymbolicLookup.map(values()); 19 | 20 | private Aggregate(String symbol) { 21 | this.symbol = symbol; 22 | } 23 | 24 | @Override 25 | public String symbol() { 26 | return symbol; 27 | } 28 | 29 | public static Aggregate fromString(String val) { 30 | return SymbolicLookup.resolve(val, map, Aggregate.class); 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return symbol(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/Criterion.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion; 2 | 3 | /** 4 | * Enclosing type for all Query criterion components 5 | */ 6 | public interface Criterion { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/GroupCriterion.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion; 2 | 3 | /** 4 | * Enclosing type for all Query criterion components 5 | */ 6 | public interface GroupCriterion extends Criterion{ 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/GroupRestrictions.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion; 2 | 3 | import com.eharmony.pho.query.criterion.expression.*; 4 | import com.eharmony.pho.query.criterion.junction.Conjunction; 5 | import com.eharmony.pho.query.criterion.junction.Disjunction; 6 | import com.eharmony.pho.query.criterion.projection.AggregateProjection; 7 | 8 | import java.util.Collection; 9 | 10 | /** 11 | * Hibernate style Restriction expression builder 12 | */ 13 | public class GroupRestrictions { 14 | 15 | private GroupRestrictions() { 16 | } 17 | 18 | public static EqualityExpression eq(AggregateProjection projection, Object value) { 19 | return new EqualityExpression(Operator.EQUAL, projection.getPropertyName(), value, projection); 20 | } 21 | 22 | public static EqualityExpression ne(AggregateProjection projection, Object value) { 23 | return new EqualityExpression(Operator.NOT_EQUAL, projection.getPropertyName(), value); 24 | } 25 | 26 | public static EqualityExpression lt(AggregateProjection projection, Object value) { 27 | return new EqualityExpression(Operator.LESS_THAN, projection.getPropertyName(), value); 28 | } 29 | 30 | public static EqualityExpression like(AggregateProjection projection, Object value) { 31 | return new EqualityExpression(Operator.LIKE, projection.getPropertyName(), value); 32 | } 33 | 34 | public static EqualityExpression ilike(AggregateProjection projection, Object value) { 35 | return new EqualityExpression(Operator.ILIKE, projection.getPropertyName(), value); 36 | } 37 | 38 | public static EqualityExpression lte(AggregateProjection projection, Object value) { 39 | return new EqualityExpression(Operator.LESS_THAN_OR_EQUAL, projection.getPropertyName(), value); 40 | } 41 | 42 | public static EqualityExpression gt(AggregateProjection projection, Object value) { 43 | return new EqualityExpression(Operator.GREATER_THAN, projection.getPropertyName(), value); 44 | } 45 | 46 | public static EqualityExpression gte(AggregateProjection projection, Object value) { 47 | return new EqualityExpression(Operator.GREATER_THAN_OR_EQUAL, projection.getPropertyName(), value); 48 | } 49 | 50 | public static RangeExpression between(AggregateProjection projection, Object from, Object to) { 51 | return new RangeExpression(Operator.BETWEEN, projection.getPropertyName(), from, to); 52 | } 53 | 54 | public static SetExpression discreteRange(AggregateProjection projection, int from, int to) { 55 | return new SetExpression(Operator.IN, projection.getPropertyName(), from <= to ? Restrictions.range(from, to) : Restrictions.range(to, from)); 56 | } 57 | 58 | public static SetExpression in(AggregateProjection projection, Object[] values) { 59 | return new SetExpression(Operator.IN, projection.getPropertyName(), values); 60 | } 61 | 62 | public static SetExpression in(AggregateProjection projection, Collection values) { 63 | return in(projection, values.toArray()); 64 | } 65 | 66 | public static SetExpression notIn(AggregateProjection projection, Object[] values) { 67 | return new SetExpression(Operator.NOT_IN, projection.getPropertyName(), values); 68 | } 69 | 70 | public static SetExpression notIn(AggregateProjection projection, Collection values) { 71 | return notIn(projection, values.toArray()); 72 | } 73 | 74 | public static UnaryExpression isNull(AggregateProjection projection) { 75 | return new UnaryExpression(Operator.NULL, projection.getPropertyName()); 76 | } 77 | 78 | public static UnaryExpression isNotNull(AggregateProjection projection) { 79 | return new UnaryExpression(Operator.NOT_NULL, projection.getPropertyName()); 80 | } 81 | 82 | public static UnaryExpression isEmpty(AggregateProjection projection) { 83 | return new UnaryExpression(Operator.EMPTY, projection.getPropertyName()); 84 | } 85 | 86 | public static UnaryExpression isNotEmpty(AggregateProjection projection) { 87 | return new UnaryExpression(Operator.NOT_EMPTY, projection.getPropertyName()); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/Operator.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | 5 | /** 6 | * Query operator type enumeration 7 | */ 8 | public enum Operator implements Symbolic { 9 | EQUAL("="), NOT_EQUAL("!="), GREATER_THAN(">"), GREATER_THAN_OR_EQUAL(">="), LESS_THAN("<"), LESS_THAN_OR_EQUAL( 10 | "<="), 11 | 12 | BETWEEN("between"), 13 | 14 | LIKE("like"), ILIKE("ilike"), 15 | 16 | NULL("null"), NOT_NULL("not null"), EMPTY("empty"), NOT_EMPTY("not empty"), 17 | 18 | IN("in"), NOT_IN("not in"), CONTAINS("contains"), 19 | 20 | WITHIN("within"), 21 | 22 | AND("and"), OR("or"); 23 | 24 | private final String symbol; 25 | private static final ImmutableMap map = SymbolicLookup.map(values()); 26 | 27 | private Operator(String symbol) { 28 | this.symbol = symbol; 29 | } 30 | 31 | @Override 32 | public String symbol() { 33 | return symbol; 34 | } 35 | 36 | public static Operator fromString(String val) { 37 | return SymbolicLookup.resolve(val, map, Operator.class); 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return symbol(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/Ordering.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion; 2 | 3 | 4 | /** 5 | * Ordering criterion 6 | * This defines the order that results will be returned in. 7 | */ 8 | public class Ordering implements Criterion, WithProperty { 9 | 10 | public static enum Order { 11 | ASCENDING, DESCENDING 12 | } 13 | 14 | /** 15 | * When property name is null should those values be put first or last 16 | */ 17 | public static enum NullOrdering { 18 | FIRST, LAST 19 | } 20 | 21 | private final String propertyName; 22 | private final Order order; 23 | private final NullOrdering nullOrdering; 24 | 25 | /** 26 | * Create an ordering to order results by the given property in ascending order with nulls first 27 | * @param propertyName the name of the property to order by 28 | * @return the ordering 29 | */ 30 | public static Ordering asc(String propertyName) { 31 | return new Ordering(propertyName, Order.ASCENDING, NullOrdering.FIRST); 32 | } 33 | 34 | /** 35 | * Create an ordering to order results by the given property in descending order with nulls first 36 | * @param propertyName the name of the property to order by 37 | * @return the ordering 38 | */ 39 | public static Ordering desc(String propertyName) { 40 | return new Ordering(propertyName, Order.DESCENDING, NullOrdering.FIRST); 41 | } 42 | 43 | /** 44 | * Create an ordering to order results by the given property in ascending order with nulls first 45 | * @param propertyName the name of the property to order by 46 | * @param nullOrdering should nulls be first or last 47 | * @return the ordering 48 | */ 49 | public static Ordering asc(String propertyName, NullOrdering nullOrdering) { 50 | return new Ordering(propertyName, Order.ASCENDING, nullOrdering); 51 | } 52 | 53 | /** 54 | * Create an ordering to order results by the given property in descending order with nulls first 55 | * @param propertyName the name of the property to order by 56 | * @param nullOrdering should nulls be first or last 57 | * @return the ordering 58 | */ 59 | public static Ordering desc(String propertyName, NullOrdering nullOrdering) { 60 | return new Ordering(propertyName, Order.DESCENDING, nullOrdering); 61 | } 62 | 63 | /** 64 | * Create an ordering 65 | * @param propertyName the property to order by 66 | * @param order the order that should be applied (ascending or descending) 67 | * @param nullOrdering whether nulls should be first or last 68 | */ 69 | public Ordering(String propertyName, Order order, NullOrdering nullOrdering) { 70 | this.propertyName = propertyName; 71 | this.order = order; 72 | this.nullOrdering = nullOrdering; 73 | } 74 | 75 | @Override 76 | public String getPropertyName() { 77 | return propertyName; 78 | } 79 | 80 | /** 81 | * Get the order to return the sorted items in 82 | * @return ascending or descending 83 | */ 84 | public Order getOrder() { 85 | return order; 86 | } 87 | 88 | /** 89 | * Get the ordering of nulls 90 | * @return whether nulls should be first or last 91 | */ 92 | public NullOrdering getNullOrdering() { 93 | 94 | return nullOrdering; 95 | 96 | } 97 | 98 | @Override 99 | public int hashCode() { 100 | final int prime = 31; 101 | int result = 1; 102 | result = prime * result + ((order == null) ? 0 : order.hashCode()); 103 | result = prime * result + ((propertyName == null) ? 0 : propertyName.hashCode()); 104 | return result; 105 | } 106 | 107 | @Override 108 | public boolean equals(Object obj) { 109 | if (this == obj) 110 | return true; 111 | if (obj == null) 112 | return false; 113 | if (getClass() != obj.getClass()) 114 | return false; 115 | Ordering other = (Ordering) obj; 116 | if (order != other.order) 117 | return false; 118 | if (propertyName == null) { 119 | if (other.propertyName != null) 120 | return false; 121 | } else if (!propertyName.equals(other.propertyName)) 122 | return false; 123 | return true; 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/Orderings.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | /** 8 | * A List of Ordering criteria. 9 | * 10 | * Encapsulated for type safety and to combat type erasure. 11 | */ 12 | public class Orderings { 13 | 14 | private final List orderings = new ArrayList(); 15 | 16 | public Orderings() { 17 | } 18 | 19 | public Orderings(Ordering... orderings) { 20 | this.orderings.addAll(Arrays.asList(orderings)); 21 | } 22 | 23 | public void add(Ordering ordering) { 24 | orderings.add(ordering); 25 | } 26 | 27 | // Discuss: defensive copy? 28 | public List get() { 29 | return orderings; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "Orderings [" + orderings + "]"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/Projections.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion; 2 | 3 | import com.eharmony.pho.query.criterion.projection.*; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public class Projections { 10 | 11 | private Projections() { 12 | } 13 | 14 | public static GroupProjection groupBy( String... propertyNames ) { 15 | return new GroupProjection( propertyNames ); 16 | } 17 | 18 | public static CountProjection count( String propertyName ) { 19 | return new CountProjection( propertyName ); 20 | } 21 | 22 | public static MaxProjection max( String propertyName ) { 23 | return new MaxProjection( propertyName ); 24 | } 25 | 26 | public static MinProjection min( String propertyName ) { 27 | return new MinProjection( propertyName ); 28 | } 29 | 30 | public static AvgProjection avg( String propertyName ) { 31 | return new AvgProjection( propertyName ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/Restrictions.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion; 2 | 3 | import java.util.Collection; 4 | 5 | import com.eharmony.pho.query.criterion.expression.EqualityExpression; 6 | import com.eharmony.pho.query.criterion.expression.NativeExpression; 7 | import com.eharmony.pho.query.criterion.expression.RangeExpression; 8 | import com.eharmony.pho.query.criterion.expression.SetExpression; 9 | import com.eharmony.pho.query.criterion.expression.UnaryExpression; 10 | import com.eharmony.pho.query.criterion.junction.Conjunction; 11 | import com.eharmony.pho.query.criterion.junction.Disjunction; 12 | 13 | /** 14 | * Hibernate style Restriction expression builder 15 | */ 16 | public class Restrictions { 17 | 18 | private Restrictions() { 19 | } 20 | 21 | /** 22 | * Apply an "equal" constraint to the named property 23 | * 24 | * @param propertyName 25 | * String 26 | * @param value 27 | * Object 28 | * @return Criterion 29 | */ 30 | public static EqualityExpression eq(String propertyName, Object value) { 31 | return new EqualityExpression(Operator.EQUAL, propertyName, value); 32 | } 33 | 34 | /** 35 | * Apply a "not equal" constraint to the named property 36 | * 37 | * @param propertyName 38 | * String 39 | * @param value 40 | * Object 41 | * @return Criterion 42 | */ 43 | public static EqualityExpression ne(String propertyName, Object value) { 44 | return new EqualityExpression(Operator.NOT_EQUAL, propertyName, value); 45 | } 46 | 47 | /** 48 | * Apply a "less than" constraint to the named property 49 | * 50 | * @param propertyName 51 | * String 52 | * @param value 53 | * Object 54 | * @return Criterion 55 | */ 56 | public static EqualityExpression lt(String propertyName, Object value) { 57 | return new EqualityExpression(Operator.LESS_THAN, propertyName, value); 58 | } 59 | 60 | /** 61 | * Apply a "like" constraint to the named property 62 | * 63 | * @param propertyName 64 | * String 65 | * @param value 66 | * Object 67 | * @return Criterion 68 | */ 69 | public static EqualityExpression like(String propertyName, Object value) { 70 | return new EqualityExpression(Operator.LIKE, propertyName, value); 71 | } 72 | 73 | /** 74 | * Apply a "ilike" (case insensitive like) constraint to the named property 75 | * 76 | * @param propertyName 77 | * String 78 | * @param value 79 | * Object 80 | * @return Criterion 81 | */ 82 | public static EqualityExpression ilike(String propertyName, Object value) { 83 | return new EqualityExpression(Operator.ILIKE, propertyName, value); 84 | } 85 | 86 | /** 87 | * Apply a "less than or equal" constraint to the named property 88 | * 89 | * @param propertyName 90 | * String 91 | * @param value 92 | * Object 93 | * @return Criterion 94 | */ 95 | public static EqualityExpression lte(String propertyName, Object value) { 96 | return new EqualityExpression(Operator.LESS_THAN_OR_EQUAL, propertyName, value); 97 | } 98 | 99 | /** 100 | * Apply a "greater than" constraint to the named property 101 | * 102 | * @param propertyName 103 | * String 104 | * @param value 105 | * Object 106 | * @return Criterion 107 | */ 108 | public static EqualityExpression gt(String propertyName, Object value) { 109 | return new EqualityExpression(Operator.GREATER_THAN, propertyName, value); 110 | } 111 | 112 | /** 113 | * Apply a "greater than or equal" constraint to the named property 114 | * 115 | * @param propertyName 116 | * String 117 | * @param value 118 | * Object 119 | * @return Criterion 120 | */ 121 | public static EqualityExpression gte(String propertyName, Object value) { 122 | return new EqualityExpression(Operator.GREATER_THAN_OR_EQUAL, propertyName, value); 123 | } 124 | 125 | /** 126 | * Apply a "between" constraint to the named property 127 | * 128 | * @param propertyName 129 | * String 130 | * @param from 131 | * Object 132 | * @param to 133 | * Object 134 | * @return Criterion 135 | */ 136 | public static RangeExpression between(String propertyName, Object from, Object to) { 137 | return new RangeExpression(Operator.BETWEEN, propertyName, from, to); 138 | } 139 | 140 | /** 141 | * Apply a "between" constraint to the named integer property with a finite, discrete number of values. This is 142 | * translated into an inclusive "in" expression. 143 | * 144 | * @param propertyName 145 | * String 146 | * @param from 147 | * Object 148 | * @param to 149 | * Object 150 | * @return Criterion 151 | */ 152 | public static SetExpression discreteRange(String propertyName, int from, int to) { 153 | return new SetExpression(Operator.IN, propertyName, from <= to ? range(from, to) : range(to, from)); 154 | } 155 | 156 | /* 157 | * NOTE: this yields an Integer[], not an int[] to conform with the Object[] signature of the Expressions. Give 158 | * that, nulls are still unacceptable. 159 | */ 160 | protected static Integer[] range(int from, int to) { 161 | if (from > to) { 162 | throw new IllegalArgumentException("from must be <= to (" + from + "," + to + ")"); 163 | } 164 | int n = to - from + 1; 165 | Integer[] range = new Integer[n]; 166 | for (int i = 0; i < n; i++) { 167 | range[i] = from + i; 168 | } 169 | return range; 170 | } 171 | 172 | /** 173 | * Apply an "in" constraint to the named property 174 | * 175 | * @param propertyName 176 | * String 177 | * @param values 178 | * Object[] 179 | * @return Criterion 180 | */ 181 | public static SetExpression in(String propertyName, Object[] values) { 182 | return new SetExpression(Operator.IN, propertyName, values); 183 | } 184 | 185 | /** 186 | * Apply an "in" constraint to the named property 187 | * 188 | * @param propertyName 189 | * String 190 | * @param values 191 | * Collection 192 | * @return Criterion 193 | */ 194 | public static SetExpression in(String propertyName, Collection values) { 195 | return in(propertyName, values.toArray()); 196 | } 197 | 198 | /** 199 | * Apply a "not in" constraint to the named property 200 | * 201 | * @param propertyName 202 | * String 203 | * @param values 204 | * Object[] 205 | * @return Criterion 206 | */ 207 | public static SetExpression notIn(String propertyName, Object[] values) { 208 | return new SetExpression(Operator.NOT_IN, propertyName, values); 209 | } 210 | 211 | /** 212 | * Apply a "not in" constraint to the named property 213 | * 214 | * @param propertyName 215 | * String 216 | * @param values 217 | * Collection 218 | * @return Criterion 219 | */ 220 | public static SetExpression notIn(String propertyName, Collection values) { 221 | return notIn(propertyName, values.toArray()); 222 | } 223 | 224 | /** 225 | * Apply a "contains" constraint to the named property 226 | * 227 | * @param propertyName 228 | * String 229 | * @param value 230 | * Object 231 | * @return SetExpression 232 | */ 233 | public static SetExpression contains(String propertyName, Object value) { 234 | return contains(propertyName, new Object[] { value }); 235 | } 236 | 237 | /** 238 | * Apply a "contains" constraint to the named property 239 | * 240 | * @param propertyName 241 | * String 242 | * @param values 243 | * Object[] 244 | * @return Criterion 245 | */ 246 | public static SetExpression contains(String propertyName, Object[] values) { 247 | return new SetExpression(Operator.CONTAINS, propertyName, values); 248 | } 249 | 250 | /** 251 | * Apply an "is null" constraint to the named property 252 | * 253 | * @param propertyName 254 | * String 255 | * @return Criterion 256 | */ 257 | public static UnaryExpression isNull(String propertyName) { 258 | return new UnaryExpression(Operator.NULL, propertyName); 259 | } 260 | 261 | /** 262 | * Apply an "is not null" constraint to the named property 263 | * 264 | * @param propertyName 265 | * String 266 | * @return Criterion 267 | */ 268 | public static UnaryExpression isNotNull(String propertyName) { 269 | return new UnaryExpression(Operator.NOT_NULL, propertyName); 270 | } 271 | 272 | /** 273 | * Constrain a collection valued property to be empty 274 | * 275 | * @param propertyName 276 | * String 277 | * @return UnaryExpression 278 | * 279 | */ 280 | public static UnaryExpression isEmpty(String propertyName) { 281 | return new UnaryExpression(Operator.EMPTY, propertyName); 282 | } 283 | 284 | public static UnaryExpression isNotEmpty(String propertyName) { 285 | return new UnaryExpression(Operator.NOT_EMPTY, propertyName); 286 | } 287 | 288 | public static NativeExpression nativeQuery(Class type, T expression) { 289 | return new NativeExpression(type, expression); 290 | } 291 | 292 | /** 293 | * Return the conjunction of two expressions 294 | * 295 | * @param criteria 296 | * Criterion 297 | * @return Conjunction 298 | */ 299 | public static Conjunction and(Criterion... criteria) { 300 | return new Conjunction(criteria); 301 | } 302 | 303 | /** 304 | * Return the disjunction of two expressions 305 | * 306 | * @param criteria 307 | * Criterion 308 | * @return Disjunction 309 | */ 310 | public static Disjunction or(Criterion... criteria) { 311 | return new Disjunction(criteria); 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/Symbolic.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion; 2 | 3 | /** 4 | * Definition of a type that can be represented with a string symbol 5 | */ 6 | public interface Symbolic { 7 | 8 | public String symbol(); 9 | 10 | } -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/SymbolicLookup.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion; 2 | 3 | import java.util.Arrays; 4 | import com.google.common.base.Function; 5 | import com.google.common.collect.ImmutableMap; 6 | import com.google.common.collect.Maps; 7 | 8 | /** 9 | * A tool for resolving a Symbolic object from its String symbol 10 | */ 11 | public class SymbolicLookup implements Function { 12 | 13 | @Override 14 | public String apply(Symbolic symbolic) { 15 | return symbolic.symbol(); 16 | } 17 | 18 | public static ImmutableMap map(T[] values) { 19 | return Maps.uniqueIndex(Arrays.asList(values), new SymbolicLookup()); 20 | } 21 | 22 | public static T resolve(String symbol, ImmutableMap map, Class clss) { 23 | T symbolic = map.get(symbol); 24 | if (symbolic == null) { 25 | throw new IllegalArgumentException(symbol + " is not a valid " + clss.getSimpleName() + " symbol"); 26 | } 27 | return symbolic; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/WithAggregateFunction.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion; 2 | 3 | /** 4 | * Definition of a type that has an Operator 5 | */ 6 | public interface WithAggregateFunction { 7 | 8 | public Aggregate getAggregate(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/WithOperator.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion; 2 | 3 | /** 4 | * Definition of a type that has an Operator 5 | */ 6 | public interface WithOperator { 7 | 8 | public Operator getOperator(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/WithProperty.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion; 2 | 3 | /** 4 | * Definition of a type that has a property name 5 | */ 6 | public interface WithProperty { 7 | 8 | public String getPropertyName(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/expression/EqualityExpression.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion.expression; 2 | 3 | import com.eharmony.pho.query.criterion.Aggregate; 4 | import com.eharmony.pho.query.criterion.Operator; 5 | import com.eharmony.pho.query.criterion.projection.AggregateProjection; 6 | 7 | /** 8 | * An equality expression comparing the property against the value with the 9 | * operator. 10 | */ 11 | public class EqualityExpression extends Expression { 12 | 13 | private final Object value; 14 | 15 | public EqualityExpression(Operator operator, String propertyName, Object value) { 16 | super(operator, propertyName); 17 | this.value = value; 18 | } 19 | 20 | public EqualityExpression(Operator operator, String propertyName, Object value, AggregateProjection aggregateProjection) { 21 | super(operator, propertyName, aggregateProjection); 22 | this.value = value; 23 | } 24 | 25 | public Object getValue() { 26 | return value; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return getPropertyName() + " " + getOperator() + " " + value; 32 | } 33 | 34 | @Override 35 | public int hashCode() { 36 | final int prime = 31; 37 | int result = super.hashCode(); 38 | result = prime * result + ((value == null) ? 0 : value.hashCode()); 39 | return result; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object obj) { 44 | if (this == obj) 45 | return true; 46 | if (!super.equals(obj)) 47 | return false; 48 | if (getClass() != obj.getClass()) 49 | return false; 50 | EqualityExpression other = (EqualityExpression) obj; 51 | if (value == null) { 52 | if (other.value != null) 53 | return false; 54 | } else if (!value.equals(other.value)) 55 | return false; 56 | return true; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/expression/Expression.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion.expression; 2 | 3 | import com.eharmony.pho.query.criterion.Criterion; 4 | import com.eharmony.pho.query.criterion.Operator; 5 | import com.eharmony.pho.query.criterion.WithOperator; 6 | import com.eharmony.pho.query.criterion.WithProperty; 7 | import com.eharmony.pho.query.criterion.projection.AggregateProjection; 8 | 9 | /** 10 | * An abstract expression with an operator that acts on a property name. 11 | */ 12 | public abstract class Expression implements Criterion, WithOperator, WithProperty { 13 | 14 | private final Operator operator; 15 | private final String propertyName; 16 | private final AggregateProjection aggregateProjection; 17 | 18 | protected Expression(Operator operator, String propertyName, AggregateProjection aggregateProjection) { 19 | this.operator = operator; 20 | this.propertyName = propertyName; 21 | this.aggregateProjection = aggregateProjection; 22 | } 23 | 24 | protected Expression(Operator operator, String propertyName) { 25 | this.operator = operator; 26 | this.propertyName = propertyName; 27 | this.aggregateProjection = null; 28 | } 29 | 30 | @Override 31 | public Operator getOperator() { 32 | return operator; 33 | } 34 | 35 | @Override 36 | public String getPropertyName() { 37 | return propertyName; 38 | } 39 | 40 | public AggregateProjection getAggregateProjection() { 41 | return aggregateProjection; 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | final int prime = 31; 47 | int result = 1; 48 | result = prime * result 49 | + ((operator == null) ? 0 : operator.hashCode()); 50 | result = prime * result 51 | + ((propertyName == null) ? 0 : propertyName.hashCode()); 52 | result = prime * result 53 | + ((aggregateProjection == null) ? 0 : aggregateProjection.hashCode()); 54 | return result; 55 | } 56 | 57 | @Override 58 | public boolean equals(Object obj) { 59 | if (this == obj) 60 | return true; 61 | if (obj == null) 62 | return false; 63 | if (getClass() != obj.getClass()) 64 | return false; 65 | Expression other = (Expression) obj; 66 | if (operator != other.operator) 67 | return false; 68 | if (propertyName == null) { 69 | if (other.propertyName != null) 70 | return false; 71 | } else if (!propertyName.equals(other.propertyName)) 72 | return false; 73 | if (aggregateProjection == null) { 74 | if (other.aggregateProjection != null) 75 | return false; 76 | } else if (!aggregateProjection.equals(other.aggregateProjection)) 77 | return false; 78 | return true; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/expression/NativeExpression.java: -------------------------------------------------------------------------------- 1 | 2 | package com.eharmony.pho.query.criterion.expression; 3 | 4 | import com.eharmony.pho.query.criterion.Criterion; 5 | 6 | /** 7 | * A typed, native datastore query component. 8 | */ 9 | public class NativeExpression implements Criterion { 10 | 11 | private final Class expressionClass; 12 | private final Object expression; 13 | 14 | public NativeExpression(Class expressionClass, T expression) { 15 | this.expressionClass = expressionClass; 16 | this.expression = expression; 17 | } 18 | 19 | public Class getExpressionClass() { 20 | return expressionClass; 21 | } 22 | 23 | public Object getExpression() { 24 | return expression; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return "NativeExpression [" + expression + "]"; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | final int prime = 31; 35 | int result = 1; 36 | result = prime * result 37 | + ((expression == null) ? 0 : expression.hashCode()); 38 | result = prime * result 39 | + ((expressionClass == null) ? 0 : expressionClass.hashCode()); 40 | return result; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object obj) { 45 | if (this == obj) 46 | return true; 47 | if (obj == null) 48 | return false; 49 | if (getClass() != obj.getClass()) 50 | return false; 51 | NativeExpression other = (NativeExpression) obj; 52 | if (expression == null) { 53 | if (other.expression != null) 54 | return false; 55 | } else if (!expression.equals(other.expression)) 56 | return false; 57 | if (expressionClass == null) { 58 | if (other.expressionClass != null) 59 | return false; 60 | } else if (!expressionClass.equals(other.expressionClass)) 61 | return false; 62 | return true; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/expression/RangeExpression.java: -------------------------------------------------------------------------------- 1 | 2 | package com.eharmony.pho.query.criterion.expression; 3 | 4 | import com.eharmony.pho.query.criterion.Operator; 5 | 6 | /** 7 | * A closed range expression. 8 | */ 9 | public class RangeExpression extends Expression { 10 | 11 | private final Object from; 12 | private final Object to; 13 | 14 | public RangeExpression(Operator operator, String propertyName, Object from, 15 | Object to) { 16 | super(operator, propertyName); 17 | this.from = from; 18 | this.to = to; 19 | } 20 | 21 | public Object getFrom() { 22 | return from; 23 | } 24 | 25 | public Object getTo() { 26 | return to; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return getPropertyName() + " " + getOperator() + " " + from + "," + to; 32 | } 33 | 34 | @Override 35 | public int hashCode() { 36 | final int prime = 31; 37 | int result = super.hashCode(); 38 | result = prime * result + ((from == null) ? 0 : from.hashCode()); 39 | result = prime * result + ((to == null) ? 0 : to.hashCode()); 40 | return result; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object obj) { 45 | if (this == obj) 46 | return true; 47 | if (!super.equals(obj)) 48 | return false; 49 | if (getClass() != obj.getClass()) 50 | return false; 51 | RangeExpression other = (RangeExpression) obj; 52 | if (from == null) { 53 | if (other.from != null) 54 | return false; 55 | } else if (!from.equals(other.from)) 56 | return false; 57 | if (to == null) { 58 | if (other.to != null) 59 | return false; 60 | } else if (!to.equals(other.to)) 61 | return false; 62 | return true; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/expression/SetExpression.java: -------------------------------------------------------------------------------- 1 | 2 | package com.eharmony.pho.query.criterion.expression; 3 | 4 | import java.util.Arrays; 5 | 6 | import com.eharmony.pho.query.criterion.Operator; 7 | import com.google.common.base.Joiner; 8 | 9 | /** 10 | * A set expression (in, contains, etc.). 11 | */ 12 | public class SetExpression extends Expression { 13 | 14 | private final Object[] values; 15 | 16 | public SetExpression(Operator operator, String propertyName, 17 | final Object[] values) { 18 | super(operator, propertyName); 19 | this.values = values; 20 | } 21 | 22 | public Object[] getValues() { 23 | return values; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return getPropertyName() + " " + getOperator() + " [" 29 | + Joiner.on(',').join(values) + "]"; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | final int prime = 31; 35 | int result = super.hashCode(); 36 | result = prime * result + Arrays.hashCode(values); 37 | return result; 38 | } 39 | 40 | @Override 41 | public boolean equals(Object obj) { 42 | if (this == obj) 43 | return true; 44 | if (!super.equals(obj)) 45 | return false; 46 | if (getClass() != obj.getClass()) 47 | return false; 48 | SetExpression other = (SetExpression) obj; 49 | if (!Arrays.equals(values, other.values)) 50 | return false; 51 | return true; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/expression/UnaryExpression.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion.expression; 2 | 3 | import com.eharmony.pho.query.criterion.Operator; 4 | 5 | /** 6 | * A unary expression (is null, not null, is empty, not empty) 7 | */ 8 | public class UnaryExpression extends Expression { 9 | 10 | public UnaryExpression(Operator operator, String propertyName) { 11 | super(operator, propertyName); 12 | } 13 | 14 | @Override 15 | public String toString() { 16 | return getPropertyName() + " " + getOperator(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/junction/Conjunction.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion.junction; 2 | 3 | import com.eharmony.pho.query.criterion.Criterion; 4 | import com.eharmony.pho.query.criterion.Operator; 5 | 6 | /** 7 | * And 8 | */ 9 | public class Conjunction extends Junction { 10 | 11 | public Conjunction() { 12 | super(Operator.AND); 13 | } 14 | 15 | public Conjunction(Criterion... criteria) { 16 | this(); 17 | addAll(criteria); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/junction/Disjunction.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion.junction; 2 | 3 | import com.eharmony.pho.query.criterion.Criterion; 4 | import com.eharmony.pho.query.criterion.Operator; 5 | 6 | /** 7 | * Or 8 | */ 9 | public class Disjunction extends Junction { 10 | 11 | public Disjunction() { 12 | super(Operator.OR); 13 | } 14 | 15 | public Disjunction(Criterion... criteria) { 16 | this(); 17 | addAll(criteria); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/junction/Junction.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion.junction; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.eharmony.pho.query.criterion.Criterion; 7 | import com.eharmony.pho.query.criterion.Operator; 8 | import com.eharmony.pho.query.criterion.WithOperator; 9 | import com.google.common.base.Joiner; 10 | 11 | /** 12 | * and / or 13 | */ 14 | public abstract class Junction implements Criterion, WithOperator { 15 | 16 | private final List criteria = new ArrayList(); 17 | private final Operator operator; 18 | 19 | protected Junction(Operator operator) { 20 | this.operator = operator; 21 | } 22 | 23 | public Junction add(Criterion criterion) { 24 | criteria.add(criterion); 25 | return this; 26 | } 27 | 28 | public Junction addAll(Criterion... criterions) { 29 | for (Criterion criterion : criterions) { 30 | if (criterion != null) { 31 | criteria.add(criterion); 32 | } 33 | } 34 | return this; 35 | } 36 | 37 | @Override 38 | public Operator getOperator() { 39 | return operator; 40 | } 41 | 42 | public List getCriteria() { 43 | // TODO : immutable list? 44 | return new ArrayList(criteria); 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return "(" + Joiner.on(") " + operator.symbol() + " (").join(criteria) + ")"; 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | final int prime = 31; 55 | int result = 1; 56 | result = prime * result + ((criteria == null) ? 0 : criteria.hashCode()); 57 | result = prime * result + ((operator == null) ? 0 : operator.hashCode()); 58 | return result; 59 | } 60 | 61 | @Override 62 | public boolean equals(Object obj) { 63 | if (this == obj) 64 | return true; 65 | if (obj == null) 66 | return false; 67 | if (getClass() != obj.getClass()) 68 | return false; 69 | Junction other = (Junction) obj; 70 | if (criteria == null) { 71 | if (other.criteria != null) 72 | return false; 73 | } else if (!criteria.equals(other.criteria)) 74 | return false; 75 | if (operator != other.operator) 76 | return false; 77 | return true; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/projection/AggregateProjection.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion.projection; 2 | 3 | import com.eharmony.pho.query.criterion.Aggregate; 4 | 5 | public class AggregateProjection extends Projection { 6 | private String propertyName; 7 | private Aggregate function; 8 | 9 | protected AggregateProjection(Aggregate function, String propertyName) { 10 | super(function, propertyName); 11 | this.propertyName = propertyName; 12 | this.function = function; 13 | } 14 | 15 | public String getPropertyName(){ 16 | return propertyName; 17 | } 18 | 19 | public AggregateProjection setPropertyName(String propertyName) { 20 | this.propertyName = propertyName; 21 | return this; 22 | } 23 | 24 | public String getName() { 25 | return function.symbol() + "(" + propertyName + ")"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/projection/AvgProjection.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion.projection; 2 | 3 | import com.eharmony.pho.query.criterion.Aggregate; 4 | 5 | public class AvgProjection extends AggregateProjection{ 6 | public AvgProjection(String propertyName) { 7 | super(Aggregate.AVG, propertyName); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/projection/CountProjection.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion.projection; 2 | 3 | import com.eharmony.pho.query.criterion.Aggregate; 4 | 5 | public class CountProjection extends AggregateProjection{ 6 | public CountProjection(String propertyName) { 7 | super(Aggregate.COUNT, propertyName); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/projection/GroupProjection.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion.projection; 2 | 3 | import com.eharmony.pho.query.criterion.Aggregate; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class GroupProjection extends Projection{ 9 | 10 | public GroupProjection(String... propertyNames) { 11 | super(Aggregate.GROUP_BY, propertyNames); 12 | } 13 | 14 | @Override 15 | public String toString() { 16 | return "GroupProjection{" + getPropertyNames() + "}"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/projection/MaxProjection.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion.projection; 2 | 3 | import com.eharmony.pho.query.criterion.Aggregate; 4 | 5 | public class MaxProjection extends AggregateProjection { 6 | public MaxProjection(String propertyName) { 7 | super(Aggregate.MAX, propertyName); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/projection/MinProjection.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion.projection; 2 | 3 | import com.eharmony.pho.query.criterion.Aggregate; 4 | 5 | public class MinProjection extends AggregateProjection { 6 | public MinProjection(String propertyName) { 7 | super(Aggregate.MIN, propertyName); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/query/criterion/projection/Projection.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.query.criterion.projection; 2 | 3 | import com.eharmony.pho.query.criterion.*; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | public class Projection implements Criterion, WithAggregateFunction { 9 | private final Aggregate function; 10 | private final List propertyNames; 11 | 12 | protected Projection(Aggregate function, String... propertyNames) { 13 | this.function = function; 14 | this.propertyNames = Arrays.asList(propertyNames); 15 | } 16 | 17 | @Override 18 | public Aggregate getAggregate() { 19 | return this.function; 20 | } 21 | 22 | public List getPropertyNames() { 23 | return this.propertyNames; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/translator/AbstractQueryTranslator.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.translator; 2 | 3 | import java.lang.reflect.Array; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import com.eharmony.pho.query.QuerySelect; 7 | import com.eharmony.pho.query.criterion.Aggregate; 8 | import com.eharmony.pho.query.criterion.Criterion; 9 | import com.eharmony.pho.query.criterion.Operator; 10 | import com.eharmony.pho.query.criterion.Ordering; 11 | import com.eharmony.pho.query.criterion.expression.EqualityExpression; 12 | import com.eharmony.pho.query.criterion.expression.Expression; 13 | import com.eharmony.pho.query.criterion.expression.NativeExpression; 14 | import com.eharmony.pho.query.criterion.expression.RangeExpression; 15 | import com.eharmony.pho.query.criterion.expression.SetExpression; 16 | import com.eharmony.pho.query.criterion.expression.UnaryExpression; 17 | import com.eharmony.pho.query.criterion.junction.Conjunction; 18 | import com.eharmony.pho.query.criterion.junction.Disjunction; 19 | import com.eharmony.pho.query.criterion.junction.Junction; 20 | import com.eharmony.pho.query.criterion.projection.*; 21 | 22 | /** 23 | * Abstract Query Translation. Convert a generic Query with nested criteria to a datastore specific query. Extend to 24 | * provide datastore specific query component implementations 25 | * 26 | * @param the query type 27 | * @param the ordering type 28 | * @param

the projected type 29 | */ 30 | public abstract class AbstractQueryTranslator implements QueryTranslator { 31 | 32 | private final Class queryClass; 33 | private final Class orderClass; 34 | private final PropertyResolver propertyResolver; 35 | 36 | public AbstractQueryTranslator(Class queryClass, Class orderClass, PropertyResolver propertyResolver) { 37 | this.queryClass = queryClass; 38 | this.orderClass = orderClass; 39 | this.propertyResolver = propertyResolver; 40 | } 41 | 42 | protected Class getQueryClass() { 43 | return queryClass; 44 | } 45 | 46 | protected Class getOrderClass() { 47 | return orderClass; 48 | } 49 | 50 | protected PropertyResolver getPropertyResolver() { 51 | return propertyResolver; 52 | } 53 | 54 | /* 55 | * (non-Javadoc) 56 | * 57 | * @see 58 | * com.eharmony.matching.seeking.translator.QueryTranslator#translate(com.eharmony.matching.seeking.query.Query) 59 | */ 60 | @Override 61 | public Q translate(QuerySelect query) { 62 | Criterion rootCriterion = query.getCriteria(); 63 | Class entityClass = query.getEntityClass(); 64 | return translate(rootCriterion, entityClass); 65 | } 66 | 67 | /* 68 | * Notes regarding the abundance of instanceof checks: 69 | * 70 | * A case for considering it the lesser of evils: https://sites.google.com/site/steveyegge2/when-polymorphism-fails 71 | * 72 | * If we were using Scala, we could create datastore specific Traits that would allow us to extend the Generic Query 73 | * and allow it produce a datastore specific query. 74 | * 75 | * There's also a strong case for the visitor pattern. The downsides are needlessly exposing the underlying 76 | * Criterion types to the various implementations and a significant increase in the number of classes needed for 77 | * implementations. 78 | * 79 | * If this becomes a maintainability or performance issue then we should look at replacing the current approach with 80 | * something like the visitor pattern. 81 | */ 82 | protected Q translate(Criterion c, Class entityClass, AggregateProjection projection) { 83 | // a Criterion can be an Expression or a Junction 84 | if (c instanceof Expression) { 85 | return translate((Expression) c, entityClass, projection); 86 | } else if (c instanceof Junction) { 87 | return translate((Junction) c, entityClass); 88 | } else if (c instanceof GroupProjection) { 89 | return translate((GroupProjection) c, entityClass); 90 | } else if (c instanceof NativeExpression) { 91 | return translate((NativeExpression) c, entityClass); 92 | } else { 93 | throw unsupported(c.getClass()); 94 | } 95 | } 96 | 97 | protected Q translate(Criterion c, Class entityClass) { 98 | return translate(c, entityClass, null); 99 | } 100 | 101 | protected abstract Q translate(NativeExpression e, Class entityClass); 102 | 103 | protected Q translate(Expression e, Class entityClass, AggregateProjection projection) { 104 | String fieldName = propertyResolver.resolve(e.getPropertyName(), entityClass); 105 | if (e instanceof EqualityExpression) { 106 | return translate((EqualityExpression) e, fieldName, projection); 107 | } else if (e instanceof RangeExpression) { 108 | return translate((RangeExpression) e, fieldName, projection); 109 | } else if (e instanceof SetExpression) { 110 | return translate((SetExpression) e, fieldName, projection); 111 | } else if (e instanceof UnaryExpression) { 112 | return translate((UnaryExpression) e, fieldName, projection); 113 | } else { 114 | throw unsupported(e.getClass()); 115 | } 116 | } 117 | 118 | protected Q translate(Projection projection, Class entityClass) { 119 | if (projection instanceof GroupProjection) { 120 | List propertyNames = projection.getPropertyNames(); 121 | return translate((GroupProjection) projection, 122 | propertyNames.stream() 123 | .map(n -> propertyResolver.resolve(n, entityClass)) 124 | .toArray(String[]::new)); 125 | } else { 126 | throw unsupported(projection.getClass()); 127 | } 128 | } 129 | 130 | protected Q translate(Junction j, Class entityClass) { 131 | // a Junction can be a Conjunction (and) or a Disjunction (or) 132 | if (j instanceof Conjunction) { 133 | return translate((Conjunction) j, entityClass); 134 | } else if (j instanceof Disjunction) { 135 | return translate((Disjunction) j, entityClass); 136 | } else { 137 | throw unsupported(j.getClass()); 138 | } 139 | } 140 | 141 | protected Q translate(Conjunction j, Class entityClass) { 142 | return and(subqueries(j, entityClass)); 143 | } 144 | 145 | protected Q translate(Disjunction j, Class entityClass) { 146 | return or(subqueries(j, entityClass)); 147 | } 148 | 149 | @SuppressWarnings("unchecked") 150 | protected Q[] subqueries(Junction j, Class entityClass) { 151 | List criteria = j.getCriteria(); 152 | List translated = new ArrayList(criteria.size()); 153 | for (Criterion c : criteria) { 154 | Q q = translate(c, entityClass, ((Expression) c).getAggregateProjection()); 155 | if (q != null) { 156 | translated.add(q); 157 | } 158 | } 159 | return translated.toArray((Q[]) Array.newInstance(queryClass, translated.size())); 160 | } 161 | 162 | protected Q translate(EqualityExpression e, String fieldName, AggregateProjection projection) { 163 | Operator operator = e.getOperator(); 164 | Object value = e.getValue(); 165 | if (projection != null) { 166 | fieldName = (String) translate(projection, fieldName); 167 | } 168 | switch (operator) { 169 | case EQUAL: 170 | return eq(fieldName, value); 171 | case NOT_EQUAL: 172 | return ne(fieldName, value); 173 | case GREATER_THAN: 174 | return gt(fieldName, value); 175 | case GREATER_THAN_OR_EQUAL: 176 | return gte(fieldName, value); 177 | case LESS_THAN: 178 | return lt(fieldName, value); 179 | case LESS_THAN_OR_EQUAL: 180 | return lte(fieldName, value); 181 | case LIKE: 182 | return like(fieldName, value); 183 | case ILIKE: 184 | return insensitiveLike(fieldName, value); 185 | default: 186 | throw unsupported(operator, EqualityExpression.class); 187 | } 188 | } 189 | 190 | protected Q translate(RangeExpression e, String fieldName, AggregateProjection projection) { 191 | Operator operator = e.getOperator(); 192 | Object from = e.getFrom(); 193 | Object to = e.getTo(); 194 | if (projection != null) { 195 | fieldName = (String) translate(projection, fieldName); 196 | } 197 | switch (operator) { 198 | case BETWEEN: 199 | return between(fieldName, from, to); 200 | default: 201 | throw unsupported(operator, RangeExpression.class); 202 | } 203 | } 204 | 205 | protected Q translate(SetExpression e, String fieldName, AggregateProjection projection) { 206 | Operator operator = e.getOperator(); 207 | Object[] values = e.getValues(); 208 | if (projection != null) { 209 | fieldName = (String) translate(projection, fieldName); 210 | } 211 | switch (operator) { 212 | case IN: 213 | return in(fieldName, values); 214 | case NOT_IN: 215 | return notIn(fieldName, values); 216 | case CONTAINS: 217 | return contains(fieldName, values); 218 | default: 219 | throw unsupported(operator, SetExpression.class); 220 | } 221 | } 222 | 223 | protected Q translate(UnaryExpression e, String fieldName, AggregateProjection projection) { 224 | Operator operator = e.getOperator(); 225 | if (projection != null) { 226 | fieldName = (String) translate(projection, fieldName); 227 | } 228 | switch (operator) { 229 | case NULL: 230 | return isNull(fieldName); 231 | case NOT_NULL: 232 | return notNull(fieldName); 233 | case EMPTY: 234 | return isEmpty(fieldName); 235 | case NOT_EMPTY: 236 | return notEmpty(fieldName); 237 | default: 238 | throw unsupported(operator, UnaryExpression.class); 239 | } 240 | } 241 | 242 | protected P translate(AggregateProjection a, String fieldName) { 243 | Aggregate aggregate = a.getAggregate(); 244 | switch (aggregate) { 245 | case AVG: 246 | return avg(fieldName); 247 | case MAX: 248 | return max(fieldName); 249 | case MIN: 250 | return min(fieldName); 251 | case COUNT: 252 | return count(fieldName); 253 | default: 254 | throw unsupported(aggregate, AggregateProjection.class); 255 | } 256 | } 257 | 258 | protected Q translate(GroupProjection g, String... fieldNames) { 259 | return groupBy(fieldNames); 260 | } 261 | 262 | 263 | @Override 264 | public O translateOrder(QuerySelect query) { 265 | List orderingList = query.getOrder().get(); 266 | @SuppressWarnings("unchecked") 267 | O[] orders = (O[]) Array.newInstance(orderClass, orderingList.size()); 268 | for (int i = 0; i < orders.length; i++) { 269 | Ordering ordering = orderingList.get(i); 270 | orders[i] = order(propertyResolver.resolve(ordering.getPropertyName(), query.getEntityClass()), 271 | ordering); 272 | } 273 | return order(orders); 274 | } 275 | 276 | protected UnsupportedOperationException unsupported(Class type) { 277 | throw new UnsupportedOperationException(type.getSimpleName() + " type not supported."); 278 | } 279 | 280 | protected UnsupportedOperationException unsupported(Operator operator, Class expressionType) { 281 | throw new UnsupportedOperationException(operator + " not supported for " + expressionType.getSimpleName()); 282 | } 283 | 284 | protected UnsupportedOperationException unsupported(Aggregate aggregate, Class projectionType) { 285 | throw new UnsupportedOperationException(aggregate + " not supported for " + projectionType.getSimpleName()); 286 | } 287 | 288 | protected UnsupportedOperationException unsupported(NativeExpression e) { 289 | throw new UnsupportedOperationException("Native Expression (" + e.getExpression() + ") of type " 290 | + e.getExpressionClass() + " not supported."); 291 | } 292 | 293 | /** 294 | * Translate an "equal" expression 295 | * 296 | * @param fieldName the resolved field name 297 | * @param value the reference value 298 | * @return Q 299 | */ 300 | public abstract Q eq(String fieldName, Object value); 301 | 302 | /** 303 | * Translate a "not equal" expression 304 | * 305 | * @param fieldName the resolved field name 306 | * @param value the reference value 307 | * @return Q 308 | */ 309 | public abstract Q ne(String fieldName, Object value); 310 | 311 | /** 312 | * Translate a "less than" expression 313 | * 314 | * @param fieldName the resolved field name 315 | * @param value the reference value 316 | * @return Q 317 | */ 318 | public abstract Q lt(String fieldName, Object value); 319 | 320 | /** 321 | * Translate a "less than or equal" expression 322 | * 323 | * @param fieldName the resolved field name 324 | * @param value the reference value 325 | * @return Q 326 | */ 327 | public abstract Q lte(String fieldName, Object value); 328 | 329 | /** 330 | * Translate a "greater than" expression 331 | * 332 | * @param fieldName the resolved field name 333 | * @param value the reference value 334 | * @return Q 335 | */ 336 | public abstract Q gt(String fieldName, Object value); 337 | 338 | /** 339 | * Translate a "greater than or equal" expression 340 | * 341 | * @param fieldName the resolved field name 342 | * @param value the reference value 343 | * @return Q 344 | */ 345 | public abstract Q gte(String fieldName, Object value); 346 | 347 | /** 348 | * Translate a "between" expression 349 | * 350 | * @param fieldName the resolved field name 351 | * @param from the lower bound value 352 | * @param to the upper bound value 353 | * @return Q 354 | */ 355 | public abstract Q between(String fieldName, Object from, Object to); 356 | 357 | /** 358 | * Translate an "in" expression 359 | * 360 | * @param fieldName the resolved field name 361 | * @param values the reference values 362 | * @return Q 363 | */ 364 | public abstract Q in(String fieldName, Object[] values); 365 | 366 | /** 367 | * Translate a "not in" expression 368 | * 369 | * @param fieldName the resolved field name 370 | * @param values the reference values 371 | * @return Q 372 | */ 373 | public abstract Q notIn(String fieldName, Object[] values); 374 | 375 | /** 376 | * Translate a "contains" expression 377 | * 378 | * @param fieldName the resolved field name 379 | * @param values the reference values 380 | * @return Q 381 | */ 382 | public abstract Q contains(String fieldName, Object[] values); 383 | 384 | /** 385 | * Translate a "is null" expression 386 | * 387 | * @param fieldName the resolved field name 388 | * @return Q 389 | */ 390 | public abstract Q isNull(String fieldName); 391 | 392 | /** 393 | * Translate a "not null" expression 394 | * 395 | * @param fieldName the resolved field name 396 | * @return Q 397 | */ 398 | public abstract Q notNull(String fieldName); 399 | 400 | /** 401 | * Translate a "like" expression 402 | * 403 | * @param fieldName the resolved field name 404 | * @param value the value to be like 405 | * @return Q 406 | */ 407 | public abstract Q like(String fieldName, Object value); 408 | 409 | /** 410 | * Translate an "ilike" expression 411 | * 412 | * @param fieldName the resolved field name 413 | * @param value the value to be like 414 | * @return Q 415 | */ 416 | public abstract Q insensitiveLike(String fieldName, Object value); 417 | 418 | /** 419 | * Translate a "is empty" expression 420 | * 421 | * @param fieldName the resolved field name 422 | * @return Q 423 | */ 424 | public abstract Q isEmpty(String fieldName); 425 | 426 | /** 427 | * Translate a "is empty" expression 428 | * 429 | * @param fieldName the resolved field name 430 | * @return Q 431 | */ 432 | public abstract Q notEmpty(String fieldName); 433 | 434 | /** 435 | * Translate an order statement 436 | * 437 | * @param fieldName the resolved field name 438 | * @param ordering the ordering 439 | * @return O 440 | */ 441 | public abstract O order(String fieldName, Ordering ordering); 442 | 443 | /** 444 | * Join multiple orderings 445 | * 446 | * @param orders O 447 | * @return O 448 | */ 449 | public abstract O order(@SuppressWarnings("unchecked") O... orders); 450 | 451 | /** 452 | * Translate an "and" expression 453 | * 454 | * @param subqueries Q 455 | * @return Q 456 | */ 457 | public abstract Q and(@SuppressWarnings("unchecked") Q... subqueries); 458 | 459 | /** 460 | * Translate an "or" expression 461 | * 462 | * @param subqueries Q 463 | * @return Q 464 | */ 465 | public abstract Q or(@SuppressWarnings("unchecked") Q... subqueries); 466 | 467 | /** 468 | * Translate a "limit expression" expression 469 | * 470 | * @param value Integer number of results to return 471 | * @return Q 472 | */ 473 | public abstract Q limit(Integer value); 474 | 475 | public abstract Q groupBy(String... fieldNames); 476 | 477 | public abstract P count(String fieldName); 478 | 479 | public abstract P avg(String fieldName); 480 | 481 | public abstract P countAll(); 482 | 483 | public abstract P sum(String fieldName); 484 | 485 | public abstract P max(String fieldName); 486 | 487 | public abstract P min(String fieldName); 488 | 489 | } 490 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/translator/EntityResolver.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.translator; 2 | 3 | /** 4 | * Description for a type that resolves a datastore specific table/collection name for an entity class 5 | */ 6 | public interface EntityResolver { 7 | 8 | /** 9 | * Resolve the datastore specific collection / table name for the provided entity class 10 | * 11 | * @param entityClass 12 | * Class 13 | * @return String 14 | */ 15 | public String resolve(Class entityClass); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/translator/PropertyResolver.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.translator; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import com.eharmony.pho.mapper.EntityPropertyBinding; 7 | 8 | /** 9 | * Resolve datastore specific names for properties of an entity class. 10 | * 11 | */ 12 | public interface PropertyResolver { 13 | 14 | public String resolve(String entityFieldName, Class entityClass); 15 | 16 | public List resolveEntityMappingPropertyNames(List entityFieldNames, Class entityClass); 17 | 18 | public EntityPropertyBinding resolveEntityPropertyBindingByStoreMappingName(String mappingName, Class clz); 19 | 20 | public EntityPropertyBinding resolveEntityPropertyBindingByEntityFieldName(String entityFieldName, 21 | Class entityClass); 22 | 23 | public Map getStoreFieldNamePropertyBindingMap(Class clz); 24 | 25 | public Map getEntityPropertyNamePropertyBindingMap(Class clz); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/translator/QueryTranslator.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.translator; 2 | 3 | import com.eharmony.pho.query.QuerySelect; 4 | import com.eharmony.pho.query.QueryUpdate; 5 | 6 | /** 7 | * Description of a class that translates a generic Query to a datastore specific query 8 | * 9 | * @param 10 | * the query type 11 | * @param 12 | * the ordering type 13 | * @param

14 | * the projected type 15 | */ 16 | public interface QueryTranslator { 17 | 18 | /** 19 | * Translate the generic Query 20 | * 21 | * @param 22 | * entity Type 23 | * @param 24 | * return type 25 | * @param query 26 | * QuerySelect 27 | * @return Q 28 | */ 29 | public Q translate(QuerySelect query); 30 | 31 | /** 32 | * Translate the generic Orderings 33 | * 34 | * @param 35 | * entity Type 36 | * @param 37 | * return type 38 | * @param query 39 | * QuerySelect 40 | * @return O 41 | */ 42 | public O translateOrder(QuerySelect query); 43 | 44 | /** 45 | * Translate the "Projections" 46 | * 47 | * @param 48 | * entity Type 49 | * @param 50 | * return type 51 | * @param query 52 | * QuerySelect 53 | * @return P 54 | */ 55 | public P translateProjection(QuerySelect query); 56 | 57 | /** 58 | * Translate the generic Query 59 | * 60 | * @param 61 | * entity Type 62 | * @param updateQuery 63 | * QueryUpdate 64 | * @return Q 65 | */ 66 | public Q translate(QueryUpdate updateQuery); 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/eharmony/pho/translator/SimpleEntityResolver.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.translator; 2 | 3 | /** 4 | * The Simple Entity Resolver uses the simple name of the class for the table/collection name 5 | */ 6 | public class SimpleEntityResolver implements EntityResolver { 7 | 8 | /* 9 | * (non-Javadoc) 10 | * 11 | * @see com.eharmony.matching.seeking.translator.EntityResolver#resolve(java.lang.Class) 12 | */ 13 | @Override 14 | public String resolve(Class entityClass) { 15 | return entityClass.getSimpleName(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring/application-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/resources/hbase.properties: -------------------------------------------------------------------------------- 1 | hbase.zk.host=localhost 2 | hbase.zk.port=2181 3 | 4 | -------------------------------------------------------------------------------- /src/test/java/com/eharmony/pho/hbase/mapper/EntityPropertiesResolverTest.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.hbase.mapper; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | import com.eharmony.pho.hbase.model.EmbededEntityExample; 10 | import com.eharmony.pho.hbase.model.NestedEntity; 11 | import com.eharmony.pho.mapper.EntityPropertiesMappingContext; 12 | import com.eharmony.pho.mapper.EntityPropertiesResolver; 13 | import com.eharmony.pho.mapper.EntityPropertyBinding; 14 | 15 | public class EntityPropertiesResolverTest { 16 | 17 | @Test 18 | public void testMapToEntityProperties() throws ClassNotFoundException { 19 | NestedEntity nestedClass = new NestedEntity(); 20 | nestedClass.setName("eharmony"); 21 | nestedClass.setNestedClassDescription("property mapper test"); 22 | 23 | EmbededEntityExample embdedClassExample = new EmbededEntityExample(); 24 | embdedClassExample.setNestedObject(nestedClass); 25 | embdedClassExample.setAnnotatedProperty("annotated property value"); 26 | embdedClassExample.setProperty2("second property"); 27 | 28 | List entityClassNames = new ArrayList(); 29 | entityClassNames.add("com.eharmony.pho.hbase.model.EmbededEntityExample"); 30 | EntityPropertiesMappingContext mappingContext = new EntityPropertiesMappingContext(entityClassNames); 31 | EntityPropertiesResolver resolver = new EntityPropertiesResolver(mappingContext); 32 | EntityPropertyBinding propBinding = resolver.resolveEntityPropertyBindingByStoreMappingName("annotatedProperty",EmbededEntityExample.class); 33 | Assert.assertNotNull(propBinding); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/eharmony/pho/hbase/mapper/PhoenixProjectedResultMapperTest.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.hbase.mapper; 2 | 3 | import static org.junit.Assert.assertFalse; 4 | import static org.junit.Assert.assertNotNull; 5 | import static org.junit.Assert.assertTrue; 6 | import static org.mockito.Matchers.any; 7 | import static org.mockito.Mockito.mock; 8 | import static org.mockito.Mockito.when; 9 | 10 | import java.sql.ResultSetMetaData; 11 | import java.util.Iterator; 12 | 13 | import org.apache.phoenix.jdbc.PhoenixResultSet; 14 | import org.junit.Test; 15 | import org.mockito.invocation.InvocationOnMock; 16 | import org.mockito.stubbing.Answer; 17 | public class PhoenixProjectedResultMapperTest { 18 | 19 | 20 | @Test 21 | public void testMapResultsResultSetClassOfR_Long() throws Exception { 22 | 23 | PhoenixProjectedResultMapper mapper = new PhoenixProjectedResultMapper(null); 24 | @SuppressWarnings("resource") 25 | PhoenixResultSet rs = mock(PhoenixResultSet.class); 26 | ResultSetMetaData rsMeta = mock(ResultSetMetaData.class); 27 | 28 | when(rs.getMetaData()).thenReturn(rsMeta); 29 | when(rs.next()).thenAnswer(new Answer() { 30 | boolean t = false; 31 | 32 | @Override 33 | public Boolean answer(InvocationOnMock invocation) throws Throwable { 34 | t = !t; 35 | return t; 36 | } 37 | 38 | }); 39 | when(rsMeta.getColumnCount()).thenReturn(1); 40 | when(rsMeta.getColumnName(1)).thenReturn("count"); 41 | when(rs.getObject(any())).thenReturn(new Long(1000)); 42 | Iterable longL = mapper.mapResults(rs, Long.class); 43 | assertNotNull(longL); 44 | Iterator it = longL.iterator(); 45 | assertTrue(it.next() == 1000); 46 | assertFalse(it.hasNext()); 47 | } 48 | 49 | @Test 50 | public void testMapResultsResultSetClassOfR_Int() throws Exception { 51 | 52 | PhoenixProjectedResultMapper mapper = new PhoenixProjectedResultMapper(null); 53 | @SuppressWarnings("resource") 54 | PhoenixResultSet rs = mock(PhoenixResultSet.class); 55 | ResultSetMetaData rsMeta = mock(ResultSetMetaData.class); 56 | 57 | when(rs.getMetaData()).thenReturn(rsMeta); 58 | when(rs.next()).thenAnswer(new Answer() { 59 | boolean t = false; 60 | 61 | @Override 62 | public Boolean answer(InvocationOnMock invocation) throws Throwable { 63 | t = !t; 64 | return t; 65 | } 66 | 67 | }); 68 | when(rsMeta.getColumnCount()).thenReturn(1); 69 | when(rsMeta.getColumnName(1)).thenReturn("count"); 70 | when(rs.getObject(any())).thenReturn(new Integer(1000)); 71 | Iterable intL = mapper.mapResults(rs, Integer.class); 72 | assertNotNull(intL); 73 | Iterator it = intL.iterator(); 74 | assertTrue(it.next() == 1000); 75 | assertFalse(it.hasNext()); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/com/eharmony/pho/hbase/model/EmbededEntityExample.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.hbase.model; 2 | 3 | import com.google.code.morphia.annotations.Embedded; 4 | import com.google.code.morphia.annotations.Entity; 5 | import com.google.code.morphia.annotations.Property; 6 | 7 | @Entity(value = "embded_model") 8 | public class EmbededEntityExample { 9 | @Embedded 10 | private NestedEntity nestedObject; 11 | 12 | @Property(value = "annotatedProperty") 13 | private String annotatedProperty; 14 | private String property2; 15 | 16 | public NestedEntity getNestedObject() { 17 | return nestedObject; 18 | } 19 | 20 | public void setNestedObject(NestedEntity nestedObject) { 21 | this.nestedObject = nestedObject; 22 | } 23 | 24 | public String getAnnotatedProperty() { 25 | return annotatedProperty; 26 | } 27 | 28 | public void setAnnotatedProperty(String annotatedProperty) { 29 | this.annotatedProperty = annotatedProperty; 30 | } 31 | 32 | public String getProperty2() { 33 | return property2; 34 | } 35 | 36 | public void setProperty2(String property2) { 37 | this.property2 = property2; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/eharmony/pho/hbase/model/NestedEntity.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.hbase.model; 2 | 3 | import com.google.code.morphia.annotations.Property; 4 | 5 | public class NestedEntity { 6 | @Property(value = "nestedClassName") 7 | private String name; 8 | 9 | private String nestedClassDescription; 10 | 11 | public String getName() { 12 | return name; 13 | } 14 | 15 | public void setName(String name) { 16 | this.name = name; 17 | } 18 | 19 | public String getNestedClassDescription() { 20 | return nestedClassDescription; 21 | } 22 | 23 | public void setNestedClassDescription(String nestedClassDescription) { 24 | this.nestedClassDescription = nestedClassDescription; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/eharmony/pho/hbase/model/TranslationTestClass.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.hbase.model; 2 | 3 | import java.util.Date; 4 | 5 | import com.google.code.morphia.annotations.Entity; 6 | import com.google.code.morphia.annotations.Property; 7 | 8 | @Entity(value="user") 9 | public class TranslationTestClass { 10 | 11 | @Property(value="user_name") 12 | private String name; 13 | 14 | @Property(value="uid") 15 | private int userId; 16 | 17 | @Property(value="created_date") 18 | private Date createdAt; 19 | 20 | @Property(value="pwd") 21 | private String password; 22 | 23 | public String getName() { 24 | return name; 25 | } 26 | public void setName(String name) { 27 | this.name = name; 28 | } 29 | public int getUserId() { 30 | return userId; 31 | } 32 | public void setUserId(int userId) { 33 | this.userId = userId; 34 | } 35 | public Date getCreatedAt() { 36 | return createdAt; 37 | } 38 | public void setCreatedAt(Date createdAt) { 39 | this.createdAt = createdAt; 40 | } 41 | public String getPassword() { 42 | return password; 43 | } 44 | public void setPassword(String password) { 45 | this.password = password; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/eharmony/pho/hbase/translator/PhoenixHBaseQueryTranslatorTest.java: -------------------------------------------------------------------------------- 1 | package com.eharmony.pho.hbase.translator; 2 | 3 | import java.text.DateFormat; 4 | import java.text.ParseException; 5 | import java.text.SimpleDateFormat; 6 | import java.util.ArrayList; 7 | import java.util.Date; 8 | import java.util.LinkedList; 9 | import java.util.List; 10 | 11 | import com.eharmony.pho.query.criterion.GroupRestrictions; 12 | import com.eharmony.pho.query.criterion.Projections; 13 | import org.apache.commons.lang.StringUtils; 14 | import org.junit.Assert; 15 | import org.junit.Before; 16 | import org.junit.Test; 17 | 18 | import com.eharmony.pho.hbase.model.TranslationTestClass; 19 | import com.eharmony.pho.mapper.EntityPropertiesMappingContext; 20 | import com.eharmony.pho.mapper.EntityPropertiesResolver; 21 | import com.eharmony.pho.query.QuerySelect; 22 | import com.eharmony.pho.query.QueryUpdate; 23 | import com.eharmony.pho.query.builder.QueryBuilder; 24 | import com.eharmony.pho.query.builder.QueryUpdateBuilder; 25 | import com.eharmony.pho.query.criterion.Ordering; 26 | import com.eharmony.pho.query.criterion.Restrictions; 27 | 28 | public class PhoenixHBaseQueryTranslatorTest { 29 | 30 | public static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss z"; 31 | private final DateFormat dateFormat = new SimpleDateFormat(TIMESTAMP_FORMAT); 32 | private EntityPropertiesResolver entityPropertiesResolver = null; 33 | 34 | @Before 35 | public void setUp() throws ClassNotFoundException { 36 | final List classesList = new ArrayList(); 37 | classesList.add("com.eharmony.pho.hbase.model.TranslationTestClass"); 38 | classesList.add("com.eharmony.pho.hbase.model.EmbededEntityExample"); 39 | EntityPropertiesMappingContext context = new EntityPropertiesMappingContext(classesList); 40 | entityPropertiesResolver = new EntityPropertiesResolver(context); 41 | } 42 | 43 | @Test 44 | public void testStringLike() throws ParseException, ClassNotFoundException { 45 | 46 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 47 | String city = "Angeles"; 48 | String result = translator.like("fname", city); 49 | String expected = "fname LIKE '%Angeles%'"; 50 | Assert.assertEquals(expected, result); 51 | } 52 | 53 | @Test 54 | public void testStringILike() throws ParseException, ClassNotFoundException { 55 | 56 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 57 | String city = "Angeles"; 58 | String result = translator.insensitiveLike("fname", city); 59 | String expected = "fname ILIKE '%Angeles%'"; 60 | 61 | Assert.assertEquals(expected, result); 62 | } 63 | 64 | @Test 65 | public void testDateEq() throws ParseException, ClassNotFoundException { 66 | 67 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 68 | String dateString = "2011-12-19 18:35:34 PDT PST"; 69 | Date date = getDate(dateString); 70 | String result = translator.eq("deliveryDate", date); 71 | Assert.assertNotNull(result); 72 | System.out.println(result); 73 | Assert.assertTrue(StringUtils.containsAny(result, dateString)); 74 | //String expected = "deliveryDate = TO_DATE('2011-12-19 17:35:34 PST', 'yyyy-MM-dd HH:mm:ss z')"; 75 | //Assert.assertEquals(expected, result); 76 | } 77 | 78 | @Test 79 | public void testStringEq() throws ParseException, ClassNotFoundException { 80 | 81 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 82 | String name = "vijay"; 83 | String result = translator.eq("fname", name); 84 | Assert.assertNotNull(result); 85 | System.out.println(result); 86 | Assert.assertTrue(StringUtils.containsAny(result, name)); 87 | Assert.assertEquals("fname = 'vijay'", result); 88 | } 89 | 90 | @Test 91 | public void testIntegerEq() throws ParseException, ClassNotFoundException { 92 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 93 | int uid = 3000; 94 | String result = translator.eq("uid", uid); 95 | Assert.assertNotNull(result); 96 | System.out.println(result); 97 | Assert.assertEquals("uid = 3000", result); 98 | } 99 | 100 | @Test 101 | public void testBooleanEq() throws ParseException, ClassNotFoundException { 102 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 103 | boolean isUser = true; 104 | String result = translator.eq("isu", isUser); 105 | Assert.assertNotNull(result); 106 | System.out.println(result); 107 | Assert.assertEquals("isu = true", result); 108 | } 109 | 110 | @Test 111 | public void testLongEq() throws ParseException, ClassNotFoundException { 112 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 113 | long mid = 12l; 114 | String result = translator.eq("mid", mid); 115 | Assert.assertNotNull(result); 116 | System.out.println(result); 117 | Assert.assertEquals("mid = 12", result); 118 | } 119 | 120 | @Test 121 | public void testFloatEq() throws ParseException, ClassNotFoundException { 122 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 123 | float distance = 12.5f; 124 | String result = translator.eq("distance", distance); 125 | Assert.assertNotNull(result); 126 | System.out.println(result); 127 | Assert.assertEquals("distance = 12.5", result); 128 | } 129 | 130 | private Date getDate(String date) throws ParseException { 131 | return dateFormat.parse(date); 132 | } 133 | 134 | @Test 135 | public void testTranslateSelect() throws ClassNotFoundException { 136 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 137 | QuerySelect query = QueryBuilder 138 | .builderFor(TranslationTestClass.class).select().add(Restrictions.eq("uid", 2)).build(); 139 | String queryStr = translator.translate(query); 140 | System.out.println(queryStr); 141 | Assert.assertTrue(StringUtils.contains(queryStr, "SELECT")); 142 | 143 | } 144 | 145 | @Test 146 | public void testTranslateSelectWithProjection() throws ClassNotFoundException { 147 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 148 | List fields = new LinkedList(); 149 | fields.add("name"); 150 | fields.add("userId"); 151 | fields.add("createdAt"); 152 | fields.add("pwd"); 153 | QuerySelect query = QueryBuilder 154 | .builderFor(TranslationTestClass.class).select(fields).add(Restrictions.eq("userId", 2)).build(); 155 | String queryStr = translator.translate(query); 156 | System.out.println(queryStr); 157 | Assert.assertTrue(StringUtils.contains(queryStr, "SELECT")); 158 | 159 | } 160 | 161 | @Test 162 | public void testTranslateSelectWithCriteria() throws ClassNotFoundException { 163 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 164 | QuerySelect query = QueryBuilder 165 | .builderFor(TranslationTestClass.class).select() 166 | .add(Restrictions.eq("userId", 2)) 167 | .add(Restrictions.eq("name", "vija'y")) 168 | .add(Restrictions.gte("createdAt", new Date())) 169 | .build(); 170 | String queryStr = translator.translate(query); 171 | System.out.println(queryStr); 172 | Assert.assertTrue(StringUtils.contains(queryStr, "SELECT")); 173 | Assert.assertFalse(StringUtils.contains(queryStr, "ORDER")); 174 | 175 | } 176 | 177 | @Test 178 | public void testTranslateSelectWithCriteriaAndOrder() throws ClassNotFoundException { 179 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 180 | QuerySelect query = QueryBuilder 181 | .builderFor(TranslationTestClass.class).select() 182 | .add(Restrictions.eq("userId", 2)) 183 | .add(Restrictions.eq("name", "vija'y")) 184 | .addOrder(Ordering.desc("createdAt")) 185 | .build(); 186 | String queryStr = translator.translate(query); 187 | System.out.println(queryStr); 188 | Assert.assertTrue(StringUtils.contains(queryStr, "SELECT")); 189 | Assert.assertTrue(StringUtils.contains(queryStr, "DESC")); 190 | 191 | } 192 | 193 | @Test 194 | public void testTranslateSelectWithCriteriaAndOrderAndValidLimit() throws ClassNotFoundException { 195 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 196 | QuerySelect query = QueryBuilder 197 | .builderFor(TranslationTestClass.class).select() 198 | .add(Restrictions.eq("userId", 2)) 199 | .add(Restrictions.eq("name", "vija'y")) 200 | .addOrder(Ordering.desc("createdAt")) 201 | .setMaxResults(10) 202 | .build(); 203 | String queryStr = translator.translate(query); 204 | Assert.assertTrue(StringUtils.contains(queryStr, "SELECT")); 205 | Assert.assertTrue(StringUtils.contains(queryStr, "DESC")); 206 | Assert.assertTrue(StringUtils.contains(queryStr, "ORDER BY created_date DESC NULLS FIRST LIMIT 10")); 207 | } 208 | 209 | @Test 210 | public void testTranslateSelectWithCriteriaAndOrderAndInValidLimit() throws ClassNotFoundException { 211 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 212 | QuerySelect query = QueryBuilder 213 | .builderFor(TranslationTestClass.class).select() 214 | .add(Restrictions.eq("userId", 2)) 215 | .add(Restrictions.eq("name", "vija'y")) 216 | .addOrder(Ordering.desc("createdAt")) 217 | .setMaxResults(0) 218 | .build(); 219 | String queryStr = translator.translate(query); 220 | System.out.println(queryStr); 221 | Assert.assertTrue(StringUtils.contains(queryStr, "SELECT")); 222 | Assert.assertTrue(StringUtils.contains(queryStr, "DESC")); 223 | Assert.assertFalse(StringUtils.contains(queryStr, "LIMIT 10")); 224 | 225 | } 226 | 227 | @Test 228 | public void testTranslateUpsertWithQuoteInName() throws ClassNotFoundException { 229 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 230 | QueryUpdate query = QueryUpdateBuilder 231 | .builderFor(buildTestClassObjectA()) 232 | .update() 233 | .build(); 234 | String queryStr = translator.translate(query); 235 | System.out.println(queryStr); 236 | Assert.assertTrue(StringUtils.contains(queryStr, "UPSERT")); 237 | Assert.assertTrue(StringUtils.contains(queryStr, "Plain''fiekd''")); 238 | 239 | } 240 | 241 | @Test 242 | public void testTranslateUpsertWithEmptySelectedFields() throws ClassNotFoundException { 243 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 244 | QueryUpdate query = QueryUpdateBuilder 245 | .builderFor(buildTestClassObjectB()) 246 | .update(new ArrayList()) 247 | .build(); 248 | 249 | String queryStr = translator.translate(query); 250 | System.out.println(queryStr); 251 | Assert.assertTrue(StringUtils.contains(queryStr, "UPSERT")); 252 | Assert.assertTrue(StringUtils.contains(queryStr, "special~!@#$%^&")); 253 | 254 | } 255 | 256 | @Test 257 | public void testTranslateUpsertWithNullFields() throws ClassNotFoundException { 258 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 259 | QueryUpdate query = QueryUpdateBuilder 260 | .builderFor(buildTestClassObjectC()) 261 | .update(new ArrayList()) 262 | .build(); 263 | 264 | String queryStr = translator.translate(query); 265 | System.out.println(queryStr); 266 | Assert.assertTrue(StringUtils.contains(queryStr, "UPSERT")); 267 | 268 | } 269 | 270 | @Test 271 | public void testIsNull() throws ParseException, ClassNotFoundException { 272 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 273 | String result = translator.isNull("myField"); 274 | Assert.assertNotNull(result); 275 | Assert.assertEquals("myField IS NULL", result); 276 | } 277 | 278 | @Test 279 | public void testIsNotNull() throws ParseException, ClassNotFoundException { 280 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 281 | String result = translator.notNull("myField"); 282 | Assert.assertNotNull(result); 283 | Assert.assertEquals("myField IS NOT NULL", result); 284 | } 285 | 286 | @Test 287 | public void testGroupBy() throws ParseException, ClassNotFoundException { 288 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 289 | String result = translator.groupBy("myField"); 290 | Assert.assertNotNull(result); 291 | Assert.assertEquals("GROUP BY(myField)", result); 292 | } 293 | 294 | @Test 295 | public void testGroupByTwoFields() throws ParseException, ClassNotFoundException { 296 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 297 | String result = translator.groupBy("myField", "myField2"); 298 | Assert.assertNotNull(result); 299 | Assert.assertEquals("GROUP BY(myField, myField2)", result); 300 | } 301 | 302 | @Test 303 | public void testMax() throws ParseException, ClassNotFoundException { 304 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 305 | String result = translator.max("myField"); 306 | Assert.assertNotNull(result); 307 | Assert.assertEquals("MAX(myField)", result); 308 | } 309 | 310 | @Test 311 | public void testMin() throws ParseException, ClassNotFoundException { 312 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 313 | String result = translator.min("myField"); 314 | Assert.assertNotNull(result); 315 | Assert.assertEquals("MIN(myField)", result); 316 | } 317 | 318 | @Test 319 | public void testAvg() throws ParseException, ClassNotFoundException { 320 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 321 | String result = translator.avg("myField"); 322 | Assert.assertNotNull(result); 323 | Assert.assertEquals("AVG(myField)", result); 324 | } 325 | 326 | @Test 327 | public void testSum() throws ParseException, ClassNotFoundException { 328 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 329 | String result = translator.sum("myField"); 330 | Assert.assertNotNull(result); 331 | Assert.assertEquals("SUM(myField)", result); 332 | } 333 | 334 | @Test 335 | public void testCount() throws ParseException, ClassNotFoundException { 336 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 337 | String result = translator.count("myField"); 338 | Assert.assertNotNull(result); 339 | Assert.assertEquals("COUNT(myField)", result); 340 | } 341 | 342 | @Test 343 | public void testTranslateSelectWithGroupBy() throws ClassNotFoundException { 344 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 345 | QuerySelect query = QueryBuilder 346 | .builderFor(TranslationTestClass.class).select() 347 | .add(Restrictions.eq("userId", 2)) 348 | .add(Restrictions.eq("name", "vija'y")) 349 | .addProjection(Projections.groupBy("name")) 350 | .build(); 351 | String queryStr = translator.translate(query); 352 | System.out.println(queryStr); 353 | String expected = 354 | "SELECT user_name FROM user WHERE (uid = 2) AND (user_name = 'vija''y') GROUP BY(user_name)"; 355 | Assert.assertEquals(expected, queryStr); 356 | } 357 | 358 | @Test 359 | public void testTranslateSelectWithGroupByAndAggregates() throws ClassNotFoundException { 360 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 361 | QuerySelect query = QueryBuilder 362 | .builderFor(TranslationTestClass.class).select() 363 | .add(Restrictions.eq("userId", 2)) 364 | .add(Restrictions.eq("name", "vija'y")) 365 | .addProjection(Projections.groupBy("name")) 366 | .addProjection(Projections.max("userId")) 367 | .build(); 368 | String queryStr = translator.translate(query); 369 | System.out.println(queryStr); 370 | String expected = 371 | "SELECT user_name, MAX(uid) FROM user WHERE (uid = 2) AND (user_name = 'vija''y') GROUP BY(user_name)"; 372 | Assert.assertEquals(expected, queryStr); 373 | } 374 | 375 | @Test 376 | public void testTranslateSelectWithGroupByAndHavingClause() throws ClassNotFoundException { 377 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 378 | QuerySelect query = QueryBuilder 379 | .builderFor(TranslationTestClass.class).select() 380 | .add(Restrictions.eq("userId", 2)) 381 | .add(Restrictions.eq("name", "vija'y")) 382 | .addProjection(Projections.groupBy("name")) 383 | .addProjection(Projections.max("userId")) 384 | .addGroupCriterion( GroupRestrictions.eq(Projections.max("userId"), 2)) 385 | .build(); 386 | String queryStr = translator.translate(query); 387 | System.out.println(queryStr); 388 | String expected = 389 | "SELECT user_name, MAX(uid) FROM user WHERE (uid = 2) AND (user_name = 'vija''y') GROUP BY(user_name) HAVING MAX(uid) = 2"; 390 | Assert.assertEquals(expected, queryStr); 391 | } 392 | 393 | @Test 394 | public void testTranslateSelectWithGroupByAndHavingClauses() throws ClassNotFoundException { 395 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 396 | QuerySelect query = QueryBuilder 397 | .builderFor(TranslationTestClass.class).select() 398 | .add(Restrictions.eq("userId", 2)) 399 | .add(Restrictions.eq("name", "vija'y")) 400 | .addProjection(Projections.groupBy("name")) 401 | .addProjection(Projections.max("userId")) 402 | .addGroupCriterion(GroupRestrictions.eq(Projections.max("userId"), 2)) 403 | .addGroupCriterion(GroupRestrictions.eq(Projections.max("name"), "vija'y")) 404 | .build(); 405 | String queryStr = translator.translate(query); 406 | System.out.println(queryStr); 407 | String expected = 408 | "SELECT user_name, MAX(uid) FROM user WHERE (uid = 2) AND (user_name = 'vija''y') GROUP BY(user_name) " + 409 | "HAVING (MAX(uid) = 2) AND (MAX(user_name) = 'vija''y')"; 410 | Assert.assertEquals(expected, queryStr); 411 | } 412 | 413 | @Test 414 | public void testCountAll() throws ParseException, ClassNotFoundException { 415 | PhoenixHBaseQueryTranslator translator = new PhoenixHBaseQueryTranslator(entityPropertiesResolver); 416 | String result = translator.countAll(); 417 | Assert.assertNotNull(result); 418 | Assert.assertEquals("COUNT(*)", result); 419 | } 420 | 421 | private TranslationTestClass buildTestClassObjectA() { 422 | TranslationTestClass testClass = new TranslationTestClass(); 423 | testClass.setName("Plain'fiekd'\\\\\\"); 424 | testClass.setUserId(1); 425 | return testClass; 426 | } 427 | 428 | private TranslationTestClass buildTestClassObjectB() { 429 | TranslationTestClass testClass = new TranslationTestClass(); 430 | testClass.setName("special~!@#$%^&*()_+=-:;\"'?/>.<,|\\\\}]{[clas's\\"); 431 | testClass.setUserId(1); 432 | return testClass; 433 | } 434 | 435 | private TranslationTestClass buildTestClassObjectC() { 436 | TranslationTestClass testClass = new TranslationTestClass(); 437 | testClass.setName(null); 438 | testClass.setUserId(1); 439 | return testClass; 440 | } 441 | 442 | 443 | } 444 | --------------------------------------------------------------------------------