/* * Hibernate OGM, Domain model persistence for NoSQL datastores * * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.ogm.datastore.mongodb.test.query.parsing; import static org.fest.assertions.Assertions.assertThat; import java.util.HashMap; import java.util.Map; import org.hibernate.hql.QueryParser; import org.hibernate.hql.ast.spi.EntityNamesResolver; import org.hibernate.ogm.datastore.mongodb.logging.impl.Log; import org.hibernate.ogm.datastore.mongodb.logging.impl.LoggerFactory; import org.hibernate.ogm.datastore.mongodb.query.parsing.impl.MongoDBProcessingChain; import org.hibernate.ogm.datastore.mongodb.query.parsing.impl.MongoDBQueryParsingResult; import org.hibernate.ogm.datastore.mongodb.test.query.parsing.model.IndexedEntity; import org.hibernate.ogm.datastore.mongodb.test.query.parsing.model.inheritance.singletable.CommunityMemberST; import org.hibernate.ogm.datastore.mongodb.test.query.parsing.model.inheritance.singletable.EmployeeST; import org.hibernate.ogm.datastore.mongodb.test.query.parsing.model.inheritance.singletable.PersonST; import org.hibernate.ogm.datastore.mongodb.utils.MapBasedEntityNamesResolver; import org.hibernate.ogm.utils.OgmTestCase; import org.junit.Before; import org.junit.Test; /** * Integration test for {@link org.hibernate.ogm.datastore.mongodb.query.parsing.impl.MongoDBQueryResolverDelegate} and * {@link org.hibernate.ogm.datastore.mongodb.query.parsing.impl.MongoDBQueryRendererDelegate}. * * @author Gunnar Morling */ public class MongoDBQueryParsingTest extends OgmTestCase { private Log log = LoggerFactory.getLogger(); private QueryParser queryParser; @Before public void setupParser() { queryParser = new QueryParser(); } @Test public void shouldCreateUnrestrictedQuery() { assertMongoDbQuery( "from IndexedEntity", "{ }" ); } @Test public void shouldCreateQueryWithSingleDiscriminatorValue() { assertMongoDbQuery( "from EmployeeST", "{ \"DTYPE\" : \"EMP\" }", EmployeeST.class ); } @Test public void shouldCreateQueryWithSingleDiscriminatorValueWithFilter() { assertMongoDbQuery( "from EmployeeST e where e.employer = 'Red Hat'", "{ \"$and\" : [" + "{ \"employer\" : \"Red Hat\" }, " + "{ \"DTYPE\" : \"EMP\" }" + "] }", EmployeeST.class ); } @Test public void shouldCreateQueryWithMultipleDiscriminatorValues() { assertMongoDbQuery( "from CommunityMemberST", "{ \"DTYPE\" : " + "{ \"$in\" : [\"CMM\", \"EMP\"] } " + "}", CommunityMemberST.class ); } @Test public void shouldCreateQueryWithMultipleDiscriminatorValuesWithFilter() { assertMongoDbQuery( "from CommunityMemberST c where c.project = 'Hibernate OGM'", "{ \"$and\" : [" + "{ \"project\" : \"Hibernate OGM\" }, " + "{ \"DTYPE\" : " + "{ \"$in\" : [\"CMM\", \"EMP\"] }" + " }" + "] }", CommunityMemberST.class ); } @Test public void shouldCreateRestrictedQueryUsingSelect() { assertMongoDbQuery( "select e from IndexedEntity e where e.title = 'same'", "{ \"title\" : \"same\" }" ); } @Test public void shouldUseSpecialNameForIdPropertyInWhereClause() { assertMongoDbQuery( "select e from IndexedEntity e where e.id = '1'", "{ \"_id\" : \"1\" }" ); } @Test public void shouldUseColumnNameForPropertyInWhereClause() { assertMongoDbQuery( "select e from IndexedEntity e where e.name = 'Bob'", "{ \"entityName\" : \"Bob\" }" ); } @Test public void shouldCreateProjectionQuery() { MongoDBQueryParsingResult parsingResult = parseQuery( "select e.id, e.name, e.position from IndexedEntity e" ); assertThat( parsingResult.getQuery().toJson() ).isEqualTo( "{ }" ); assertThat( parsingResult.getProjection().toJson() ).isEqualTo( "{ \"_id\" : 1, \"entityName\" : 1, \"position\" : 1 }" ); } @Test public void shouldAddNumberPropertyAsNumber() { assertMongoDbQuery( "select e from IndexedEntity e where e.position = 2", "{ \"position\" : { \"$numberLong\" : \"2\" } }" ); } @Test public void shouldCreateLessOrEqualQuery() { assertMongoDbQuery( "select e from IndexedEntity e where e.position <= 20", "{ \"position\" : { \"$lte\" : { \"$numberLong\" : \"20\" } } }" ); } @Test public void shouldCreateQueryWithNegationInWhereClause() { assertMongoDbQuery( "select e from IndexedEntity e where e.name <> 'Bob'", "{ \"entityName\" : { \"$ne\" : \"Bob\" } }" ); } @Test public void shouldCreateQueryWithNestedNegationInWhereClause() { assertMongoDbQuery( "select e from IndexedEntity e where NOT e.name <> 'Bob'", "{ \"entityName\" : \"Bob\" }" ); } @Test public void shouldCreateQueryUsingSelectWithConjunctionInWhereClause() { assertMongoDbQuery( "select e from IndexedEntity e where e.title = 'same' and e.position = 1", "{ \"$and\" : [" + "{ \"title\" : \"same\" }, " + "{ \"position\" : { \"$numberLong\" : \"1\" } }" + "] }" ); } @Test public void shouldCreateQueryWithNegationAndConjunctionInWhereClause() { assertMongoDbQuery( "select e from IndexedEntity e where NOT ( e.name = 'Bob' AND e.position = 1 )", "{ \"$or\" : [" + "{ \"entityName\" : { \"$ne\" : \"Bob\" } }, " + "{ \"position\" : { \"$ne\" : { \"$numberLong\" : \"1\" } } }" + "] }" ); } @Test public void shouldCreateNegatedRangeQuery() { assertMongoDbQuery( "select e from IndexedEntity e where e.name = 'Bob' and not e.position between 1 and 3", "{ \"$and\" : [" + "{ \"entityName\" : \"Bob\" }, " + "{ \"$or\" : [" + "{ \"position\" : { \"$lt\" : { \"$numberLong\" : \"1\" } } }, " + "{ \"position\" : { \"$gt\" : { \"$numberLong\" : \"3\" } } }" + "] }" + "] }" ); } @Test public void shouldCreateBooleanQueryUsingSelect() { assertMongoDbQuery( "select e from IndexedEntity e where e.name = 'same' or ( e.id = 4 and e.name = 'booh')", "{ \"$or\" : [" + "{ \"entityName\" : \"same\" }, " + "{ \"$and\" : [" + "{ \"_id\" : \"4\" }, " + "{ \"entityName\" : \"booh\" }" + "] }" + "] }" ); } @Test public void shouldCreateNumericBetweenQuery() { Map<String, Object> namedParameters = new HashMap<String, Object>(); namedParameters.put( "lower", 10L ); namedParameters.put( "upper", 20L ); assertMongoDbQuery( "select e from IndexedEntity e where e.position between :lower and :upper", namedParameters, "{ \"$and\" : [" + "{ \"position\" : { \"$gte\" : { \"$numberLong\" : \"10\" } } }, " + "{ \"position\" : { \"$lte\" : { \"$numberLong\" : \"20\" } } }" + "] }" ); } @Test public void shouldCreateInQuery() { assertMongoDbQuery( "select e from IndexedEntity e where e.title IN ( 'foo', 'bar', 'same')", "{ \"title\" : " + "{ \"$in\" : [\"foo\", \"bar\", \"same\"] }" + " }" ); } @Test public void shouldCreateNotInQuery() { assertMongoDbQuery( "select e from IndexedEntity e where e.title NOT IN ( 'foo', 'bar', 'same')", "{ \"title\" : " + "{ \"$nin\" : [\"foo\", \"bar\", \"same\"] }" + " }" ); } @Test public void shouldCreateLikeQuery() { assertMongoDbQuery( "select e from IndexedEntity e where e.title like 'Ali_e%'", "{ \"title\" : " + "{ \"$regex\" : \"^\\\\QAli\\\\E.\\\\Qe\\\\E.*$\", " + "\"$options\" : \"s\"" + " }" + " }" ); } @Test public void shouldCreateNotLikeQuery() { assertMongoDbQuery( "select e from IndexedEntity e where e.title not like 'Ali_e%'", "{ \"title\" : " + "{ \"$not\" : " + "{ \"$regex\" : \"^\\\\QAli\\\\E.\\\\Qe\\\\E.*$\", " + "\"$options\" : \"s\"" + " }" + " }" + " }" ); } @Test public void shouldCreateIsNullQuery() { assertMongoDbQuery( "select e from IndexedEntity e where e.title is null", "{ \"title\" : " + "{ \"$exists\" : false }" + " }" ); } @Test public void shouldCreateIsNotNullQuery() { assertMongoDbQuery( "select e from IndexedEntity e where e.title is not null", "{ \"title\" : " + "{ \"$exists\" : true }" + " }" ); } private void assertMongoDbQuery(String queryString, String expectedMongoDbQuery) { assertMongoDbQuery( queryString, null, expectedMongoDbQuery ); } private void assertMongoDbQuery(String queryString, String expectedMongoDbQuery, Class<?> expectedEntityType) { assertMongoDbQuery( queryString, null, expectedMongoDbQuery, expectedEntityType ); } private void assertMongoDbQuery(String queryString, Map<String, Object> namedParameters, String expectedMongoDbQuery) { assertMongoDbQuery( queryString, namedParameters, expectedMongoDbQuery, IndexedEntity.class ); } private void assertMongoDbQuery(String queryString, Map<String, Object> namedParameters, String expectedMongoDbQuery, Class<?> expectedEntityType) { MongoDBQueryParsingResult parsingResult = parseQuery( queryString, namedParameters ); assertThat( parsingResult ).isNotNull(); assertThat( parsingResult.getEntityType() ).isSameAs( expectedEntityType ); if ( expectedMongoDbQuery == null ) { assertThat( parsingResult.getQuery() ).isNull(); } else { assertThat( parsingResult.getQuery() ).isNotNull(); log.debugf( "expectedMongoDbQuery: %s", expectedMongoDbQuery ); log.debugf( " actualMongoDbQuery: %s", parsingResult.getQuery().toJson() ); assertThat( parsingResult.getQuery().toJson() ).isEqualTo( expectedMongoDbQuery ); } } private MongoDBQueryParsingResult parseQuery(String queryString) { return parseQuery( queryString, null ); } private MongoDBQueryParsingResult parseQuery(String queryString, Map<String, Object> namedParameters) { return queryParser.parseQuery( queryString, setUpMongoDbProcessingChain( namedParameters ) ); } private MongoDBProcessingChain setUpMongoDbProcessingChain(Map<String, Object> namedParameters) { Map<String, Class<?>> entityNames = new HashMap<String, Class<?>>(); entityNames.put( "com.acme.IndexedEntity", IndexedEntity.class ); entityNames.put( "IndexedEntity", IndexedEntity.class ); entityNames.put( "CommunityMemberST", CommunityMemberST.class ); entityNames.put( "PersonST", PersonST.class ); entityNames.put( "EmployeeST", EmployeeST.class ); EntityNamesResolver nameResolver = new MapBasedEntityNamesResolver( entityNames ); return new MongoDBProcessingChain( getSessionFactory(), nameResolver, namedParameters ); } @Override protected Class<?>[] getAnnotatedClasses() { return new Class<?>[]{ IndexedEntity.class, PersonST.class, CommunityMemberST.class, EmployeeST.class }; } }