Using exists on list of child records

Currently migrating from HS5 and trying to move away from index null as. We have followed the documentations example to use the exists predicate with negation as a null check, however this causes issues when the field we are checking is in a list of child objects.

If parent has two child records with a field called “name”, one of which is null and one of which is not null the above method will not match the parent record as parent.child.name exists, where as for our use case we would want it to.

I am struggling to see a way we could make this work with the existing boolean logic. Is there a way to make this work, or should we revert back to using index null as?

I can see two ways.

Using nested object fields

Make the “child” object field (the one holding the name) nested and wrap your exists() predicate in a nested predicate.

E.g. something like this:

@Indexed
public class Parent {

    @IndexedEmbedded(structure = NESTED)
    List<Child> child;

}

public class Child {

    @FullTextField
    String name;

}

public List<Parent> search(...) {
    return searchSession.search( Parent.class )
            .where( f -> f.nested().objectField( "child" ) 
                    .nest( f.bool()
                            .mustNot( f.exists().field( "child.name" ) ) 
                    ) )
            .fetchHits( 20 ); 
}

Use a custom field and bridge

Perhaps simpler, define an additional boolean field next to your name field, and use a match predicate on that field (you don’t need a negation then):

@Indexed
public class Parent {

    @IndexedEmbedded
    List<Child> child;

}

public class Child {

    @FullTextField
    @GenericField(name = "is_unnamed", valueBridge = @ValueBridgeRef(type = IsEmptyOrNullBridge.class))
    String name;

}

public class IsEmptyOrNullBridge implements ValueBridge<String, Boolean> {

	@Override
	public Boolean toIndexedValue(String value, ValueBridgeToIndexedValueContext context) {
		return value == null || value.isEmpty();
	}

}


public List<Parent> search(...) {
    return searchSession.search( Parent.class )
            .where( f -> f.match().field( "child.is_unnamed" ).matching( true ) )
            .fetchHits( 20 ); 
}

Thanks again for the quick help, I’ll give these a go :slight_smile:

No problem.

That being said, you can still use indexNullAs if you want.

It’s just that now, you will have to handle the null token explicitly, i.e. decide of a string to index instead of null (e.g. “NO_NAME”) and search for that string instead of null in your search query.
That’s the main change since 5.x: now it’s clear that indexNullAs is just a hack, and it’s clearer that it has limitations (what if someone is named “NO_NAME”? :slight_smile: ). But if it works for you, then it’s fine to use it.

EDIT: Ah, and also, indexNullAs is not available on full-text fields, due to underlying technical constraints. So, if your field is a @FullTextField, then indeed, you should look for alternatives.