Do Sortable Fields in a Programmatic Mapping require indexNullAs if the value is nullable?

With the following programmatic mapping:

objectField.field(SORT_APPENDIX =("sort"), f -> f.asString().sortable(Sortable.YES)).toReference()

I get this exception on sort, when one value is null:

org.hibernate.search.util.common.SearchException: HSEARCH600000: Unknown field '(...).sort'.

So is it a default requirement for programmatic mappings to have an indexNullAs()? I think maybe im missing something else. I think in the normal mapping with @KeywordField(sortable= Sortable.YES) indexNullAs is not required.

objectField.field(SORT_APPENDIX, f -> f.asString().indexNullAs("").sortable(Sortable.YES)).toReference()

Full binder Code (maybe the return on bridgedElement == null is baaaad)?:

public class AnzeigeStringerBinder implements PropertyBinder {
    public static final String VALUE_APPENDIX = "value";
    public static final String SORT_APPENDIX = "sort";

    /** Optionaler Name für das Feld, ansonsten ist es der Property name */
    private final String fieldName;

    public AnzeigeStringerBinder() {
        this.fieldName = null;
    }

    public AnzeigeStringerBinder(String fieldName) {
        this.fieldName = fieldName;
    }

    @Override
    public void bind(PropertyBindingContext context) {
        context.dependencies().useRootOnly();
        context.bridgedElement().isAssignableTo(AnzeigeStringer.class);

        final String propertyName = Optional.ofNullable(this.fieldName).orElse(context.bridgedElement().name());
        final IndexSchemaObjectField objectField = context.indexSchemaElement()
                .objectField(propertyName);

        context.bridge(
                new AnzeigeStringerBridge(
                        objectField.toReference(),
                        objectField.field(VALUE_APPENDIX, f -> f.asString()).toReference(),
                        objectField.field(SORT_APPENDIX, f -> f.asString().indexNullAs("").sortable(Sortable.YES)).toReference()
                )
        );
    }

    private static class AnzeigeStringerBridge implements PropertyBridge {
        private final IndexObjectFieldReference objectField;
        private final IndexFieldReference<String> value;
        private final IndexFieldReference<String> sort;


        public <F> AnzeigeStringerBridge(IndexObjectFieldReference objectField, IndexFieldReference<String> value, IndexFieldReference<String> sort) {
            this.objectField = objectField;
            this.value = value;
            this.sort = sort;
        }

        @Override
        public void write(DocumentElement target, Object bridgedElement, PropertyBridgeWriteContext context) {
            if (bridgedElement == null) {
                return;
            }
            final AnzeigeStringer anzeigeStringer = (AnzeigeStringer) bridgedElement;
            final DocumentElement object = target.addObject(this.objectField);
            object.addValue(this.value, anzeigeStringer.value());
            object.addValue(this.sort, anzeigeStringer.anzeigeString());
        }


    }
}

The binder code looks correct.

This exception is typically thrown when the field doesn’t exist in the internal Hibernate Search schema, before Hibernate Search even looks at the content of the index. So I find it odd it’s being thrown only when a value is null. Are you sure that’s really what causes the problem? The problem doesn’t occur when all indexed values are non-null?

indexNullAs should not be necessary here in any case.

If you are using a custom mapping annotation, did you make sure to set the retention to RUNTIME (@Retention(RetentionPolicy.RUNTIME))?

Can I see your entity code (mapping annotations) and query code?

EDIT: Edited to mention custom mapping annotation.

Ive further found addNullObject. Maybe i’m required to use it? (Checking right now if it then works)

 if (bridgedElement == null) {
                target.addNullObject(this.objectField);
                return;
}

Custom Annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.FIELD })
@PropertyMapping(processor = @PropertyMappingAnnotationProcessorRef(
        type = AnzeigeStringerBinding.Processor.class
))
@Documented
public @interface AnzeigeStringerBinding {
    String fieldName() default "";

    class Processor implements PropertyMappingAnnotationProcessor<AnzeigeStringerBinding> {
        @Override
        public void process(PropertyMappingStep mapping, AnzeigeStringerBinding annotation,
                            PropertyMappingAnnotationProcessorContext context) {

            if (annotation.fieldName().isEmpty()) {
                mapping.binder(new AnzeigeStringerBinder());
                return;
            }
            mapping.binder(new AnzeigeStringerBinder(annotation.fieldName()));
        }
    }
}

Entity Mapping:

@ElementCollection
    @LazyCollection(LazyCollectionOption.FALSE)
    @CollectionTable(name = "KATASTER_FLURSTUECKE")
    @IndexedEmbedded(structure = ObjectStructure.NESTED)
    private List<Flurstueck> flurstuecke = new ArrayList<>();
    public static final String PATH_PRIMEGEMARKUNG = "primeGemarkung";

    @Transient
    @IndexingDependency(derivedFrom = @ObjectPath(
            @PropertyValue(propertyName = "flurstuecke")
    ))
    @AnzeigeStringerBinding(fieldName = PATH_PRIMEGEMARKUNG)
    public Flurstueck.Gemarkung getPrimeGemarkung() {
        return flurstuecke.stream().filter(Flurstueck::isPrime).findAny().map(Flurstueck::getGemarkung).orElse(null);
    }

Nope, that shouldn’t be related. Again, the exception you’re getting is only related to the internal Hibernate Search schema; the data that was actually indexed should be irrelevant.

Your custom annotation and entity mapping look fine.

Can you confirm the problem doesn’t occur when all indexed values are non-null?

Can you show me what your query looks like?

EDIT: Sorry for taking lots of your time, im currently confirming the error.

Query code is somewhat scattered around the application:

Path Building

GEMARKUNG(String.join(".", KauffallTE_.TE_GEBAEUDE, TEGebaeude_.TE_GEBAEUDE_KATASTER, TEGebaeudeKataster.PATH_PRIMEGEMARKUNG, AnzeigeStringerBinder.SORT_APPENDIX)),

Sort Part in Query (see sortProperty.getHibernateSearchIndexPath)

.sort(f -> {
                    final Optional<Sort.Order> firstOrder = Optional.of(pageable.getSort()).map(Sort::iterator)
                            .filter(Iterator::hasNext)
                            .map(Iterator::next);
                    if (firstOrder.isPresent()) {
                        final Sort.Order o = firstOrder.get();
                        final HibernateSearchSortProperty sortProperty = TESucheSortProperties.parseSortProperty(o.getProperty());
                        return f.field(sortProperty.getHibernateSearchIndexPath() /**<-- here the path appears */ ).order(mapOrder(o));
                    } else {
                        return f.field(KauffallSucheSortProperties.URKUNDENDATUM.getHibernateSearchIndexPath()).desc();
                    }
                })

Ok found it everything works but my mapping was wrong (because the gemarkung field occurs in multiple paths, i checked at the wrong place all the time) :see_no_evil: :see_no_evil:

— Sorry, Sorry, Sorry

1 Like