/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. 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. For additional information regarding * copyright in this work, please see the NOTICE file in the top level * directory of this distribution. */ package org.apache.usergrid.persistence.index.impl; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicLong; import com.google.common.base.Optional; import org.apache.usergrid.persistence.core.CassandraFig; import org.apache.usergrid.persistence.index.*; import org.apache.usergrid.persistence.model.field.*; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.commons.lang3.time.StopWatch; import org.apache.usergrid.persistence.core.scope.ApplicationScopeImpl; import org.apache.usergrid.persistence.core.test.UseModules; import org.apache.usergrid.persistence.core.util.Health; import org.apache.usergrid.persistence.index.guice.TestIndexModule; import org.apache.usergrid.persistence.index.utils.UUIDUtils; import org.apache.usergrid.persistence.model.entity.Entity; import org.apache.usergrid.persistence.model.entity.Id; import org.apache.usergrid.persistence.model.entity.SimpleId; import org.apache.usergrid.persistence.model.util.EntityUtils; import org.apache.usergrid.persistence.model.util.UUIDGenerator; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Maps; import com.google.inject.Inject; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @RunWith( EsRunner.class ) @UseModules( { TestIndexModule.class } ) public class EntityIndexTest extends BaseIT { private static final Logger logger = LoggerFactory.getLogger(EntityIndexTest.class); @Inject public EntityIndexFactory eif; @Inject public IndexFig fig; @Inject public IndexProducer indexProducer; @Inject public CassandraFig cassandraFig; @Inject @Rule public ElasticSearchRule elasticSearchRule; private EntityIndex entityIndex; private SimpleId appId; @Before public void setup(){ appId = new SimpleId(UUID.randomUUID(), "application" ); IndexLocationStrategy strategy = new TestIndexIdentifier(cassandraFig,fig,new ApplicationScopeImpl(appId)); entityIndex = eif.createEntityIndex( strategy ); } @Test public void testIndex() throws IOException, InterruptedException { final String entityType = "thing"; IndexEdge searchEdge = new IndexEdgeImpl( appId, "things", SearchEdge.NodeType.SOURCE, 1 ); final SearchTypes searchTypes = SearchTypes.fromTypes( entityType ); insertJsonBlob( entityType, searchEdge, "/sample-large.json", 101, 0 ); testQueries( searchEdge, searchTypes ); } /** * Tests that when types conflict, but should match queries they work */ @Test public void testIndexVariations() throws IOException { Id appId = new SimpleId( "application" ); final String entityType = "thing"; IndexEdge indexEdge = new IndexEdgeImpl( appId, "things", SearchEdge.NodeType.SOURCE, 1 ); final SearchTypes searchTypes = SearchTypes.fromTypes( entityType ); EntityIndexBatch batch = entityIndex.createBatch(); UUID uuid = UUID.randomUUID(); Entity entity1 = new Entity( entityType ); EntityUtils.setVersion(entity1, UUIDGenerator.newTimeUUID()); entity1.setField(new UUIDField(IndexingUtils.ENTITY_ID_FIELDNAME, UUID.randomUUID())); entity1.setField( new StringField( "testfield", "test" ) ); entity1.setField(new IntegerField("ordinal", 0)); entity1.setField(new UUIDField("testuuid", uuid)); entity1.setField( new NullField("nullfield")); batch.index( indexEdge, entity1 ); indexProducer.put(batch.build()).subscribe(); Entity entity2 = new Entity( entityType ); EntityUtils.setVersion(entity2, UUIDGenerator.newTimeUUID()); List<String> list = new ArrayList<>(); list.add("test"); entity2.setField(new ArrayField<>("testfield", list)); entity2.setField(new IntegerField("ordinal", 1)); batch.index( indexEdge, entity2 ); indexProducer.put(batch.build()).subscribe();; entityIndex.refreshAsync().toBlocking().first(); StopWatch timer = new StopWatch(); timer.start(); CandidateResults candidateResults = entityIndex.search( indexEdge, searchTypes, "select * where testfield = 'test' order by ordinal", 100, 0, false ); timer.stop(); assertEquals( 2, candidateResults.size() ); logger.debug( "Query time {}ms", timer.getTime() ); final CandidateResult candidate1 = candidateResults.get(0); //check the id and version assertEquals( entity1.getId(), candidate1.getId() ); assertEquals(entity1.getVersion(), candidate1.getVersion()); final CandidateResult candidate2 = candidateResults.get(1); //check the id and version assertEquals( entity2.getId(), candidate2.getId() ); assertEquals( entity2.getVersion(), candidate2.getVersion() ); //make sure we can query uuids out as strings and not wrapped candidateResults = entityIndex.search( indexEdge, searchTypes, "select * where testuuid = '"+uuid+"'", 100, 0, false ); assertEquals(entity1.getId(),candidateResults.get(0).getId()); candidateResults = entityIndex.search( indexEdge, searchTypes, "select * where testuuid = "+uuid, 100, 0, false); assertEquals(entity1.getId(),candidateResults.get(0).getId()); } @Test public void testIndexThreads() throws IOException { long now = System.currentTimeMillis(); final int threads = 20; final int size = 30; final String entityType = "thing"; final CountDownLatch latch = new CountDownLatch( threads ); final AtomicLong failTime = new AtomicLong( 0 ); InputStream is = this.getClass().getResourceAsStream("/sample-large.json"); ObjectMapper mapper = new ObjectMapper(); final List<Object> sampleJson = mapper.readValue( is, new TypeReference<List<Object>>() {} ); for ( int i = 0; i < threads; i++ ) { final IndexEdge indexEdge = new IndexEdgeImpl( appId, "things", SearchEdge.NodeType.SOURCE, i ); Thread thread = new Thread( () -> { try { EntityIndexBatch batch = entityIndex.createBatch(); insertJsonBlob( sampleJson, batch, entityType, indexEdge, size, 0 ); indexProducer.put(batch.build()).subscribe();; } catch ( Exception e ) { synchronized ( failTime ) { if ( failTime.get() == 0 ) { failTime.set( System.currentTimeMillis() ); } } System.out.println( e.toString() ); fail( "threw exception" ); } finally { latch.countDown(); } } ); thread.start(); } try { latch.await(); } catch ( InterruptedException ie ) { throw new RuntimeException( ie ); } assertTrue( "system must have failed at " + ( failTime.get() - now ), failTime.get() == 0 ); } @Test public void testAddMultipleIndexes() throws IOException { final String entityType = "thing"; IndexEdge searchEdge = new IndexEdgeImpl( appId, "things", SearchEdge.NodeType.SOURCE, 10 ); final SearchTypes searchTypes = SearchTypes.fromTypes(entityType); insertJsonBlob( entityType, searchEdge, "/sample-large.json", 101, 0); testQueries(searchEdge, searchTypes); entityIndex.addIndex(UUID.randomUUID()+"_v2", 1, 0, "one"); insertJsonBlob( entityType, searchEdge, "/sample-large.json", 101, 100); //Hilda Youn testQuery( searchEdge, searchTypes, "name = 'Hilda Young'", 1 ); testQuery( searchEdge, searchTypes, "name = 'Lowe Kelley'", 1 ); logger.info("hi"); } @Test public void testDeleteWithAlias() throws IOException { final String entityType = "thing"; IndexEdge searchEdge = new IndexEdgeImpl( appId, "things", SearchEdge.NodeType.SOURCE, 1 ); final SearchTypes searchTypes = SearchTypes.fromTypes( entityType ); insertJsonBlob( entityType, searchEdge, "/sample-large.json", 1, 0 ); entityIndex.addIndex(UUID.randomUUID() + "v2", 1, 0, "one"); entityIndex.refreshAsync().toBlocking().first(); insertJsonBlob( entityType, searchEdge, "/sample-large.json", 1, 1 ); CandidateResults crs = testQuery( searchEdge, searchTypes, "name = 'Bowers Oneil'", 1 ); EntityIndexBatch entityIndexBatch = entityIndex.createBatch(); entityIndexBatch.deindex(searchEdge, crs.get(0)); indexProducer.put(entityIndexBatch.build()).subscribe(); entityIndex.refreshAsync().toBlocking().first(); //Hilda Youn testQuery(searchEdge, searchTypes, "name = 'Bowers Oneil'", 0); } private void insertJsonBlob( String entityType, IndexEdge indexEdge, String filePath, final int max, final int startIndex ) throws IOException { InputStream is = this.getClass().getResourceAsStream( filePath ); ObjectMapper mapper = new ObjectMapper(); List<Object> sampleJson = mapper.readValue(is, new TypeReference<List<Object>>() { }); EntityIndexBatch batch = entityIndex.createBatch(); insertJsonBlob(sampleJson, batch, entityType, indexEdge, max, startIndex); indexProducer.put(batch.build()).subscribe();; EntityIndex.IndexRefreshCommandInfo info = entityIndex.refreshAsync().toBlocking().first(); long time = info.getExecutionTime(); logger.info("refresh took ms:" + time); } private void insertJsonBlob( List<Object> sampleJson, EntityIndexBatch batch, String entityType, IndexEdge indexEdge, final int max, final int startIndex ) throws IOException { int count = 0; StopWatch timer = new StopWatch(); timer.start(); if ( startIndex > 0 ) { for ( int i = 0; i < startIndex; i++ ) { sampleJson.remove( 0 ); } } for ( Object o : sampleJson ) { Map<String, Object> item = ( Map<String, Object> ) o; Entity entity = new Entity( entityType ); entity = EntityIndexMapUtils.fromMap( entity, item ); EntityUtils.setVersion( entity, UUIDGenerator.newTimeUUID() ); entity.setField( new UUIDField( IndexingUtils.ENTITY_ID_FIELDNAME, UUID.randomUUID() ) ); batch.index( indexEdge, entity ); if ( ++count > max ) { break; } } timer.stop(); logger.info( "Total time to index {} entries {}ms, average {}ms/entry", new Object[] { count, timer.getTime(), timer.getTime() / count } ); } @Test public void testDeindex() { IndexEdge searchEdge = new IndexEdgeImpl( appId, "fastcars", SearchEdge.NodeType.SOURCE, 1 ); Map entityMap = new HashMap() {{ put( "name", "Ferrari 212 Inter" ); put( "introduced", 1952 ); put( "topspeed", 215 ); }}; Entity entity = EntityIndexMapUtils.fromMap( entityMap ); EntityUtils.setId(entity, new SimpleId( "fastcar" ) ); EntityUtils.setVersion(entity, UUIDGenerator.newTimeUUID() ); entity.setField(new UUIDField(IndexingUtils.ENTITY_ID_FIELDNAME, UUID.randomUUID() ) ); indexProducer.put(entityIndex.createBatch().index( searchEdge, entity ).build()).subscribe(); entityIndex.refreshAsync().toBlocking().first(); CandidateResults candidateResults = entityIndex .search( searchEdge, SearchTypes.fromTypes( entity.getId().getType() ), "name contains 'Ferrari*'", 10, 0, false ); assertEquals( 1, candidateResults.size() ); EntityIndexBatch batch = entityIndex.createBatch(); batch.deindex( searchEdge, entity ); indexProducer.put(batch.build()).subscribe();; entityIndex.refreshAsync().toBlocking().first(); candidateResults = entityIndex .search(searchEdge, SearchTypes.fromTypes( entity.getId().getType() ), "name contains 'Ferrari*'", 10, 0, false ); assertEquals(0, candidateResults.size()); } private CandidateResults testQuery( final SearchEdge scope, final SearchTypes searchTypes, final String queryString, final int num ) { StopWatch timer = new StopWatch(); timer.start(); CandidateResults candidateResults = entityIndex.search( scope, searchTypes, queryString, 1000, 0, false ); timer.stop(); assertEquals(num, candidateResults.size()); logger.debug("Query time {}ms", timer.getTime()); return candidateResults; } private void testQueries( final SearchEdge scope, SearchTypes searchTypes) { testQuery( scope, searchTypes, "age > 35", 29 ); testQuery( scope, searchTypes, "age <= 35", 73 ); testQuery( scope, searchTypes, "age <= 35 or age > 35", 102 ); // TODO: uncomment this test when you are ready to fix USERGRID-1314 // (https://issues.apache.org/jira/browse/USERGRID-1314) // testQuery( scope, searchTypes, "name = 'astro*' or age > 35", 29 ); testQuery( scope, searchTypes, "name = 'Morgan Pierce'", 1 ); testQuery( scope, searchTypes, "name = 'morgan pierce'", 1 ); testQuery( scope, searchTypes, "name = 'Morgan'", 0 ); testQuery( scope, searchTypes, "name contains 'Morgan'", 1 ); testQuery( scope, searchTypes, "company > 'GeoLogix'", 64 ); testQuery( scope, searchTypes, "gender = 'female'", 45 ); testQuery( scope, searchTypes, "name = 'Minerva Harrell' and age > 39", 1 ); testQuery( scope, searchTypes, "name = 'Minerva Harrell' and age > 39 and age < 41", 1 ); testQuery( scope, searchTypes, "name = 'Minerva Harrell' and age > 40", 0 ); testQuery( scope, searchTypes, "name = 'Minerva Harrell' and age >= 40", 1 ); testQuery( scope, searchTypes, "name = 'Minerva Harrell' and age <= 40", 1 ); testQuery( scope, searchTypes, "name = 'Morgan* '", 1 ); testQuery( scope, searchTypes, "name = 'Morgan*'", 1 ); // test a couple of array sub-property queries int totalUsers = 102; // nobody has a friend named Jack the Ripper testQuery( scope, searchTypes, "friends.name = 'Jack the Ripper'", 0 ); // everybody doesn't have a friend named Jack the Ripper testQuery( scope, searchTypes, "not (friends.name = 'Jack the Ripper')", totalUsers ); // one person has a friend named Shari Hahn testQuery( scope, searchTypes, "friends.name = 'Wendy Moody'", 1 ); // everybody but 1 doesn't have a friend named Shari Hahh testQuery( scope, searchTypes, "not (friends.name = 'Shari Hahn')", totalUsers - 1 ); } /** * Tests that Entity-to-map and Map-to-entity round trip works. */ @Test public void testEntityIndexMapUtils() throws IOException { InputStream is = this.getClass().getResourceAsStream( "/sample-small.json" ); ObjectMapper mapper = new ObjectMapper(); List<Object> contacts = mapper.readValue( is, new TypeReference<List<Object>>() {} ); for ( Object o : contacts ) { Map<String, Object> map1 = ( Map<String, Object> ) o; // convert map to entity Entity entity1 = EntityIndexMapUtils.fromMap( map1 ); // convert entity back to map Map map2 = EntityIndexMapUtils.toMap( entity1 ); // the two maps should be the same Map diff = Maps.difference( map1, map2 ).entriesDiffering(); assertEquals( 0, diff.size() ); } } @Test public void deleteVerification() throws Throwable { Id ownerId = new SimpleId( "owner" ); IndexEdge indexSCope = new IndexEdgeImpl( ownerId, "user", SearchEdge.NodeType.SOURCE, 10 ); final String middleName = "middleName" + UUIDUtils.newTimeUUID(); Map entityMap = new HashMap() {{ put( "username", "edanuff" ); put( "email", "ed@anuff.com" ); put( "middlename", middleName ); }}; Entity user = EntityIndexMapUtils.fromMap( entityMap ); EntityUtils.setId( user, new SimpleId( "edanuff" ) ); EntityUtils.setVersion( user, UUIDGenerator.newTimeUUID() ); EntityIndexBatch batch = entityIndex.createBatch(); batch.index( indexSCope, user ); indexProducer.put(batch.build()).subscribe();; entityIndex.refreshAsync().toBlocking().first(); final String query = "where username = 'edanuff'"; CandidateResults r = entityIndex.search( indexSCope, SearchTypes.fromTypes( "edanuff" ), query, 10, 0, false); assertEquals( user.getId(), r.get( 0 ).getId()); batch.deindex( indexSCope, user.getId(), user.getVersion() ); indexProducer.put(batch.build()).subscribe();; entityIndex.refreshAsync().toBlocking().first(); // EntityRef r = entityIndex.search( indexSCope, SearchTypes.fromTypes( "edanuff" ), query, 10, 0, false ); assertFalse( r.iterator().hasNext() ); } @Test public void multiValuedTypes() { Id appId = new SimpleId( "entityindextest" ); Id ownerId = new SimpleId( "multivaluedtype" ); IndexEdge indexScope = new IndexEdgeImpl( ownerId, "user", SearchEdge.NodeType.SOURCE, 10 ); entityIndex.createBatch(); // Bill has favorites as string, age as string and retirement goal as number Map billMap = new HashMap() {{ put( "username", "bill" ); put( "email", "bill@example.com" ); put( "age", "thirtysomething" ); put( "favorites", "scallops, croquet, wine" ); put( "retirementGoal", 100000 ); }}; Entity bill = EntityIndexMapUtils.fromMap( billMap ); EntityUtils.setId( bill, new SimpleId( UUIDGenerator.newTimeUUID(), "user" ) ); EntityUtils.setVersion( bill, UUIDGenerator.newTimeUUID() ); EntityIndexBatch batch = entityIndex.createBatch(); batch.index( indexScope, bill ); // Fred has age as int, favorites as object and retirement goal as object Map fredMap = new HashMap() {{ put( "username", "fred" ); put( "email", "fred@example.com" ); put( "age", 41 ); put( "favorites", new HashMap<String, Object>() {{ put( "food", "cheezewiz" ); put( "sport", "nascar" ); put( "beer", "budwizer" ); }} ); put( "retirementGoal", new HashMap<String, Object>() {{ put( "car", "Firebird" ); put( "home", "Mobile" ); }} ); }}; Entity fred = EntityIndexMapUtils.fromMap( fredMap ); EntityUtils.setId( fred, new SimpleId( UUIDGenerator.newTimeUUID(), "user" ) ); EntityUtils.setVersion( fred, UUIDGenerator.newTimeUUID() ); batch.index( indexScope, fred); indexProducer.put(batch.build()).subscribe();; entityIndex.refreshAsync().toBlocking().first(); final SearchTypes searchTypes = SearchTypes.fromTypes( "user" ); CandidateResults r = entityIndex.search( indexScope, searchTypes, "where username = 'bill'", 10, 0, false); assertEquals( bill.getId(), r.get( 0 ).getId() ); r = entityIndex.search( indexScope, searchTypes, "where username = 'fred'", 10, 0, false); assertEquals(fred.getId(), r.get(0).getId()); r = entityIndex.search( indexScope, searchTypes, "where age = 41", 10, 0, false); assertEquals(fred.getId(), r.get(0).getId()); r = entityIndex.search( indexScope, searchTypes, "where age = 'thirtysomething'", 10, 0, false); assertEquals(bill.getId(), r.get(0).getId()); } @Test public void healthTest() { Id appId = new SimpleId( "entityindextest" ); assertNotEquals( "cluster should be ok", Health.RED, entityIndex.getClusterHealth() ); assertEquals( "index should be ready", Health.GREEN, entityIndex.getIndexHealth() ); entityIndex.refreshAsync().toBlocking().first(); assertNotEquals( "cluster should be fine", Health.RED, entityIndex.getIndexHealth() ); assertNotEquals( "cluster should be ready now", Health.RED, entityIndex.getClusterHealth() ); } @Test public void testCursorFormat() throws Exception { String myType = UUID.randomUUID().toString(); Id ownerId = new SimpleId( UUID.randomUUID(),"owner" ); IndexEdge indexEdge = new IndexEdgeImpl( ownerId, "users", SearchEdge.NodeType.SOURCE, 10 ); final EntityIndexBatch batch = entityIndex.createBatch(); final int size = 100; final List<Id> entityIds = new ArrayList<>( size ); for ( int i = 0; i < size; i++ ) { final String middleName = "middleName" + UUIDUtils.newTimeUUID(); final int ordinal = i; Map entityMap = new HashMap() {{ put( "username", "edanuff" ); put( "email", "ed@anuff.com" ); put( "middlename", middleName ); put( "ordinal", ordinal ); put( "mytype", myType); }}; final Id userId = new SimpleId( "user" ); Entity user = EntityIndexMapUtils.fromMap( entityMap ); EntityUtils.setId( user, userId ); EntityUtils.setVersion( user, UUIDGenerator.newTimeUUID() ); entityIds.add(userId ); batch.index( indexEdge, user ); } indexProducer.put(batch.build()).subscribe();; entityIndex.refreshAsync().toBlocking().first(); final int limit = 5; final int expectedPages = size / limit; Optional<Integer> offset = Optional.absent(); UUID lastId = null; final String query = "select * where mytype='"+myType+"' order by ordinal asc"; int i ; for ( i=0; i < expectedPages; i++ ) { final CandidateResults results = !offset.isPresent() ? entityIndex.search( indexEdge, SearchTypes.allTypes(), query, limit, 0, false ) : entityIndex.search(indexEdge, SearchTypes.allTypes(), query, limit, i * limit, false); assertEquals(limit, results.size()); int ordinal = 0;//i == 0 ? 0 : 1; assertNotEquals("Scroll matches last item from previous page",lastId, results.get(ordinal).getId().getUuid()); lastId = results.get(limit -1).getId().getUuid(); offset = results.getOffset(); assertEquals("Failed on page "+i, results.get( ordinal ).getId(), entityIds.get( i*limit ) ); } //get our next page, we shouldn't get a cursor final CandidateResults results = entityIndex.search(indexEdge, SearchTypes.allTypes(), query, limit, i * limit, false); assertEquals( 0, results.size() ); assertFalse(results.hasOffset()); } @Test public void queryByUUID() throws Throwable { Id appId = new SimpleId( "application" ); Id ownerId = new SimpleId( "owner" ); IndexEdge indexSCope = new IndexEdgeImpl( ownerId, "user", SearchEdge.NodeType.SOURCE, 10 ); final UUID searchUUID = UUIDGenerator.newTimeUUID(); Map entityMap = new HashMap() {{ put( "searchUUID", searchUUID ); }}; Entity user = EntityIndexMapUtils.fromMap( entityMap ); final Id entityId = new SimpleId( "entitytype" ); EntityUtils.setId( user, entityId ); EntityUtils.setVersion( user, UUIDGenerator.newTimeUUID() ); EntityIndexBatch batch = entityIndex.createBatch(); batch.index( indexSCope, user ); indexProducer.put(batch.build()).subscribe();; entityIndex.refreshAsync().toBlocking().first(); final String query = "where searchUUID = " + searchUUID; final CandidateResults r = entityIndex.search( indexSCope, SearchTypes.fromTypes(entityId.getType()), query, 10, 0, false); assertEquals(user.getId(), r.get(0).getId()); } @Test public void queryByStringWildCardSpaces() throws Throwable { Id appId = new SimpleId( "application" ); Id ownerId = new SimpleId( "owner" ); IndexEdge indexSCope = new IndexEdgeImpl( ownerId, "user", SearchEdge.NodeType.SOURCE, 10 ); Map entityMap = new HashMap() {{ put( "string", "I am a search string" ); }}; Entity user = EntityIndexMapUtils.fromMap( entityMap ); final Id entityId = new SimpleId( "entitytype" ); EntityUtils.setId( user, entityId ); EntityUtils.setVersion( user, UUIDGenerator.newTimeUUID() ); EntityIndexBatch batch = entityIndex.createBatch(); batch.index(indexSCope, user); indexProducer.put(batch.build()).subscribe();; entityIndex.refreshAsync().toBlocking().first(); final String query = "where string = 'I am*'"; final CandidateResults r = entityIndex.search( indexSCope, SearchTypes.fromTypes( entityId.getType() ), query, 10, 0, false); assertEquals(user.getId(), r.get(0).getId()); //shouldn't match final String queryNoWildCard = "where string = 'I am'"; final CandidateResults noWildCardResults = entityIndex.search( indexSCope, SearchTypes.fromTypes( entityId.getType() ), queryNoWildCard, 10, 0, false ); assertEquals( 0, noWildCardResults.size() ); } @Test public void sortyByString() throws Throwable { Id appId = new SimpleId( "application" ); Id ownerId = new SimpleId( "owner" ); IndexEdge indexSCope = new IndexEdgeImpl( ownerId, "user", SearchEdge.NodeType.SOURCE, 10 ); /** * Ensures sort ordering is correct when more than 1 token is present. Should order by the unanalyzed field, * not the analyzed field */ final Entity first = new Entity( "search" ); first.setField( new StringField( "string", "alpha long string" ) ); EntityUtils.setVersion( first, UUIDGenerator.newTimeUUID() ); final Entity second = new Entity( "search" ); second.setField(new StringField("string", "bravo long string")); EntityUtils.setVersion( second, UUIDGenerator.newTimeUUID() ); EntityIndexBatch batch = entityIndex.createBatch(); batch.index(indexSCope, first ); batch.index( indexSCope, second ); indexProducer.put(batch.build()).subscribe();; entityIndex.refreshAsync().toBlocking().first(); final String ascQuery = "order by string"; final CandidateResults ascResults = entityIndex.search(indexSCope, SearchTypes.fromTypes( first.getId().getType() ), ascQuery, 10 , 0, false); assertEquals( first.getId(), ascResults.get( 0).getId() ); assertEquals( second.getId(), ascResults.get( 1 ).getId() ); //search in reversed final String descQuery = "order by string desc"; final CandidateResults descResults = entityIndex.search(indexSCope, SearchTypes.fromTypes( first.getId().getType() ), descQuery, 10 , 0, false); assertEquals( second.getId(), descResults.get( 0).getId() ); assertEquals( first.getId(), descResults.get( 1 ).getId() ); } @Test public void unionString() throws Throwable { Id appId = new SimpleId( "application" ); Id ownerId = new SimpleId( "owner" ); final Entity first = new Entity( "search" ); first.setField( new StringField( "string", "alpha long string" ) ); EntityUtils.setVersion( first, UUIDGenerator.newTimeUUID() ); final Entity second = new Entity( "search" ); second.setField( new StringField( "string", "bravo long string" ) ); EntityUtils.setVersion( second, UUIDGenerator.newTimeUUID() ); EntityIndexBatch batch = entityIndex.createBatch(); //get ordering, so 2 is before 1 when both match IndexEdge indexScope1 = new IndexEdgeImpl( ownerId, "searches", SearchEdge.NodeType.SOURCE, 10 ); batch.index(indexScope1, first); IndexEdge indexScope2 = new IndexEdgeImpl( ownerId, "searches", SearchEdge.NodeType.SOURCE, 11 ); batch.index(indexScope2, second); indexProducer.put(batch.build()).subscribe();; entityIndex.refreshAsync().toBlocking().first(); final String singleMatchQuery = "string contains 'alpha' OR string contains 'foo'"; final CandidateResults singleResults = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), singleMatchQuery, 10, 0, false ); assertEquals(1, singleResults.size()); assertEquals(first.getId(), singleResults.get(0).getId()); //search in reversed final String bothKeywordsMatch = "string contains 'alpha' OR string contains 'bravo'"; final CandidateResults singleKeywordUnion = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), bothKeywordsMatch, 10 , 0, false); assertEquals( 2, singleKeywordUnion.size() ); assertEquals( second.getId(), singleKeywordUnion.get( 0).getId() ); assertEquals( first.getId(), singleKeywordUnion.get( 1 ).getId() ); final String twoKeywordMatches = "string contains 'alpha' OR string contains 'long'"; final CandidateResults towMatchResults = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), twoKeywordMatches, 10, 0, false ); assertEquals( 2, towMatchResults.size() ); assertEquals(second.getId(), towMatchResults.get( 0).getId() ); assertEquals(first.getId(), towMatchResults.get( 1 ).getId() ); } /** * Tests that when NOT is the only query term, it functions correctly */ @Test public void notRootOperandFilter() throws Throwable { Id appId = new SimpleId( "application" ); Id ownerId = new SimpleId( "owner" ); final Entity first = new Entity( "search" ); first.setField( new IntegerField( "int", 1 ) ); EntityUtils.setVersion( first, UUIDGenerator.newTimeUUID() ); final Entity second = new Entity( "search" ); second.setField( new IntegerField( "int", 2 ) ); EntityUtils.setVersion( second, UUIDGenerator.newTimeUUID() ); EntityIndexBatch batch = entityIndex.createBatch(); //get ordering, so 2 is before 1 when both match IndexEdge indexScope1 = new IndexEdgeImpl( ownerId, "searches", SearchEdge.NodeType.SOURCE, 10 ); batch.index( indexScope1, first ); IndexEdge indexScope2 = new IndexEdgeImpl( ownerId, "searches", SearchEdge.NodeType.SOURCE, 11 ); batch.index( indexScope2, second); indexProducer.put(batch.build()).subscribe();; entityIndex.refreshAsync().toBlocking().first(); final String notFirst = "NOT int = 1"; final CandidateResults notFirstResults = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), notFirst, 10, 0, false ); assertEquals( 1, notFirstResults.size() ); assertEquals(second.getId(), notFirstResults.get( 0 ).getId() ); //search in reversed final String notSecond = "NOT int = 2"; final CandidateResults notSecondUnion = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), notSecond, 10, 0, false ); assertEquals( 1, notSecondUnion.size() ); assertEquals( first.getId(), notSecondUnion.get( 0 ).getId() ); final String notBothReturn = "NOT int = 3"; final CandidateResults notBothReturnResults = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), notBothReturn, 10, 0, false ); assertEquals( 2, notBothReturnResults.size() ); assertEquals( second.getId(), notBothReturnResults.get( 0).getId() ); assertEquals( first.getId(), notBothReturnResults.get( 1 ).getId() ); final String notFilterBoth = "(NOT int = 1) AND (NOT int = 2) "; final CandidateResults filterBoth = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), notFilterBoth, 10, 0, false ); assertEquals( 0, filterBoth.size() ); final String noMatchesAnd = "(NOT int = 3) AND (NOT int = 4)"; final CandidateResults noMatchesAndResults = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), noMatchesAnd, 10, 0, false ); assertEquals( 2, noMatchesAndResults.size() ); assertEquals( second.getId(), noMatchesAndResults.get( 0).getId() ); assertEquals( first.getId(), noMatchesAndResults.get( 1 ).getId() ); final String noMatchesOr = "(NOT int = 3) AND (NOT int = 4)"; final CandidateResults noMatchesOrResults = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), noMatchesOr, 10, 0, false ); assertEquals( 2, noMatchesOrResults.size() ); assertEquals( second.getId(), noMatchesOrResults.get( 0).getId() ); assertEquals( first.getId(), noMatchesOrResults.get( 1 ).getId() ); } /** * Tests that when NOT is the only query term, it functions correctly */ @Test public void notRootOperandQuery() throws Throwable { Id appId = new SimpleId( "application" ); Id ownerId = new SimpleId( "owner" ); final Entity first = new Entity( "search" ); first.setField( new StringField( "string", "I ate a sammich" ) ); EntityUtils.setVersion( first, UUIDGenerator.newTimeUUID() ); final Entity second = new Entity( "search" ); second.setField( new StringField( "string", "I drank a beer" ) ); EntityUtils.setVersion( second, UUIDGenerator.newTimeUUID() ); EntityIndexBatch batch = entityIndex.createBatch(); //get ordering, so 2 is before 1 when both match IndexEdge indexScope1 = new IndexEdgeImpl( ownerId, "searches", SearchEdge.NodeType.SOURCE, 10 ); batch.index( indexScope1, first ); IndexEdge indexScope2 = new IndexEdgeImpl( ownerId, "searches", SearchEdge.NodeType.SOURCE, 11 ); batch.index( indexScope2, second); indexProducer.put(batch.build()).subscribe();; entityIndex.refreshAsync().toBlocking().first(); final String notFirst = "NOT string = 'I ate a sammich'"; final CandidateResults notFirstResults = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), notFirst, 10, 0, false ); assertEquals( 1, notFirstResults.size() ); assertEquals(second.getId(), notFirstResults.get( 0 ).getId() ); final String notFirstWildCard = "NOT string = 'I ate*'"; final CandidateResults notFirstWildCardResults = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), notFirstWildCard, 10, 0, false ); assertEquals( 1, notFirstWildCardResults.size() ); assertEquals(second.getId(), notFirstWildCardResults.get( 0 ).getId() ); final String notFirstContains = "NOT string contains 'sammich'"; final CandidateResults notFirstContainsResults = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), notFirstContains, 10 , 0, false); assertEquals( 1, notFirstContainsResults.size() ); assertEquals(second.getId(), notFirstContainsResults.get( 0 ).getId() ); //search in reversed final String notSecond = "NOT string = 'I drank a beer'"; final CandidateResults notSecondUnion = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), notSecond, 10, 0, false ); assertEquals( 1, notSecondUnion.size() ); assertEquals( first.getId(), notSecondUnion.get( 0 ).getId() ); final String notSecondWildcard = "NOT string = 'I drank*'"; final CandidateResults notSecondWildcardUnion = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), notSecondWildcard, 10, 0, false ); assertEquals( 1, notSecondWildcardUnion.size() ); assertEquals( first.getId(), notSecondWildcardUnion.get( 0 ).getId() ); final String notSecondContains = "NOT string contains 'beer'"; final CandidateResults notSecondContainsUnion = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), notSecondContains, 10, 0, false ); assertEquals( 1, notSecondContainsUnion.size() ); assertEquals( first.getId(), notSecondContainsUnion.get( 0 ).getId() ); final String notBothReturn = "NOT string = 'I'm a foodie'"; final CandidateResults notBothReturnResults = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), notBothReturn, 10, 0, false ); assertEquals( 2, notBothReturnResults.size() ); assertEquals( second.getId(), notBothReturnResults.get( 0).getId() ); assertEquals( first.getId(), notBothReturnResults.get( 1 ).getId() ); final String notFilterBoth = "(NOT string = 'I ate a sammich') AND (NOT string = 'I drank a beer') "; final CandidateResults filterBoth = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), notFilterBoth, 10, 0, false ); assertEquals( 0, filterBoth.size() ); final String noMatchesAnd = "(NOT string = 'I ate*') AND (NOT string = 'I drank*')"; final CandidateResults noMatchesAndResults = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), noMatchesAnd, 10, 0, false ); assertEquals( 0, noMatchesAndResults.size() ); final String noMatchesContainsAnd = "(NOT string contains 'ate') AND (NOT string contains 'drank')"; final CandidateResults noMatchesContainsAndResults = entityIndex .search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), noMatchesContainsAnd, 10, 0, false ); assertEquals( 0, noMatchesContainsAndResults.size() ); final String noMatchesOr = "(NOT string = 'I ate*') AND (NOT string = 'I drank*')"; final CandidateResults noMatchesOrResults = entityIndex.search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), noMatchesOr, 10, 0, false ); assertEquals( 0, noMatchesOrResults.size() ); final String noMatchesContainsOr = "(NOT string contains 'ate') AND (NOT string contains 'drank')"; final CandidateResults noMatchesContainsOrResults = entityIndex .search(indexScope1, SearchTypes.fromTypes( first.getId().getType() ), noMatchesContainsOr, 10, 0, false ); assertEquals( 0, noMatchesContainsOrResults.size() ); } @Test public void testSizeByEdge(){ final String type = UUID.randomUUID().toString(); Id ownerId = new SimpleId( "owner" ); final Entity first = new Entity( type ); first.setField( new StringField( "string", "I ate a sammich" ) ); first.setSize(100); EntityUtils.setVersion( first, UUIDGenerator.newTimeUUID() ); final Entity second = new Entity( type ); second.setSize(100); second.setField( new StringField( "string", "I drank a beer" ) ); EntityUtils.setVersion( second, UUIDGenerator.newTimeUUID() ); EntityIndexBatch batch = entityIndex.createBatch(); //get ordering, so 2 is before 1 when both match IndexEdge indexScope1 = new IndexEdgeImpl( ownerId,type , SearchEdge.NodeType.SOURCE, 10 ); batch.index( indexScope1, first ); IndexEdge indexScope2 = new IndexEdgeImpl( ownerId, type+"er", SearchEdge.NodeType.SOURCE, 11 ); batch.index( indexScope2, second); indexProducer.put(batch.build()).subscribe();; entityIndex.refreshAsync().toBlocking().first(); long size = entityIndex.getTotalEntitySizeInBytes(new SearchEdgeImpl(ownerId,type, SearchEdge.NodeType.SOURCE)); assertTrue( size == 100 ); } /** * Tests that we're supporting null fields when indexing at Elasticsearch */ @Test public void testNullFields() throws IOException { Id appId = new SimpleId( "application" ); final String entityType = "thing"; IndexEdge indexEdge = new IndexEdgeImpl( appId, "things", SearchEdge.NodeType.SOURCE, 1 ); final SearchTypes searchTypes = SearchTypes.fromTypes( entityType ); EntityIndexBatch batch = entityIndex.createBatch(); UUID uuid = UUID.randomUUID(); Entity entity1 = new Entity( entityType ); EntityUtils.setVersion(entity1, UUIDGenerator.newTimeUUID()); entity1.setField(new UUIDField(IndexingUtils.ENTITY_ID_FIELDNAME, uuid)); // create a list and add 3 null values to it List<Object> listOfNulls = new ArrayList<>(3); listOfNulls.add(null); listOfNulls.add(null); listOfNulls.add(null); List<Object> listOfMixedNulls = new ArrayList<>(3); listOfMixedNulls.add(null); listOfMixedNulls.add("stringvalue"); listOfMixedNulls.add(null); listOfMixedNulls.add(10); entity1.setField( new UUIDField("uuid", uuid, true)); entity1.setField( new ArrayField<>( "arrayofnulls", listOfNulls) ); entity1.setField( new ArrayField<>( "arrayofmixednulls", listOfMixedNulls) ); entity1.setField( new NullField("nullfield")); batch.index( indexEdge, entity1 ); indexProducer.put(batch.build()).subscribe(); entityIndex.refreshAsync().toBlocking().first(); StopWatch timer = new StopWatch(); timer.start(); CandidateResults candidateResults = entityIndex.search( indexEdge, searchTypes, "select * where uuid = '"+uuid+"'", 100, 0, false ); timer.stop(); assertEquals(1, candidateResults.size()); logger.debug("Query time {}ms", timer.getTime()); final CandidateResult candidate1 = candidateResults.get(0); //check the id and version assertEquals( entity1.getId(), candidate1.getId() ); assertEquals(entity1.getVersion(), candidate1.getVersion()); } }