1313import  org .apache .lucene .index .IndexableField ;
1414import  org .apache .lucene .index .LeafReader ;
1515import  org .apache .lucene .index .PostingsEnum ;
16+ import  org .apache .lucene .index .Term ;
1617import  org .apache .lucene .index .TermsEnum ;
18+ import  org .apache .lucene .search .IndexSearcher ;
1719import  org .apache .lucene .search .MatchNoDocsQuery ;
1820import  org .apache .lucene .search .Query ;
21+ import  org .apache .lucene .search .ScoreMode ;
22+ import  org .apache .lucene .search .TermQuery ;
1923import  org .apache .lucene .util .BytesRef ;
2024import  org .elasticsearch .common .logging .DeprecationCategory ;
2125import  org .elasticsearch .common .lucene .Lucene ;
2933import  org .elasticsearch .index .fielddata .IndexFieldData ;
3034import  org .elasticsearch .index .mapper .DocumentParserContext ;
3135import  org .elasticsearch .index .mapper .FieldMapper ;
36+ import  org .elasticsearch .index .mapper .FieldNamesFieldMapper ;
3237import  org .elasticsearch .index .mapper .MappedFieldType ;
3338import  org .elasticsearch .index .mapper .MapperBuilderContext ;
3439import  org .elasticsearch .index .mapper .MappingParserContext ;
@@ -430,6 +435,7 @@ private static class SparseVectorSyntheticFieldLoader implements SourceLoader.Sy
430435        private  final  String  leafName ;
431436
432437        private  TermsEnum  termsDocEnum ;
438+         private  boolean  hasValue ;
433439
434440        private  SparseVectorSyntheticFieldLoader (String  fullPath , String  leafName ) {
435441            this .fullPath  = fullPath ;
@@ -443,39 +449,60 @@ public Stream<Map.Entry<String, StoredFieldLoader>> storedFieldLoaders() {
443449
444450        @ Override 
445451        public  DocValuesLoader  docValuesLoader (LeafReader  leafReader , int [] docIdsInLeaf ) throws  IOException  {
446-             var  fieldInfos  = leafReader .getFieldInfos ().fieldInfo (fullPath );
447-             if  (fieldInfos  == null  || fieldInfos .hasTermVectors () == false ) {
448-                 return  null ;
452+             // Use an exists query on _field_names to distinguish documents with no value 
453+             // from those containing an empty map. 
454+             var  existsQuery  = new  TermQuery (new  Term (FieldNamesFieldMapper .NAME , fullPath ));
455+             var  searcher  = new  IndexSearcher (leafReader );
456+             searcher .setQueryCache (null );
457+             var  scorer  = searcher .createWeight (existsQuery , ScoreMode .COMPLETE_NO_SCORES , 0 ).scorer (searcher .getLeafContexts ().getFirst ());
458+             if  (scorer  == null ) {
459+                 return  docId  -> false ;
449460            }
461+ 
462+             var  fieldInfos  = leafReader .getFieldInfos ().fieldInfo (fullPath );
463+             boolean  hasTermVectors  = fieldInfos  != null  && fieldInfos .hasTermVectors ();
450464            return  docId  -> {
451-                 var  terms  = leafReader .termVectors ().get (docId , fullPath );
452-                 if  (terms  == null ) {
453-                     return  false ;
465+                 termsDocEnum  = null ;
466+ 
467+                 if  (scorer .iterator ().docID () < docId ) {
468+                     scorer .iterator ().advance (docId );
454469                }
455-                 termsDocEnum  = terms .iterator ();
456-                 if  (termsDocEnum .next () == null ) {
457-                     termsDocEnum  = null ;
458-                     return  false ;
470+                 if  (scorer .iterator ().docID () != docId ) {
471+                     return  hasValue  = false ;
459472                }
460-                 return  true ;
473+ 
474+                 if  (hasTermVectors  == false ) {
475+                     return  hasValue  = true ;
476+                 }
477+ 
478+                 var  terms  = leafReader .termVectors ().get (docId , fullPath );
479+                 if  (terms  != null ) {
480+                     termsDocEnum  = terms .iterator ();
481+                     if  (termsDocEnum .next () == null ) {
482+                         termsDocEnum  = null ;
483+                     }
484+                 }
485+                 return  hasValue  = true ;
461486            };
462487        }
463488
464489        @ Override 
465490        public  boolean  hasValue () {
466-             return  termsDocEnum  !=  null ;
491+             return  hasValue ;
467492        }
468493
469494        @ Override 
470495        public  void  write (XContentBuilder  b ) throws  IOException  {
471-             assert  termsDocEnum  != null ;
472-             PostingsEnum  reuse  = null ;
496+             assert  hasValue ;
473497            b .startObject (leafName );
474-             do  {
475-                 reuse  = termsDocEnum .postings (reuse );
476-                 reuse .nextDoc ();
477-                 b .field (termsDocEnum .term ().utf8ToString (), XFeatureField .decodeFeatureValue (reuse .freq ()));
478-             } while  (termsDocEnum .next () != null );
498+             if  (termsDocEnum  != null ) {
499+                 PostingsEnum  reuse  = null ;
500+                 do  {
501+                     reuse  = termsDocEnum .postings (reuse );
502+                     reuse .nextDoc ();
503+                     b .field (termsDocEnum .term ().utf8ToString (), XFeatureField .decodeFeatureValue (reuse .freq ()));
504+                 } while  (termsDocEnum .next () != null );
505+             }
479506            b .endObject ();
480507        }
481508
@@ -485,7 +512,10 @@ public void write(XContentBuilder b) throws IOException {
485512         * @throws IOException if reading fails 
486513         */ 
487514        private  Map <String , Float > copyAsMap () throws  IOException  {
488-             assert  termsDocEnum  != null ;
515+             assert  hasValue ;
516+             if  (termsDocEnum  == null ) {
517+                 return  Map .of ();
518+             }
489519            Map <String , Float > tokenMap  = new  LinkedHashMap <>();
490520            PostingsEnum  reuse  = null ;
491521            do  {
0 commit comments