Skip to content

Commit

Permalink
HSEARCH-2471 Added support for projections on unmapped fields with El…
Browse files Browse the repository at this point in the history
…asticsearch

That's probably what was originally intended with the current code
handling projections of one-way bridged fields.
  • Loading branch information
yrodiere authored and Sanne committed Nov 28, 2016
1 parent fab76f0 commit d700b8b
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -523,15 +523,17 @@ private FieldProjection createProjection(JsonBuilder.Array sourceFilterCollector
else {
// We check if it is a field created by a field bridge
BridgeDefinedField bridgeDefinedField = rootTypeMetadata.getBridgeDefinedFieldMetadataFor( projectedField );
if ( bridgeDefinedField == null ) {
if ( bridgeDefinedField != null ) {
return createProjection( sourceFilterCollector, rootTypeMetadata, bridgeDefinedField );
}
else {
/*
* Don't fail immediately: this entity type may not be present in the results, in which case
* we don't need to be able to project on this field for this exact entity type.
* Just make sure we *will* ultimately fail if we encounter this entity type.
* No metadata: fall back to dynamically converting the resulting
* JSON to the most appropriate Java type.
*/
return new FailingUnknownFieldProjection( rootTypeMetadata, projectedField );
sourceFilterCollector.add( new JsonPrimitive( projectedField ) );
return new JsonDrivenProjection( projectedField );
}
return createProjection( sourceFilterCollector, rootTypeMetadata, bridgeDefinedField );
}
}

Expand Down Expand Up @@ -1046,27 +1048,6 @@ else if ( primitive.isString() ) {
}
}

/**
* A projection used whenever a given type is missing a projected field.
*
* @author Yoann Rodiere
*/
private static class FailingUnknownFieldProjection extends FieldProjection {
private final TypeMetadata rootTypeMetadata;
private final String absoluteName;

public FailingUnknownFieldProjection(TypeMetadata rootTypeMetadata, String absoluteName) {
super();
this.rootTypeMetadata = rootTypeMetadata;
this.absoluteName = absoluteName;
}

@Override
public Object convertHit(JsonObject hit, ConversionContext conversionContext) {
throw LOG.unknownFieldForProjection( rootTypeMetadata.getType().getName(), absoluteName );
}
}

@Override
protected void extractFacetResults() {
SearchResult searchResult = getSearchResult();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,6 @@ BulkRequestFailedException elasticsearchBulkRequestFailed(String request, String
)
SearchException unexpectedNumericEncodingType(String fieldType, String fieldName);

@Message(id = ES_BACKEND_MESSAGES_START_ID + 19,
value = "Cannot project field '%2$s' for entity %1$s: unknown field"
)
SearchException unknownFieldForProjection(String entityType, String fieldName);

@Message(id = ES_BACKEND_MESSAGES_START_ID + 20,
value = "Could not create mapping for entity type %1$s"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,23 @@
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.elasticsearch.ElasticsearchQueries;
import org.hibernate.search.elasticsearch.testutil.TestElasticsearchClient;
import org.hibernate.search.query.engine.spi.QueryDescriptor;
import org.hibernate.search.test.SearchTestBase;
import org.hibernate.search.testsupport.TestForIssue;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

/**
* @author Gunnar Morling
*/
public class ElasticsearchClassBridgeIT extends SearchTestBase {

@Rule
public TestElasticsearchClient elasticsearchClient = new TestElasticsearchClient();

@Before
public void setupTestData() {
Session s = openSession();
Expand Down Expand Up @@ -136,6 +141,30 @@ public void testProjectionOnUnindexedClassBridgeField() {
s.close();
}

@Test
public void testProjectionOnUnknownBridgeField() throws Exception {
// Add an additional field to the ES mapping, unknown to Hibernate Search
elasticsearchClient.putMapping( "golfplayer", GolfPlayer.class, "{'properties': {'fieldNotInMapping': {'type':'integer'}}}" );
elasticsearchClient.index( "golfplayer", GolfPlayer.class, "9999", "{'id':9999,'fieldNotInMapping':42}" );

Session s = openSession();
FullTextSession session = Search.getFullTextSession( s );
Transaction tx = s.beginTransaction();

QueryDescriptor query = ElasticsearchQueries.fromQueryString( "fieldNotInMapping:42" );
List<?> result = session.createFullTextQuery( query, GolfPlayer.class )
.setProjection( "fieldNotInMapping" )
.list();

assertThat( result ).hasSize( 1 );

Object[] projection = (Object[]) result.iterator().next();
assertThat( ( (Number) projection[0] ).intValue() ).describedAs( "fieldNotInMapping" ).isEqualTo( 42 );

tx.commit();
s.close();
}

@Override
public Class<?>[] getAnnotatedClasses() {
return new Class[]{ GolfPlayer.class, GolfCourse.class, Hole.class };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import io.searchbox.client.config.HttpClientConfig;
import io.searchbox.cluster.Health;
import io.searchbox.cluster.Health.Builder;
import io.searchbox.core.Index;
import io.searchbox.indices.CreateIndex;
import io.searchbox.indices.DeleteIndex;
import io.searchbox.indices.mapping.GetMapping;
Expand All @@ -56,10 +57,18 @@ public void putMapping(Class<?> mappedAndRootClass, String mappingJson) throws I
putMapping( mappedAndRootClass, mappedAndRootClass, mappingJson );
}

public void putMapping(String indexName, Class<?> mappedClass, String mappingJson) throws IOException {
putMapping( indexName, mappedClass.getName(), mappingJson );
}

public void putMapping(Class<?> rootClass, Class<?> mappedClass, String mappingJson) throws IOException {
putMapping( IndexNameNormalizer.getElasticsearchIndexName( rootClass.getName() ), mappedClass.getName(), mappingJson );
}

public String getMapping(String indexName, Class<?> mappedClass) throws IOException {
return getMapping( indexName, mappedClass.getName() );
}

public String getMapping(Class<?> mappedAndRootClass) throws IOException {
return getMapping( mappedAndRootClass, mappedAndRootClass );
}
Expand Down Expand Up @@ -107,12 +116,7 @@ protected String buildURI() {
}

public void putMapping(String indexName, String mappingName, String mappingJson) throws IOException {
/*
* Convert to JsonElement first, so that some Elasticsearch peculiarities (such as the fact that
* single quotes are not accepted as a substitute for single quotes) can be worked around.
* In tests, single quotes are way easier to include in JSON strings, because we don't have to escape them.
*/
JsonElement mappingJsonElement = new JsonParser().parse( mappingJson );
JsonElement mappingJsonElement = toJsonElement( mappingJson );

JestResult result = client.execute( new PutMapping.Builder( indexName, mappingName, mappingJsonElement ).build() );
if ( !result.isSucceeded() ) {
Expand Down Expand Up @@ -142,6 +146,33 @@ public String getMapping(String indexName, String mappingName) throws IOExceptio
return mapping.toString();
}

public void index(String indexName, Class<?> mappedClass, String id, String jsonDocument) throws IOException {
index( indexName, mappedClass.getName(), id, jsonDocument );
}

public void index(Class<?> mappedAndRootClass, String id, String jsonDocument) throws IOException {
index( mappedAndRootClass, mappedAndRootClass, id, jsonDocument );
}

public void index(Class<?> rootClass, Class<?> mappedClass, String id, String jsonDocument) throws IOException {
index( IndexNameNormalizer.getElasticsearchIndexName( rootClass.getName() ), mappedClass.getName(), id, jsonDocument );
}

public void index(String indexName, String typeName, String id, String jsonDocument) throws IOException {
JsonElement documentJsonElement = toJsonElement( jsonDocument );

JestResult result = client.execute( new Index.Builder( documentJsonElement )
.index( indexName )
.type( typeName )
.id( id )
.refresh( true )
.build() );
if ( !result.isSucceeded() ) {
throw new AssertionFailure( "Error while indexing '" + jsonDocument
+ "' on index '" + indexName + "' for tests:" + result.getErrorMessage() );
}
}

@Override
protected void before() throws Throwable {
JestClientFactory factory = new JestClientFactory();
Expand Down Expand Up @@ -181,4 +212,13 @@ private void tryDeleteESIndex(String indexName) {
}
}

/*
* Convert provided JSON to JsonElement, so that some Elasticsearch peculiarities (such as the fact that
* single quotes are not accepted as a substitute for single quotes) can be worked around.
* In tests, single quotes are way easier to include in JSON strings, because we don't have to escape them.
*/
private JsonElement toJsonElement(String jsonAsString) {
return new JsonParser().parse( jsonAsString );
}

}

0 comments on commit d700b8b

Please sign in to comment.