Skip to content

Commit

Permalink
HSEARCH-4489 Add matchNone() predicate
Browse files Browse the repository at this point in the history
- add matchNone() to DSL
- update documentation to mention new predicate
  • Loading branch information
marko-bekhta authored and yrodiere committed Mar 30, 2022
1 parent 4794169 commit edbff0b
Show file tree
Hide file tree
Showing 15 changed files with 297 additions and 0 deletions.
@@ -0,0 +1,49 @@
/*
* 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.backend.elasticsearch.search.predicate.impl;

import org.hibernate.search.backend.elasticsearch.gson.impl.JsonAccessor;
import org.hibernate.search.backend.elasticsearch.gson.impl.JsonObjectAccessor;
import org.hibernate.search.backend.elasticsearch.search.common.impl.ElasticsearchSearchIndexScope;
import org.hibernate.search.engine.search.predicate.SearchPredicate;
import org.hibernate.search.engine.search.predicate.spi.MatchNonePredicateBuilder;

import com.google.gson.JsonObject;


class ElasticsearchMatchNonePredicate extends AbstractElasticsearchPredicate {

private static final JsonObjectAccessor MATCH_NONE_ACCESSOR = JsonAccessor.root().property( "match_none" )
.asObject();

private ElasticsearchMatchNonePredicate(Builder builder) {
super( builder );
}

@Override
public void checkNestableWithin(String expectedParentNestedPath) {
// Nothing to do
}

@Override
protected JsonObject doToJsonQuery(PredicateRequestContext context,
JsonObject outerObject, JsonObject innerObject) {
MATCH_NONE_ACCESSOR.set( outerObject, innerObject );
return outerObject;
}

static class Builder extends AbstractBuilder implements MatchNonePredicateBuilder {
Builder(ElasticsearchSearchIndexScope<?> scope) {
super( scope );
}

@Override
public SearchPredicate build() {
return new ElasticsearchMatchNonePredicate( this );
}
}
}
Expand Up @@ -10,6 +10,7 @@
import org.hibernate.search.engine.search.predicate.spi.BooleanPredicateBuilder;
import org.hibernate.search.engine.search.predicate.spi.MatchAllPredicateBuilder;
import org.hibernate.search.engine.search.predicate.spi.MatchIdPredicateBuilder;
import org.hibernate.search.engine.search.predicate.spi.MatchNonePredicateBuilder;
import org.hibernate.search.engine.search.predicate.spi.SearchPredicateBuilderFactory;
import org.hibernate.search.engine.search.predicate.spi.SimpleQueryStringPredicateBuilder;

Expand All @@ -28,6 +29,11 @@ public MatchAllPredicateBuilder matchAll() {
return new ElasticsearchMatchAllPredicate.Builder( scope );
}

@Override
public MatchNonePredicateBuilder matchNone() {
return new ElasticsearchMatchNonePredicate.Builder( scope );
}

@Override
public MatchIdPredicateBuilder id() {
return new ElasticsearchMatchIdPredicate.Builder( scope );
Expand Down
@@ -0,0 +1,43 @@
/*
* 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.backend.lucene.search.predicate.impl;

import org.hibernate.search.backend.lucene.search.common.impl.LuceneSearchIndexScope;
import org.hibernate.search.engine.search.predicate.SearchPredicate;
import org.hibernate.search.engine.search.predicate.spi.MatchNonePredicateBuilder;

import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;


class LuceneMatchNonePredicate extends AbstractLuceneSearchPredicate {

private LuceneMatchNonePredicate(Builder builder) {
super( builder );
}

@Override
public void checkNestableWithin(String expectedParentNestedPath) {
// Nothing to do
}

@Override
protected Query doToQuery(PredicateRequestContext context) {
return new MatchNoDocsQuery();
}

static class Builder extends AbstractBuilder implements MatchNonePredicateBuilder {
Builder(LuceneSearchIndexScope<?> scope) {
super( scope );
}

@Override
public SearchPredicate build() {
return new LuceneMatchNonePredicate( this );
}
}
}
Expand Up @@ -10,6 +10,7 @@
import org.hibernate.search.engine.search.predicate.spi.BooleanPredicateBuilder;
import org.hibernate.search.engine.search.predicate.spi.MatchAllPredicateBuilder;
import org.hibernate.search.engine.search.predicate.spi.MatchIdPredicateBuilder;
import org.hibernate.search.engine.search.predicate.spi.MatchNonePredicateBuilder;
import org.hibernate.search.engine.search.predicate.spi.SearchPredicateBuilderFactory;
import org.hibernate.search.engine.search.predicate.spi.SimpleQueryStringPredicateBuilder;

Expand All @@ -29,6 +30,11 @@ public MatchAllPredicateBuilder matchAll() {
return new LuceneMatchAllPredicate.Builder( scope );
}

@Override
public MatchNonePredicateBuilder matchNone() {
return new LuceneMatchNonePredicate.Builder( scope );
}

@Override
public MatchIdPredicateBuilder id() {
return new LuceneMatchIdPredicate.Builder( scope );
Expand Down
Expand Up @@ -67,6 +67,19 @@ include::{sourcedir}/org/hibernate/search/documentation/search/predicate/Predica
* The score of a `matchAll` predicate is constant and equal to 1 by default,
but can be <<search-dsl-predicate-common-boost,boosted with `.boost(...)`>>.

[[search-dsl-predicate-match-none]]
== `matchNone`: match no documents

The `matchNone` predicate is the inverse of `matchAll` and matches no documents.

.Matching no documents
====
[source, JAVA, indent=0, subs="+callouts"]
----
include::{sourcedir}/org/hibernate/search/documentation/search/predicate/PredicateDslIT.java[tags=matchNone]
----
====

[[search-dsl-predicate-id]]
== `id`: match a document identifier

Expand Down
Expand Up @@ -116,6 +116,20 @@ public void matchAll() {
} );
}

@Test
public void matchNone() {
withinSearchSession( searchSession -> {
// tag::matchNone[]
List<Book> hits = searchSession.search( Book.class )
.where( f -> f.matchNone() )
.fetchHits( 20 );
// end::matchNone[]
assertThat( hits )
.extracting( Book::getId )
.isEmpty();
} );
}

@Test
public void id() {
// DO NOT USE THE BOOKX_ID CONSTANTS INSIDE TAGS BELOW:
Expand Down
@@ -0,0 +1,13 @@
/*
* 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.engine.search.predicate.dsl;

/**
* The initial and final step in "match none" predicate definition.
*/
public interface MatchNonePredicateFinalStep extends PredicateFinalStep {
}
Expand Up @@ -43,6 +43,14 @@ public interface SearchPredicateFactory {
*/
MatchAllPredicateOptionsStep<?> matchAll();

/**
* Match none of the documents.
*
* @return The initial step of a DSL where the "match none" predicate can be defined.
* @see MatchNonePredicateFinalStep
*/
MatchNonePredicateFinalStep matchNone();

/**
* Match documents where the identifier is among the given values.
*
Expand Down
@@ -0,0 +1,30 @@
/*
* 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.engine.search.predicate.dsl.impl;

import org.hibernate.search.engine.search.predicate.SearchPredicate;
import org.hibernate.search.engine.search.predicate.dsl.MatchNonePredicateFinalStep;
import org.hibernate.search.engine.search.predicate.dsl.spi.AbstractPredicateFinalStep;
import org.hibernate.search.engine.search.predicate.dsl.spi.SearchPredicateDslContext;
import org.hibernate.search.engine.search.predicate.spi.MatchNonePredicateBuilder;


public final class MatchNonePredicateFinalStepImpl extends AbstractPredicateFinalStep
implements MatchNonePredicateFinalStep {

private final MatchNonePredicateBuilder matchNoneBuilder;

public MatchNonePredicateFinalStepImpl(SearchPredicateDslContext<?> dslContext) {
super( dslContext );
this.matchNoneBuilder = dslContext.scope().predicateBuilders().matchNone();
}

@Override
protected SearchPredicate build() {
return matchNoneBuilder.build();
}
}
Expand Up @@ -15,6 +15,7 @@
import org.hibernate.search.engine.search.predicate.dsl.ExtendedSearchPredicateFactory;
import org.hibernate.search.engine.search.predicate.dsl.MatchAllPredicateOptionsStep;
import org.hibernate.search.engine.search.predicate.dsl.MatchIdPredicateMatchingStep;
import org.hibernate.search.engine.search.predicate.dsl.MatchNonePredicateFinalStep;
import org.hibernate.search.engine.search.predicate.dsl.MatchPredicateFieldStep;
import org.hibernate.search.engine.search.predicate.dsl.NamedPredicateOptionsStep;
import org.hibernate.search.engine.search.predicate.dsl.NestedPredicateFieldStep;
Expand All @@ -32,6 +33,7 @@
import org.hibernate.search.engine.search.predicate.dsl.impl.ExistsPredicateFieldStepImpl;
import org.hibernate.search.engine.search.predicate.dsl.impl.MatchAllPredicateOptionsStepImpl;
import org.hibernate.search.engine.search.predicate.dsl.impl.MatchIdPredicateMatchingStepImpl;
import org.hibernate.search.engine.search.predicate.dsl.impl.MatchNonePredicateFinalStepImpl;
import org.hibernate.search.engine.search.predicate.dsl.impl.MatchPredicateFieldStepImpl;
import org.hibernate.search.engine.search.predicate.dsl.impl.NamedPredicateOptionsStepImpl;
import org.hibernate.search.engine.search.predicate.dsl.impl.NestedPredicateFieldStepImpl;
Expand Down Expand Up @@ -64,6 +66,11 @@ public MatchAllPredicateOptionsStep<?> matchAll() {
return new MatchAllPredicateOptionsStepImpl( dslContext, this );
}

@Override
public MatchNonePredicateFinalStep matchNone() {
return new MatchNonePredicateFinalStepImpl( dslContext );
}

@Override
public MatchIdPredicateMatchingStep<?> id() {
return new MatchIdPredicateMatchingStepImpl( dslContext );
Expand Down
@@ -0,0 +1,11 @@
/*
* 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.engine.search.predicate.spi;

public interface MatchNonePredicateBuilder extends SearchPredicateBuilder {

}
Expand Up @@ -16,6 +16,8 @@ public interface SearchPredicateBuilderFactory {

MatchAllPredicateBuilder matchAll();

MatchNonePredicateBuilder matchNone();

MatchIdPredicateBuilder id();

BooleanPredicateBuilder bool();
Expand Down
@@ -0,0 +1,87 @@
/*
* 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.integrationtest.backend.tck.search.predicate;

import static org.hibernate.search.util.impl.integrationtest.common.assertion.SearchResultAssert.assertThatQuery;

import org.hibernate.search.engine.backend.document.IndexFieldReference;
import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaElement;
import org.hibernate.search.engine.backend.types.dsl.IndexFieldTypeFactory;
import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory;
import org.hibernate.search.integrationtest.backend.tck.testsupport.util.rule.SearchSetupHelper;
import org.hibernate.search.util.impl.integrationtest.mapper.stub.SimpleMappedIndex;

import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;

public class MatchNonePredicateSpecificsIT {

private static final String DOCUMENT_1 = "1";
private static final String STRING_1 = "aaa";

private static final String DOCUMENT_2 = "2";
private static final String STRING_2 = "bbb";

private static final String DOCUMENT_3 = "3";
private static final String STRING_3 = "ccc";

@ClassRule
public static final SearchSetupHelper setupHelper = new SearchSetupHelper();

private static final SimpleMappedIndex<IndexBinding> index = SimpleMappedIndex.of( IndexBinding::new );

@BeforeClass
public static void setup() {
setupHelper.start().withIndex( index ).setup();

initData();
}

@Test
public void matchNone() {
assertThatQuery( index.query()
.where( SearchPredicateFactory::matchNone ) )
.hasNoHits();
}

@Test
public void matchNoneWithinBoolPredicate() {
//check that we will find something with a single match predicate
assertThatQuery( index.query()
.where(
f -> f.bool()
.must( f.match().field( "string" ).matching( STRING_1 ).toPredicate() )
)
).hasTotalHitCount( 1 );

// make sure that matchNone will "override" the other matching predicate
assertThatQuery( index.query()
.where(
f -> f.bool()
.must( f.match().field( "string" ).matching( STRING_1 ).toPredicate() )
.must( f.matchNone() )
)
).hasNoHits();
}

private static void initData() {
index.bulkIndexer()
.add( DOCUMENT_1, document -> document.addValue( index.binding().string, STRING_1 ) )
.add( DOCUMENT_2, document -> document.addValue( index.binding().string, STRING_2 ) )
.add( DOCUMENT_3, document -> document.addValue( index.binding().string, STRING_3 ) )
.join();
}

private static class IndexBinding {
final IndexFieldReference<String> string;

IndexBinding(IndexSchemaElement root) {
string = root.field( "string", IndexFieldTypeFactory::asString ).toReference();
}
}
}
Expand Up @@ -19,6 +19,7 @@
import org.hibernate.search.engine.search.predicate.spi.ExistsPredicateBuilder;
import org.hibernate.search.engine.search.predicate.spi.MatchAllPredicateBuilder;
import org.hibernate.search.engine.search.predicate.spi.MatchIdPredicateBuilder;
import org.hibernate.search.engine.search.predicate.spi.MatchNonePredicateBuilder;
import org.hibernate.search.engine.search.predicate.spi.MatchPredicateBuilder;
import org.hibernate.search.engine.search.predicate.spi.NamedPredicateBuilder;
import org.hibernate.search.engine.search.predicate.spi.NestedPredicateBuilder;
Expand Down Expand Up @@ -101,6 +102,7 @@ public void flags(Set<SimpleQueryFlag> flags) {
}

public static class Builder implements MatchAllPredicateBuilder,
MatchNonePredicateBuilder,
BooleanPredicateBuilder,
MatchIdPredicateBuilder,
MatchPredicateBuilder,
Expand Down

0 comments on commit edbff0b

Please sign in to comment.