/*
* Hibernate Search, full-text search for your domain model
*
* 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.search.elasticsearch.test;
import static org.fest.assertions.Assertions.assertThat;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Period;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.apache.lucene.search.Query;
import org.hibernate.Transaction;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
import org.hibernate.search.elasticsearch.ElasticsearchProjectionConstants;
import org.hibernate.search.elasticsearch.testutil.JsonHelper;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hibernate.search.test.SearchTestBase;
import org.hibernate.search.testsupport.TestForIssue;
import org.junit.After;
import org.junit.Test;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
/**
* @author Yoann Rodiere
*/
@TestForIssue(jiraKey = "HSEARCH-2415")
public class ElasticsearchIndexNullAsIT extends SearchTestBase {
@After
public void deleteEntity() {
try (org.hibernate.Session s = openSession()) {
Transaction tx = s.beginTransaction();
s.delete( s.load( Sample.class, 1L ) );
s.delete( s.load( Sample.class, 2L ) );
s.flush();
tx.commit();
}
}
@Test
public void testString() throws Exception {
String string = "foo";
Sample sample = new Sample( 1L, "String example" );
sample.string = string;
assertNullFieldIndexingAndQuerying( "string", string, sample );
}
@Test
public void testInteger() throws Exception {
Integer integerValue = 42;
Sample sample = new Sample( 1L, "Integer example" );
sample.integerField = integerValue;
assertNullFieldIndexingAndQuerying( "integerField", integerValue, sample );
}
@Test
public void testLong() throws Exception {
Long longValue = 42L;
Sample sample = new Sample( 1L, "Long example" );
sample.longField = longValue;
assertNullFieldIndexingAndQuerying( "longField", longValue, sample );
}
@Test
public void testFloat() throws Exception {
Float floatValue = 42.0f;
Sample sample = new Sample( 1L, "Float example" );
sample.floatField = floatValue;
assertNullFieldIndexingAndQuerying( "floatField", floatValue, sample );
}
@Test
public void testDouble() throws Exception {
Double doubleValue = 42.0d;
Sample sample = new Sample( 1L, "Double example" );
sample.doubleField = doubleValue;
assertNullFieldIndexingAndQuerying( "doubleField", doubleValue, sample );
}
@Test
public void testBoolean() throws Exception {
Boolean booleanValue = false;
Sample sample = new Sample( 1L, "Boolean example" );
sample.booleanField = booleanValue;
assertNullFieldIndexingAndQuerying( "booleanField", booleanValue, sample );
}
@Test
public void testLocalDate() throws Exception {
LocalDate date = LocalDate.of( 2012, Month.DECEMBER, 30 );
Sample sample = new Sample( 1L, "LocalDate example" );
sample.localDate = date;
assertNullFieldIndexingAndQuerying( "localDate", date, sample );
}
@Test
public void testLocalTime() throws Exception {
LocalTime time = LocalTime.of( 13, 15, 55, 7 );
Sample sample = new Sample( 1L, "LocalTime example" );
sample.localTime = time;
assertNullFieldIndexingAndQuerying( "localTime", time, sample );
}
@Test
public void testLocalDateTime() throws Exception {
LocalDate date = LocalDate.of( 1998, Month.FEBRUARY, 12 );
LocalTime time = LocalTime.of( 13, 05, 33 );
LocalDateTime dateTime = LocalDateTime.of( date, time );
Sample sample = new Sample( 1L, "LocalDateTime example" );
sample.localDateTime = dateTime;
assertNullFieldIndexingAndQuerying( "localDateTime", dateTime, sample );
}
@Test
public void testInstant() throws Exception {
LocalDate date = LocalDate.of( 1998, Month.FEBRUARY, 12 );
LocalTime time = LocalTime.of( 13, 05, 33, 5 * 1_000_000 );
LocalDateTime dateTime = LocalDateTime.of( date, time );
Instant instant = dateTime.toInstant( ZoneOffset.UTC );
Sample sample = new Sample( 1L, "Instant example" );
sample.instant = instant;
assertNullFieldIndexingAndQuerying( "instant", instant, sample );
}
@Test
public void testDuration() throws Exception {
Duration value = Duration.ofNanos( Long.MAX_VALUE );
Sample sample = new Sample( 1L, "Duration example" );
sample.duration = value;
assertNullFieldIndexingAndQuerying( "duration", value, sample );
}
@Test
public void testPeriod() throws Exception {
Period value = Period.ZERO;
Sample sample = new Sample( 1L, "Period example" );
sample.period = value;
assertNullFieldIndexingAndQuerying( "period", value, sample );
}
@Test
public void testZoneOffset() throws Exception {
ZoneOffset value = ZoneOffset.of( "+01:00" );
Sample sample = new Sample( 1L, "zoneOffset example" );
sample.zoneOffset = value;
assertNullFieldIndexingAndQuerying( "zoneOffset", value, sample );
}
@Test
public void testZoneId() throws Exception {
ZoneId value = ZoneId.of( "GMT" );
Sample sample = new Sample( 1L, "ZoneId example" );
sample.zoneId = value;
assertNullFieldIndexingAndQuerying( "zoneId", value, sample );
}
@Test
public void testOffsetDateTime() throws Exception {
/* Elasticsearch only accepts years in the range [-292275054,292278993]
*/
OffsetDateTime value = OffsetDateTime.of(
221998, Month.FEBRUARY.getValue(), 12,
13, 05, 33, 7,
ZoneOffset.of( "+01:00" )
);
Sample sample = new Sample( 1L, "OffsetDateTime example" );
sample.offsetDateTime = value;
assertNullFieldIndexingAndQuerying( "offsetDateTime", value, sample );
}
@Test
public void testOffsetTime() throws Exception {
OffsetTime value = OffsetTime.of(
13, 05, 33, 7,
ZoneOffset.of( "+01:00" )
);
Sample sample = new Sample( 1L, "OffsetTime example" );
sample.offsetTime = value;
assertNullFieldIndexingAndQuerying( "offsetTime", value, sample );
}
@Test
public void testYear() throws Exception {
/* Elasticsearch only accepts years in the range [-292275054,292278993]
*/
Year value = Year.of( 292278993 );
Sample sample = new Sample( 1L, "Year example" );
sample.year = value;
assertNullFieldIndexingAndQuerying( "year", value, sample );
}
@Test
public void testYearMonth() throws Exception {
YearMonth value = YearMonth.of( 124, 12 );
Sample sample = new Sample( 1L, "YearMonth example" );
sample.yearMonth = value;
assertNullFieldIndexingAndQuerying( "yearMonth", value, sample );
}
@Test
public void testMonthDay() throws Exception {
MonthDay value = MonthDay.of( 12, 1 );
Sample sample = new Sample( 1L, "MonthDay example" );
sample.monthDay = value;
assertNullFieldIndexingAndQuerying( "monthDay", value, sample );
}
@SuppressWarnings("unchecked")
private void assertNullFieldIndexingAndQuerying(String field, Object expectedValue, Sample sampleWithValue) {
Sample sampleWithoutValue = new Sample( 2L, sampleWithValue.description + " - without value" );
try (org.hibernate.Session s = openSession()) {
Transaction tx = s.beginTransaction();
s.persist( sampleWithValue );
s.persist( sampleWithoutValue );
s.flush();
tx.commit();
tx = s.beginTransaction();
final FullTextSession session = Search.getFullTextSession( s );
Query query = queryBuilder( session ).keyword().onField( field ).ignoreAnalyzer().matching( expectedValue ).createQuery();
List<Object[]> result = session.createFullTextQuery( query )
.setProjection( ElasticsearchProjectionConstants.ID )
.list();
assertThat( result ).as( "Both documents (with field '" + field + "' index as null and with the same field"
+ " equal to indexNullAs) should be found when querying the indexNullAs value" )
.hasSize( 2 );
result = session.createFullTextQuery( query )
.setProjection( ElasticsearchProjectionConstants.ID, field, ElasticsearchProjectionConstants.SOURCE )
.list();
for ( Object[] projection : result ) {
if ( projection[0].equals( 1L ) ) {
assertThat( projection[1] ).as( "Document with field '" + field + "' non-null should have a non-null"
+ " projection on this field" )
.isEqualTo( expectedValue );
JsonElement json = new Gson().fromJson( (String) projection[2], JsonElement.class );
JsonElement propertyValue = json.getAsJsonObject().get( field );
assertThat( propertyValue ).as( "Document with field '" + field + "' non-null should have a value for"
+ " this field in their source" )
.isNotNull();
assertThat( propertyValue ).as( "Document with field '" + field + "' non-null should have a non-null"
+ " value for this field in their source" )
.isNotEqualTo( JsonNull.INSTANCE );
}
else {
assertThat( projection[1] ).as( "Document with field '" + field + "' indexed as null should have a null"
+ " projection on this field" )
.isNull();
// Document with a field indexed as null should have null for this field in their source
JsonHelper.assertJsonEqualsIgnoringUnknownFields( "{'" + field + "': null}", (String) projection[2] );
}
}
query = queryBuilder( session ).keyword().onField( field ).ignoreAnalyzer().matching( null ).createQuery();
result = session.createFullTextQuery( query )
.setProjection( ElasticsearchProjectionConstants.ID )
.list();
assertThat( result ).as( "Both documents (with field '" + field + "' index as null and with the same field equal"
+ " to indexNullAs) should be found when querying the null value" )
.hasSize( 2 );
tx.commit();
}
}
@Field(analyze = Analyze.NO, store = Store.YES)
private QueryBuilder queryBuilder(final FullTextSession session) {
QueryBuilder builder = session.getSearchFactory().buildQueryBuilder().forEntity( Sample.class ).get();
return builder;
}
@Entity
@Indexed
static class Sample {
public Sample() {
}
public Sample(long id, String description) {
this.id = id;
this.description = description;
}
@Id
@DocumentId
long id;
@Field(analyze = Analyze.NO, store = Store.YES)
String description;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "foo")
private String string;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "42")
private Integer integerField;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "42")
private Long longField;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "42.0")
private Float floatField;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "42.0")
private Double doubleField;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "false")
private Boolean booleanField;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "2012-12-30")
private LocalDate localDate;
@Column(name = "LOCAL_TIME") // localTime is a special keywork for some db
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "13:15:55.000000007")
private LocalTime localTime;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "1998-02-12T13:05:33")
private LocalDateTime localDateTime;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "1998-02-12T13:05:33.005+00:00[UTC]")
private Instant instant;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "9223372036854775807")
private Duration duration;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "+0000000000+0000000000+0000000000")
private Period period;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "+01:00")
private ZoneOffset zoneOffset;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "GMT")
private ZoneId zoneId;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "+221998-02-12T13:05:33.000000007+01:00")
private OffsetDateTime offsetDateTime;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "13:05:33.000000007+01:00")
private OffsetTime offsetTime;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "+292278993")
private Year year;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "0124-12")
private YearMonth yearMonth;
@Field(analyze = Analyze.NO, store = Store.YES, indexNullAs = "--12-01")
private MonthDay monthDay;
}
@Override
public Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { Sample.class };
}
}