Doc Score Retrieval In Hibernate Search 6

I am trying to port to HS 6.0 from 5.11 and have run into the following issue. Previously, in HS 5.11 I did the following to retrieve the score for each search result.

      FullTextQuery fullTextQuery = createFullTextQuery(criteria);
      fullTextQuery.setProjection(FullTextQuery.SCORE, FullTextQuery.THIS, FullTextQuery.DOCUMENT_ID);
      fullTextQuery.setFirstResult((int)pageable.getOffset());
      fullTextQuery.setMaxResults(pageable.getPageSize());

      //FullTextQuery will log if org.hibernate.search.fulltext_query is set to DEBUG
      projResultList = fullTextQuery.getResultList();
      //getResultSize must go after getResultList or query will be executed twice
      resultSize = fullTextQuery.getResultSize();;
      Iterator projIter = projResultList.iterator();
      int docId;
      float score;
      PatentDocDO doc;
      while (projIter.hasNext()) {
         projResult = (Object[]) projIter.next();
         docRes = new PatentDocSearchResultDO();
         score = (float) projEntry[0];
         doc = (PatentDocDO) projEntry[1];
         docId=(int) projResult[2];
         docRes.setDoc(doc);
         docRes.setRelevanceScore(score); 
         docRes.setDocId(docId);
         results.add(docRes);     

         }

Note that everything is done in a single query. As my search queries are quite complex, that is important from a performance perspective. In digging through the documentation of HS 6, I cannot find the equivalent of this in the query/projection DSL. I see the projection section which just returns the scores, but that requires a second search. Am I missing something? Is there a way in HS 6 to do this in a single query?

Thanks,
Keith
PS> Yes, my users are advanced patent searchers and understand the concepts and challenges around scoring.

I think you missed the “composite” projections: Hibernate Search 6.0.0.Final: Reference Documentation

You can do something like this:

SearchResult<PatentDocSearchResultDO> searchResult = Search.session(entityManager)
        .search(PatentDocDO.class)
        .select(f -> f.composite(
                (score, entity) -> {
                    PatentDocSearchResultDO hit = new PatentDocSearchResultDO();
                    hit.setDoc(entity);
                    hit.setRelevanceScore(score);
                },
                f.score(), f.entity()))
        .where(f -> {
            // Use your "criteria" here, and return the resulting predicate
        })
        .fetch((int)pageable.getOffset(), pageable.getPageSize());

results = searchResult.hits();
resultSize = searchResult.total().hitCount();

Or, better, if you declare an appropriate constructor in PatentDocSearchResultDO:

SearchResult<PatentDocSearchResultDO> searchResult = Search.session(entityManager)
        .search(PatentDocDO.class)
        .select(f -> f.composite(PatentDocSearchResultDO::new, f.score(), f.entity()))
        .where(f -> {
            // Use your "criteria" here, and return the resulting predicate
        })
        .fetch((int)pageable.getOffset(), pageable.getPageSize());

results = searchResult.hits();
resultSize = searchResult.total().hitCount();

The only thing missing here is the projection to a Lucene “docId” (which is a long). This feature was removed for a good reason: the docId can change from one query to another, so it’s not a reliable identifier outside of the context of a single query execution. Wherever Hibernate Search used to expect a Lucene “docId”, it now expects an entity identifier.

If you’re interested in some identifier, just for uniqueness, you can use the entity identifier (whatever you mapped with @Id or @DocumentId in your domain model) or the Hibernate Search document identifier (which is a String).
See entity reference projection and document reference projection. They both return an object with an id() method that returns an identifier.

I did see the composite section, but I misinterpreted it. I thought because the search was specifying a specific class (in this case PatentDocDO.class) that I could only retrieve class-associated projectable fields for the projection. Regarding the doc id, I no longer need that. I was pulling it previously in order to run explain, but that approach is no longer needed. Thank you very much.

Keith