From 596e0fc5cef2eb1ae4aba40343bf27e73aee86d6 Mon Sep 17 00:00:00 2001 From: Dave Johnson Date: Wed, 18 May 2016 17:24:25 -0400 Subject: [PATCH 1/7] Initial support for select mappings with entity object fields (i.e. nested fields) --- .../read/search/CandidateEntityFilter.java | 59 +++++++++++++- .../apache/usergrid/persistence/IndexIT.java | 80 +++++++++++++++++++ 2 files changed, 135 insertions(+), 4 deletions(-) diff --git a/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java b/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java index d47e96c2de..261259b1a0 100644 --- a/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java +++ b/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java @@ -27,7 +27,9 @@ import org.apache.usergrid.persistence.index.impl.IndexProducer; import org.apache.usergrid.persistence.model.field.DistanceField; import org.apache.usergrid.persistence.model.field.DoubleField; +import org.apache.usergrid.persistence.model.field.EntityObjectField; import org.apache.usergrid.persistence.model.field.Field; +import org.apache.usergrid.persistence.model.field.value.EntityObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -123,12 +125,26 @@ public Observable> call( if (mappings.size() > 0) { Map fieldMap = new HashMap(mappings.size()); rx.Observable.from(mappings) - .filter(mapping -> entity.getFieldMap().containsKey(mapping.getSourceFieldName())) + + .filter(mapping -> { + if ( entity.getFieldMap().containsKey(mapping.getSourceFieldName())) { + return true; + } + String[] parts = mapping.getSourceFieldName().split("\\."); + return nestedFieldCheck( parts, entity.getFieldMap() ); + }) + .doOnNext(mapping -> { Field field = entity.getField(mapping.getSourceFieldName()); - field.setName(mapping.getTargetFieldName()); - fieldMap.put(mapping.getTargetFieldName(),field); - }).toBlocking().last(); + if ( field != null ) { + field.setName( mapping.getTargetFieldName() ); + fieldMap.put( mapping.getTargetFieldName(), field ); + } else { + String[] parts = mapping.getSourceFieldName().split("\\."); + nestedFieldSet( fieldMap, parts, entity.getFieldMap() ); + } + }).toBlocking().lastOrDefault(null); + entity.setFieldMap(fieldMap); } return entityFilterResult; @@ -144,6 +160,41 @@ public Observable> call( } + private void nestedFieldSet( Map result, String[] parts, Map fieldMap) { + if ( parts.length > 0 ) { + if ( fieldMap.containsKey( parts[0] )) { + Field field = fieldMap.get( parts[0] ); + if ( field instanceof EntityObjectField ) { + EntityObjectField eof = (EntityObjectField)field; + if ( result.get( parts[0] ) == null ) { + result.put( parts[0], new EntityObjectField( parts[0], new EntityObject() ) ); + } + nestedFieldSet( + ((EntityObjectField)result.get( parts[0] )).getValue().getFieldMap(), + Arrays.copyOfRange(parts, 1, parts.length), + eof.getValue().getFieldMap()); + } else { + result.put( parts[0], field ); + } + } + } + } + + + private boolean nestedFieldCheck( String[] parts, Map fieldMap) { + if ( parts.length > 0 ) { + if ( fieldMap.containsKey( parts[0] )) { + Field field = fieldMap.get( parts[0] ); + if ( field instanceof EntityObjectField ) { + EntityObjectField eof = (EntityObjectField)field; + return nestedFieldCheck( Arrays.copyOfRange(parts, 1, parts.length), eof.getValue().getFieldMap()); + } else { + return true; + } + } + } + return false; + } /** diff --git a/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java b/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java index f4aa204ea7..7e38f1712f 100644 --- a/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java +++ b/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java @@ -17,6 +17,7 @@ package org.apache.usergrid.persistence; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; @@ -461,4 +462,83 @@ public void testPropertyUpdateWithConnectionEntityIndexEntryAudit() throws Excep } + + @Test + public void testSelectMappings() throws Exception { + + UUID applicationId = app.getId(); + + EntityManager em = setup.getEmf().getEntityManager(applicationId); + + Map entity1 = new HashMap() {{ + put("name","name_1"); + put("status", "pickled"); + put("data", new HashMap() {{ + put("xfactor", 5.1); + put("rando", "bar"); + }}); + }}; + em.create("names", entity1); + + Map entity2 = new HashMap() {{ + put("name","name_2"); + put("status", "pickled"); + put("data", new HashMap() {{ + put("xfactor", 5.1); + put("rando", "bar"); + }}); + }}; + em.create("names", entity2); + + app.refreshIndex(); + + { + Query query = Query.fromQL("select status where status = 'pickled'"); + Results r = em.searchCollection( em.getApplicationRef(), "names", query ); + assertTrue(r.getEntities() != null && r.getEntities().size() == 2); + Entity first = r.getEntities().get(0); + assertTrue(first.getDynamicProperties().size() == 2); + } + + { + Query query = Query.fromQL( "select status, data.rando where data.rando = 'bar'" ); + Results r = em.searchCollection( em.getApplicationRef(), "names", query ); + assertTrue( r.getEntities() != null && r.getEntities().size() == 2 ); + + Entity first = r.getEntities().get( 0 ); + + assertNotNull( first.getProperty("status") ); + assertEquals( first.getProperty("status"), "pickled" ); + + assertNotNull( first.getProperty("data") ); + assertEquals( ((Map)first.getProperty("data")).get("rando"), "bar" ); + + assertTrue( first.getDynamicProperties().size() == 3 ); + } + + { + // query for only one bogus field name should return empty entities + Query query = Query.fromQL( "select data.rando where status = 'pickled'" ); + Results r = em.searchCollection( em.getApplicationRef(), "names", query ); + assertTrue( r.getEntities() != null && r.getEntities().size() == 2 ); + + Entity first = r.getEntities().get( 0 ); + + assertNotNull( first.getProperty("data") ); + assertEquals( ((Map)first.getProperty("data")).get("rando"), "bar" ); + + assertTrue( first.getDynamicProperties().size() == 2 ); + } + + { + // query for only one bogus field name should return empty entities + Query query = Query.fromQL( "select data.bogusfieldname where status = 'pickled'" ); + Results r = em.searchCollection( em.getApplicationRef(), "names", query ); + assertTrue( r.getEntities() != null && r.getEntities().size() == 2 ); + Entity first = r.getEntities().get( 0 ); + assertTrue( first.getDynamicProperties().size() == 1 ); + } + + } + } From 3eec8e55eb57b889be1f2d0d13b44d20a98879dd Mon Sep 17 00:00:00 2001 From: Dave Johnson Date: Wed, 18 May 2016 18:58:17 -0400 Subject: [PATCH 2/7] Two fields. --- .../test/java/org/apache/usergrid/persistence/IndexIT.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java b/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java index 7e38f1712f..3b9f95a4f1 100644 --- a/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java +++ b/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java @@ -476,6 +476,7 @@ public void testSelectMappings() throws Exception { put("data", new HashMap() {{ put("xfactor", 5.1); put("rando", "bar"); + put("mondo", "2000"); }}); }}; em.create("names", entity1); @@ -486,6 +487,7 @@ public void testSelectMappings() throws Exception { put("data", new HashMap() {{ put("xfactor", 5.1); put("rando", "bar"); + put("mondo", "2001"); }}); }}; em.create("names", entity2); @@ -518,7 +520,7 @@ public void testSelectMappings() throws Exception { { // query for only one bogus field name should return empty entities - Query query = Query.fromQL( "select data.rando where status = 'pickled'" ); + Query query = Query.fromQL( "select data.rando,data.mondo where status = 'pickled'" ); Results r = em.searchCollection( em.getApplicationRef(), "names", query ); assertTrue( r.getEntities() != null && r.getEntities().size() == 2 ); @@ -526,6 +528,7 @@ public void testSelectMappings() throws Exception { assertNotNull( first.getProperty("data") ); assertEquals( ((Map)first.getProperty("data")).get("rando"), "bar" ); + assertEquals( ((Map)first.getProperty("data")).get("mondo"), "2001" ); assertTrue( first.getDynamicProperties().size() == 2 ); } From 0b4162035ee7d54199bd2250c2b3ac31354516b1 Mon Sep 17 00:00:00 2001 From: Dave Johnson Date: Thu, 19 May 2016 08:57:42 -0400 Subject: [PATCH 3/7] Test for doubly nested field, plus some formatting changes. --- .../read/search/CandidateEntityFilter.java | 16 ++++++----- .../apache/usergrid/persistence/IndexIT.java | 27 ++++++++++++++----- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java b/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java index 261259b1a0..e02e6c7059 100644 --- a/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java +++ b/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java @@ -52,9 +52,9 @@ /** - * Loads entities from an incoming CandidateResult emissions into entities, then streams them on - * performs internal buffering for efficiency. Note that all entities may not be emitted if our load crosses page boundaries. It is up to the - * collector to determine when to stop streaming entities. + * Loads entities from an incoming CandidateResult emissions into entities, then streams them on performs internal + * buffering for efficiency. Note that all entities may not be emitted if our load crosses page boundaries. + * It is up to the collector to determine when to stop streaming entities. */ public class CandidateEntityFilter extends AbstractFilter, FilterResult> { @@ -93,11 +93,12 @@ public Observable> call( entityCollectionManagerFactory.createCollectionManager( applicationScope ); - final EntityIndex applicationIndex = - entityIndexFactory.createEntityIndex(indexLocationStrategyFactory.getIndexLocationStrategy(applicationScope) ); + final EntityIndex applicationIndex = entityIndexFactory + .createEntityIndex(indexLocationStrategyFactory.getIndexLocationStrategy(applicationScope) ); //buffer them to get a page size we can make 1 network hop - final Observable> searchIdSetObservable = candidateResultsObservable.buffer( pipelineContext.getLimit() ) + final Observable> searchIdSetObservable = + candidateResultsObservable.buffer( pipelineContext.getLimit() ) //load them .flatMap( candidateResults -> { @@ -198,7 +199,8 @@ private boolean nestedFieldCheck( String[] parts, Map fieldMap) { /** - * Our collector to collect entities. Not quite a true collector, but works within our operational flow as this state is mutable and difficult to represent functionally + * Our collector to collect entities. Not quite a true collector, but works within our operational + * flow as this state is mutable and difficult to represent functionally */ private static final class EntityVerifier { diff --git a/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java b/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java index 3b9f95a4f1..aaf4c339b6 100644 --- a/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java +++ b/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java @@ -477,6 +477,10 @@ public void testSelectMappings() throws Exception { put("xfactor", 5.1); put("rando", "bar"); put("mondo", "2000"); + put("frosting", "chocolate"); + put("misc", new HashMap() {{ + put("range", "open"); + }}); }}); }}; em.create("names", entity1); @@ -488,12 +492,17 @@ public void testSelectMappings() throws Exception { put("xfactor", 5.1); put("rando", "bar"); put("mondo", "2001"); + put("frosting", "orange"); + put("misc", new HashMap() {{ + put("range", "open"); + }}); }}); }}; em.create("names", entity2); app.refreshIndex(); + // simple single-field select mapping { Query query = Query.fromQL("select status where status = 'pickled'"); Results r = em.searchCollection( em.getApplicationRef(), "names", query ); @@ -502,6 +511,7 @@ public void testSelectMappings() throws Exception { assertTrue(first.getDynamicProperties().size() == 2); } + // simple single-field plus nested field select mapping { Query query = Query.fromQL( "select status, data.rando where data.rando = 'bar'" ); Results r = em.searchCollection( em.getApplicationRef(), "names", query ); @@ -518,23 +528,28 @@ public void testSelectMappings() throws Exception { assertTrue( first.getDynamicProperties().size() == 3 ); } + // multiple nested fields with one doubly-nesed field { - // query for only one bogus field name should return empty entities - Query query = Query.fromQL( "select data.rando,data.mondo where status = 'pickled'" ); + Query query = Query.fromQL( "select data.rando, data.mondo, data.misc.range where status = 'pickled'" ); Results r = em.searchCollection( em.getApplicationRef(), "names", query ); assertTrue( r.getEntities() != null && r.getEntities().size() == 2 ); Entity first = r.getEntities().get( 0 ); - assertNotNull( first.getProperty("data") ); - assertEquals( ((Map)first.getProperty("data")).get("rando"), "bar" ); - assertEquals( ((Map)first.getProperty("data")).get("mondo"), "2001" ); + Map data = ((Map)first.getProperty("data")); + assertNotNull( data ); + assertEquals( data.get("rando"), "bar" ); + assertEquals( data.get("mondo"), "2001" ); + assertNull( data.get("frosting") ); + + Map misc = (Map)data.get("misc"); + assertEquals( misc.get("range"), "open" ); assertTrue( first.getDynamicProperties().size() == 2 ); } + // query for one bogus field name should return empty entities { - // query for only one bogus field name should return empty entities Query query = Query.fromQL( "select data.bogusfieldname where status = 'pickled'" ); Results r = em.searchCollection( em.getApplicationRef(), "names", query ); assertTrue( r.getEntities() != null && r.getEntities().size() == 2 ); From e9e7c39c46ca0b49b5891ee04bdac648e9777cee Mon Sep 17 00:00:00 2001 From: Dave Johnson Date: Fri, 20 May 2016 16:00:21 -0400 Subject: [PATCH 4/7] Minor refactoring and formatting changes. --- .../pipeline/read/search/CandidateEntityFilter.java | 12 +++++++++--- .../org/apache/usergrid/persistence/IndexIT.java | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java b/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java index e02e6c7059..4aa6c8dcc4 100644 --- a/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java +++ b/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java @@ -163,17 +163,19 @@ public Observable> call( private void nestedFieldSet( Map result, String[] parts, Map fieldMap) { if ( parts.length > 0 ) { + if ( fieldMap.containsKey( parts[0] )) { Field field = fieldMap.get( parts[0] ); if ( field instanceof EntityObjectField ) { EntityObjectField eof = (EntityObjectField)field; - if ( result.get( parts[0] ) == null ) { - result.put( parts[0], new EntityObjectField( parts[0], new EntityObject() ) ); - } + result.putIfAbsent( parts[0], new EntityObjectField( parts[0], new EntityObject() ) ); + + // recursion nestedFieldSet( ((EntityObjectField)result.get( parts[0] )).getValue().getFieldMap(), Arrays.copyOfRange(parts, 1, parts.length), eof.getValue().getFieldMap()); + } else { result.put( parts[0], field ); } @@ -184,11 +186,15 @@ private void nestedFieldSet( Map result, String[] parts, Map fieldMap) { if ( parts.length > 0 ) { + if ( fieldMap.containsKey( parts[0] )) { Field field = fieldMap.get( parts[0] ); if ( field instanceof EntityObjectField ) { EntityObjectField eof = (EntityObjectField)field; + + // recursion return nestedFieldCheck( Arrays.copyOfRange(parts, 1, parts.length), eof.getValue().getFieldMap()); + } else { return true; } diff --git a/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java b/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java index aaf4c339b6..d62f88ef2a 100644 --- a/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java +++ b/stack/core/src/test/java/org/apache/usergrid/persistence/IndexIT.java @@ -528,7 +528,7 @@ public void testSelectMappings() throws Exception { assertTrue( first.getDynamicProperties().size() == 3 ); } - // multiple nested fields with one doubly-nesed field + // multiple nested fields with one doubly-nested field { Query query = Query.fromQL( "select data.rando, data.mondo, data.misc.range where status = 'pickled'" ); Results r = em.searchCollection( em.getApplicationRef(), "names", query ); From 5452d68c29bb59ec8794639cdc8d6d819792e8bb Mon Sep 17 00:00:00 2001 From: Dave Johnson Date: Wed, 1 Jun 2016 09:56:01 -0400 Subject: [PATCH 5/7] Ensure that field names are treated consistently and in a case-insensitive way. --- .../read/search/CandidateEntityFilter.java | 13 +++ .../model/field/value/EntityObject.java | 15 ++- .../queries/SelectMappingsQueryTest.java | 105 ++++++++++++++++++ 3 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 stack/rest/src/test/java/org/apache/usergrid/rest/applications/queries/SelectMappingsQueryTest.java diff --git a/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java b/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java index 4aa6c8dcc4..77704361eb 100644 --- a/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java +++ b/stack/core/src/main/java/org/apache/usergrid/corepersistence/pipeline/read/search/CandidateEntityFilter.java @@ -161,6 +161,13 @@ public Observable> call( } + /** + * Sets field in result map with support for nested fields via recursion. + * + * @param result The result map of filtered fields + * @param parts The parts of the field name (more than one if field is nested) + * @param fieldMap Map of fields of the object + */ private void nestedFieldSet( Map result, String[] parts, Map fieldMap) { if ( parts.length > 0 ) { @@ -184,6 +191,12 @@ private void nestedFieldSet( Map result, String[] parts, Map fieldMap) { if ( parts.length > 0 ) { diff --git a/stack/corepersistence/model/src/main/java/org/apache/usergrid/persistence/model/field/value/EntityObject.java b/stack/corepersistence/model/src/main/java/org/apache/usergrid/persistence/model/field/value/EntityObject.java index db44e87adf..a157029aa7 100644 --- a/stack/corepersistence/model/src/main/java/org/apache/usergrid/persistence/model/field/value/EntityObject.java +++ b/stack/corepersistence/model/src/main/java/org/apache/usergrid/persistence/model/field/value/EntityObject.java @@ -19,10 +19,7 @@ package org.apache.usergrid.persistence.model.field.value; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import org.apache.usergrid.persistence.model.field.Field; @@ -41,11 +38,19 @@ public class EntityObject implements Serializable { @JsonIgnore private long size; + // field names are treated in case-insensitive way by design + static class CaseInsensitiveComparator implements Comparator { + public int compare(String o1, String o2) { + return o1.compareToIgnoreCase(o2); + } + } + public static final CaseInsensitiveComparator INSTANCE = new CaseInsensitiveComparator(); + /** * Fields the users can set */ @JsonTypeInfo( use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class" ) - private final Map fields = new HashMap(); + private Map fields = new TreeMap(INSTANCE); /** * Add the field, return the old one if it existed diff --git a/stack/rest/src/test/java/org/apache/usergrid/rest/applications/queries/SelectMappingsQueryTest.java b/stack/rest/src/test/java/org/apache/usergrid/rest/applications/queries/SelectMappingsQueryTest.java new file mode 100644 index 0000000000..eb6aeee18e --- /dev/null +++ b/stack/rest/src/test/java/org/apache/usergrid/rest/applications/queries/SelectMappingsQueryTest.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.usergrid.rest.applications.queries; + +import org.apache.usergrid.rest.test.resource.model.Collection; +import org.apache.usergrid.rest.test.resource.model.Entity; +import org.apache.usergrid.rest.test.resource.model.QueryParameters; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Iterator; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + + +public class SelectMappingsQueryTest extends QueryTestBase { + private static final Logger logger = LoggerFactory.getLogger(OrderByTest.class); + + + @Test + public void testNestedSelectFieldNames() throws Exception { + + generateTestEntities(20, "things"); + + QueryParameters params = new QueryParameters() + .setQuery("select actor.displayName,sometestprop where sometestprop = 'testprop'"); + Collection things = this.app().collection("things").get(params); + assertEquals( 10, things.getNumOfEntities() ); + + Iterator iter = things.iterator(); + while ( iter.hasNext() ) { + + Entity entity = iter.next(); + assertEquals( 5, entity.getDynamicProperties().size() ); + + assertNotNull( entity.getDynamicProperties().get("uuid") ); + assertNotNull( entity.getDynamicProperties().get("type") ); + assertNotNull( entity.getDynamicProperties().get("metadata") ); + assertNotNull( entity.getDynamicProperties().get("sometestprop") ); + + Map actor = (Map)entity.getDynamicProperties().get("actor"); + assertNotNull( actor ); + assertNotNull( actor.get("displayName") ); + + } + } + + + /** + * Shows that field names are case-insensitive. + * If you define two fields with same name but different cases, behavior is undefined. + */ + @Test + public void testFieldNameCaseSensitivity() throws Exception { + + int numberOfEntities = 10; + String collectionName = "things"; + + Entity[] entities = new Entity[numberOfEntities]; + Entity props = new Entity(); + + for (int i = 0; i < numberOfEntities; i++) { + props.put("testProp", "a"); + props.put("testprop", "b"); + entities[i] = app().collection(collectionName).post(props); + } + refreshIndex(); + + { + QueryParameters params = new QueryParameters() + .setQuery( "select * where testProp = 'b'" ); + Collection things = this.app().collection( "things" ).get( params ); + + // if field names were case sensitive, this would fail + assertEquals( numberOfEntities, things.getNumOfEntities() ); + } + + { + QueryParameters params = new QueryParameters() + .setQuery( "select * where testprop='b'" ); + Collection things = this.app().collection( "things" ).get( params ); + + assertEquals( numberOfEntities, things.getNumOfEntities() ); + } + + } + +} From e9228e111f338aabf34b334e2661e1966116f23a Mon Sep 17 00:00:00 2001 From: Dave Johnson Date: Thu, 2 Jun 2016 15:33:28 -0400 Subject: [PATCH 6/7] Tests to illustrate case-insensitive handling of Entity field names. --- .../queries/SelectMappingsQueryTest.java | 79 +++++++++++++------ 1 file changed, 54 insertions(+), 25 deletions(-) diff --git a/stack/rest/src/test/java/org/apache/usergrid/rest/applications/queries/SelectMappingsQueryTest.java b/stack/rest/src/test/java/org/apache/usergrid/rest/applications/queries/SelectMappingsQueryTest.java index eb6aeee18e..4a291b66c8 100644 --- a/stack/rest/src/test/java/org/apache/usergrid/rest/applications/queries/SelectMappingsQueryTest.java +++ b/stack/rest/src/test/java/org/apache/usergrid/rest/applications/queries/SelectMappingsQueryTest.java @@ -16,6 +16,7 @@ */ package org.apache.usergrid.rest.applications.queries; +import org.apache.commons.lang.RandomStringUtils; import org.apache.usergrid.rest.test.resource.model.Collection; import org.apache.usergrid.rest.test.resource.model.Entity; import org.apache.usergrid.rest.test.resource.model.QueryParameters; @@ -34,6 +35,9 @@ public class SelectMappingsQueryTest extends QueryTestBase { private static final Logger logger = LoggerFactory.getLogger(OrderByTest.class); + /** + * Select field mappings may include nested entity fields. + */ @Test public void testNestedSelectFieldNames() throws Exception { @@ -64,42 +68,67 @@ public void testNestedSelectFieldNames() throws Exception { /** - * Shows that field names are case-insensitive. - * If you define two fields with same name but different cases, behavior is undefined. + * When entity posted with two duplicate names with different cases, last one wins. */ @Test - public void testFieldNameCaseSensitivity() throws Exception { + public void testMixedCaseDupField() throws Exception { - int numberOfEntities = 10; String collectionName = "things"; - Entity[] entities = new Entity[numberOfEntities]; - Entity props = new Entity(); + String value = RandomStringUtils.randomAlphabetic( 20 ); + String otherValue = RandomStringUtils.randomAlphabetic( 20 ); - for (int i = 0; i < numberOfEntities; i++) { - props.put("testProp", "a"); - props.put("testprop", "b"); - entities[i] = app().collection(collectionName).post(props); - } + // create entity with testProp=value + Entity entity = new Entity() + .withProp( "testProp", value ) + .withProp( "TESTPROP", otherValue); + app().collection( collectionName ).post( entity ); refreshIndex(); - { - QueryParameters params = new QueryParameters() - .setQuery( "select * where testProp = 'b'" ); - Collection things = this.app().collection( "things" ).get( params ); + // testProp and TESTPROP should now have otherValue - // if field names were case sensitive, this would fail - assertEquals( numberOfEntities, things.getNumOfEntities() ); - } + QueryParameters params = new QueryParameters() + .setQuery( "select * where testProp='" + otherValue + "'" ); + Collection things = this.app().collection( "things" ).get( params ); + assertEquals( 1, things.getNumOfEntities() ); + + params = new QueryParameters() + .setQuery( "select * where TESTPROP='" + otherValue + "'" ); + things = app().collection( "things" ).get( params ); + assertEquals( 1, things.getNumOfEntities() ); + } - { - QueryParameters params = new QueryParameters() - .setQuery( "select * where testprop='b'" ); - Collection things = this.app().collection( "things" ).get( params ); - assertEquals( numberOfEntities, things.getNumOfEntities() ); - } + /** + * Field named testProp can be over-written by field named TESTPROP. + */ + @Test + public void testFieldOverride() throws Exception { - } + String collectionName = "things"; + // create entity with testProp=value + String value = RandomStringUtils.randomAlphabetic( 20 ); + Entity entity = new Entity().withProp( "testProp", value ); + app().collection( collectionName ).post( entity ); + refreshIndex(); + + // override with TESTPROP=newValue + String newValue = RandomStringUtils.randomAlphabetic( 20 ); + entity = new Entity().withProp( "TESTPROP", newValue ); + app().collection( collectionName ).post( entity ); + refreshIndex(); + + // testProp and TESTPROP should now have newValue + + QueryParameters params = new QueryParameters() + .setQuery( "select * where testProp='" + newValue + "'" ); + Collection things = this.app().collection( "things" ).get( params ); + assertEquals( 1, things.getNumOfEntities() ); + + params = new QueryParameters() + .setQuery( "select * where TESTPROP='" + newValue + "'" ); + things = app().collection( "things" ).get( params ); + assertEquals( 1, things.getNumOfEntities() ); + } } From 317714bc4e5be27a220420c4df4ab281309ba086 Mon Sep 17 00:00:00 2001 From: Dave Johnson Date: Fri, 3 Jun 2016 08:30:42 -0400 Subject: [PATCH 7/7] Additional test. --- .../queries/SelectMappingsQueryTest.java | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/stack/rest/src/test/java/org/apache/usergrid/rest/applications/queries/SelectMappingsQueryTest.java b/stack/rest/src/test/java/org/apache/usergrid/rest/applications/queries/SelectMappingsQueryTest.java index 4a291b66c8..fd33c15424 100644 --- a/stack/rest/src/test/java/org/apache/usergrid/rest/applications/queries/SelectMappingsQueryTest.java +++ b/stack/rest/src/test/java/org/apache/usergrid/rest/applications/queries/SelectMappingsQueryTest.java @@ -103,7 +103,7 @@ public void testMixedCaseDupField() throws Exception { * Field named testProp can be over-written by field named TESTPROP. */ @Test - public void testFieldOverride() throws Exception { + public void testFieldOverride1() throws Exception { String collectionName = "things"; @@ -119,7 +119,7 @@ public void testFieldOverride() throws Exception { app().collection( collectionName ).post( entity ); refreshIndex(); - // testProp and TESTPROP should now have newValue + // testProp and TESTPROP should new be queryable by new value QueryParameters params = new QueryParameters() .setQuery( "select * where testProp='" + newValue + "'" ); @@ -131,4 +131,38 @@ public void testFieldOverride() throws Exception { things = app().collection( "things" ).get( params ); assertEquals( 1, things.getNumOfEntities() ); } + + /** + * Field named testProp can be over-written by field named TESTPROP. + */ + @Test + public void testFieldOverride2() throws Exception { + + String collectionName = "things"; + + // create entity with TESTPROP=value + String value = RandomStringUtils.randomAlphabetic( 20 ); + Entity entity = new Entity().withProp( "TESTPROP", value ); + app().collection( collectionName ).post( entity ); + refreshIndex(); + + // override with testProp=newValue + String newValue = RandomStringUtils.randomAlphabetic( 20 ); + entity = new Entity().withProp( "testProp", newValue ); + app().collection( collectionName ).post( entity ); + refreshIndex(); + + // testProp and TESTPROP should new be queryable by new value + + QueryParameters params = new QueryParameters() + .setQuery( "select * where testProp='" + newValue + "'" ); + Collection things = this.app().collection( "things" ).get( params ); + assertEquals( 1, things.getNumOfEntities() ); + + params = new QueryParameters() + .setQuery( "select * where TESTPROP='" + newValue + "'" ); + things = app().collection( "things" ).get( params ); + assertEquals( 1, things.getNumOfEntities() ); + } + }