@@ -245,21 +245,53 @@ && handleDistributed(req, rsp)) {
245245 rsp .setHttpCaching (false );
246246 }
247247
248- /** Tracks the first-seen valid properties of a field across shards. */
249- private static class ExpectedFieldConfig {
248+ /** Per-field accumulation state across shards: merged response data and index flags tracking. */
249+ private static class MergedFieldData {
250+ final SimpleOrderedMap <Object > merged = new SimpleOrderedMap <>();
251+ final String originalShardAddr ;
252+ private Object indexFlags ;
253+ private String indexFlagsShardAddr ;
254+
255+ MergedFieldData (String shardAddr , Object indexFlags ) {
256+ this .originalShardAddr = shardAddr ;
257+ if (indexFlags != null ) {
258+ this .indexFlags = indexFlags ;
259+ this .indexFlagsShardAddr = shardAddr ;
260+ }
261+ }
262+ }
263+
264+ private static class ShardData {
250265 final String shardAddr ;
251- final LukeResponse .FieldInfo fieldInfo ;
252- Object indexFlags ;
253- String indexFlagsShardAddr ;
266+ final Map < String , LukeResponse .FieldInfo > shardFieldInfo ;
267+ private NamedList < Object > indexInfo ;
268+ private SimpleOrderedMap < Object > detailedFields ;
254269
255- ExpectedFieldConfig (String shardAddr , LukeResponse .FieldInfo fieldInfo ) {
270+ ShardData (String shardAddr , Map < String , LukeResponse .FieldInfo > shardFieldInfo ) {
256271 this .shardAddr = shardAddr ;
257- this .fieldInfo = fieldInfo ;
258- Object flags = fieldInfo .getExtras ().get (KEY_INDEX_FLAGS );
259- if (flags != null ) {
260- this .indexFlags = flags ;
261- this .indexFlagsShardAddr = shardAddr ;
272+ this .shardFieldInfo = shardFieldInfo ;
273+ }
274+
275+ void setIndexInfo (NamedList <Object > indexInfo ) {
276+ this .indexInfo = indexInfo ;
277+ }
278+
279+ void addDetailedFieldInfo (String fieldName , SimpleOrderedMap <Object > fieldStats ) {
280+ if (detailedFields == null ) {
281+ detailedFields = new SimpleOrderedMap <>();
282+ }
283+ detailedFields .add (fieldName , fieldStats );
284+ }
285+
286+ SimpleOrderedMap <Object > toResponseEntry () {
287+ SimpleOrderedMap <Object > entry = new SimpleOrderedMap <>();
288+ if (indexInfo != null ) {
289+ entry .add (RSP_INDEX , indexInfo );
290+ }
291+ if (detailedFields != null ) {
292+ entry .add (RSP_FIELDS , detailedFields );
262293 }
294+ return entry ;
263295 }
264296 }
265297
@@ -268,8 +300,7 @@ private static class ExpectedFieldConfig {
268300 * short-circuited (e.g. single-shard collection) and the caller should fall through to local
269301 * logic.
270302 */
271- private boolean handleDistributed (SolrQueryRequest req , SolrQueryResponse rsp )
272- throws IOException {
303+ private boolean handleDistributed (SolrQueryRequest req , SolrQueryResponse rsp ) {
273304 ShardHandler shardHandler = shardHandlerFactory .getShardHandler ();
274305 ResponseBuilder rb = new ResponseBuilder (req , rsp , Collections .emptyList ());
275306 shardHandler .prepDistributed (rb );
@@ -324,9 +355,7 @@ private void mergeDistributedResponses(SolrQueryResponse rsp, List<ShardResponse
324355 long totalDeletedDocs = 0 ;
325356 int totalSegmentCount = 0 ;
326357
327- Map <String , SimpleOrderedMap <Object >> mergedFields = new HashMap <>();
328- Map <String , ExpectedFieldConfig > expectedFieldConfigs = new HashMap <>();
329- SimpleOrderedMap <Object > shardsInfo = new SimpleOrderedMap <>();
358+ Map <String , MergedFieldData > mergedFields = new HashMap <>();
330359
331360 if (!responses .isEmpty ()) {
332361 ShardResponse firstRsp = responses .getFirst ();
@@ -346,12 +375,14 @@ private void mergeDistributedResponses(SolrQueryResponse rsp, List<ShardResponse
346375 }
347376 }
348377
378+ List <ShardData > shardDataList = new ArrayList <>();
379+
349380 for (ShardResponse srsp : responses ) {
350- String shardAddr = shardAddress (srsp );
351381 NamedList <Object > shardRsp = srsp .getSolrResponse ().getResponse ();
352382 LukeResponse lukeRsp = new LukeResponse ();
353383 lukeRsp .setResponse (shardRsp );
354- SimpleOrderedMap <Object > perShardEntry = new SimpleOrderedMap <>();
384+ ShardData shardData = new ShardData (shardAddress (srsp ), lukeRsp .getFieldInfo ());
385+
355386 NamedList <Object > shardIndex = lukeRsp .getIndexInfo ();
356387 if (shardIndex != null ) {
357388 totalNumDocs += Optional .ofNullable (lukeRsp .getNumDocsAsLong ()).orElse (0L );
@@ -360,43 +391,19 @@ private void mergeDistributedResponses(SolrQueryResponse rsp, List<ShardResponse
360391 Number segCount = (Number ) shardIndex .get (KEY_SEGMENT_COUNT );
361392 totalSegmentCount += segCount != null ? segCount .intValue () : 0 ;
362393
363- perShardEntry . add ( RSP_INDEX , shardIndex );
394+ shardData . setIndexInfo ( shardIndex );
364395 }
365396
366- Map <String , LukeResponse .FieldInfo > shardFieldInfo = lukeRsp .getFieldInfo ();
367- if (shardFieldInfo != null ) {
368- SimpleOrderedMap <Object > perShardFields = new SimpleOrderedMap <>();
369-
370- for (Map .Entry <String , LukeResponse .FieldInfo > entry : shardFieldInfo .entrySet ()) {
371- String fieldName = entry .getKey ();
372- LukeResponse .FieldInfo fi = entry .getValue ();
373-
374- SimpleOrderedMap <Object > merged =
375- mergedFields .computeIfAbsent (fieldName , k -> new SimpleOrderedMap <>());
376-
377- mergeShardField (shardAddr , fi , merged , expectedFieldConfigs );
378-
379- // Detailed stats — kept per-shard, not merged
380- NamedList <Integer > topTerms = fi .getTopTerms ();
381- Object histogram = fi .getExtras ().get (KEY_HISTOGRAM );
397+ processShardFields (shardData , mergedFields );
398+ shardDataList .add (shardData );
399+ }
382400
383- if (topTerms != null || fi .getDistinct () > 0 || histogram != null ) {
384- perShardEntry .putIfAbsent (RSP_FIELDS , perShardFields );
385- SimpleOrderedMap <Object > detailedFieldInfo = new SimpleOrderedMap <>();
386- if (topTerms != null ) {
387- detailedFieldInfo .add (KEY_TOP_TERMS , topTerms );
388- }
389- if (fi .getDistinct () > 0 ) {
390- detailedFieldInfo .add (KEY_DISTINCT , fi .getDistinct ());
391- }
392- if (histogram != null ) {
393- detailedFieldInfo .add (KEY_HISTOGRAM , histogram );
394- }
395- perShardFields .add (fieldName , detailedFieldInfo );
396- }
397- }
401+ SimpleOrderedMap <Object > shardsInfo = new SimpleOrderedMap <>();
402+ for (ShardData sd : shardDataList ) {
403+ SimpleOrderedMap <Object > entry = sd .toResponseEntry ();
404+ if (!entry .isEmpty ()) {
405+ shardsInfo .add (sd .shardAddr , entry );
398406 }
399- shardsInfo .add (shardAddr , perShardEntry );
400407 }
401408
402409 SimpleOrderedMap <Object > mergedIndex = new SimpleOrderedMap <>();
@@ -408,109 +415,111 @@ private void mergeDistributedResponses(SolrQueryResponse rsp, List<ShardResponse
408415
409416 if (!mergedFields .isEmpty ()) {
410417 SimpleOrderedMap <Object > mergedFieldsNL = new SimpleOrderedMap <>();
411- for (Map .Entry <String , SimpleOrderedMap < Object > > entry : mergedFields .entrySet ()) {
412- mergedFieldsNL .add (entry .getKey (), entry .getValue ());
418+ for (Map .Entry <String , MergedFieldData > entry : mergedFields .entrySet ()) {
419+ mergedFieldsNL .add (entry .getKey (), entry .getValue (). merged );
413420 }
414421 rsp .add (RSP_FIELDS , mergedFieldsNL );
415422 }
416423
417424 rsp .add (RSP_SHARDS , shardsInfo );
418425 }
419426
427+ private void processShardFields (
428+ ShardData shardData , Map <String , MergedFieldData > mergedFields ) {
429+ if (shardData .shardFieldInfo == null ) {
430+ return ;
431+ }
432+ for (Map .Entry <String , LukeResponse .FieldInfo > entry : shardData .shardFieldInfo .entrySet ()) {
433+ String fieldName = entry .getKey ();
434+ LukeResponse .FieldInfo fi = entry .getValue ();
435+
436+ mergeShardField (shardData .shardAddr , fi , mergedFields );
437+
438+ // Detailed stats — kept per-shard, not merged
439+ NamedList <Integer > topTerms = fi .getTopTerms ();
440+ Object histogram = fi .getExtras ().get (KEY_HISTOGRAM );
441+
442+ if (topTerms != null || fi .getDistinct () > 0 || histogram != null ) {
443+ SimpleOrderedMap <Object > detailedFieldInfo = new SimpleOrderedMap <>();
444+ if (topTerms != null ) {
445+ detailedFieldInfo .add (KEY_TOP_TERMS , topTerms );
446+ }
447+ if (fi .getDistinct () > 0 ) {
448+ detailedFieldInfo .add (KEY_DISTINCT , fi .getDistinct ());
449+ }
450+ if (histogram != null ) {
451+ detailedFieldInfo .add (KEY_HISTOGRAM , histogram );
452+ }
453+ shardData .addDetailedFieldInfo (fieldName , detailedFieldInfo );
454+ }
455+ }
456+ }
457+
420458 private void mergeShardField (
421- String shardAddr ,
422- LukeResponse .FieldInfo fi ,
423- SimpleOrderedMap <Object > merged ,
424- Map <String , ExpectedFieldConfig > expectedFieldConfigs ) {
459+ String shardAddr , LukeResponse .FieldInfo fi , Map <String , MergedFieldData > mergedFields ) {
425460
426461 String fieldName = fi .getName ();
427- ExpectedFieldConfig origin = expectedFieldConfigs .get (fieldName );
428- if (origin == null ) {
429- origin = new ExpectedFieldConfig (shardAddr , fi );
430- expectedFieldConfigs .put (fieldName , origin );
462+ Object indexFlags = fi .getExtras ().get (KEY_INDEX_FLAGS );
463+
464+ MergedFieldData fieldData = mergedFields .get (fieldName );
465+ if (fieldData == null ) {
466+ fieldData = new MergedFieldData (shardAddr , indexFlags );
467+ mergedFields .put (fieldName , fieldData );
468+
431469 // First shard to report this field: populate merged with schema-derived attrs
432- merged .add (KEY_TYPE , fi .getType ());
433- merged .add (KEY_SCHEMA_FLAGS , fi .getSchema ());
470+ fieldData . merged .add (KEY_TYPE , fi .getType ());
471+ fieldData . merged .add (KEY_SCHEMA_FLAGS , fi .getSchema ());
434472 Object dynBase = fi .getExtras ().get (KEY_DYNAMIC_BASE );
435473 if (dynBase != null ) {
436- merged .add (KEY_DYNAMIC_BASE , dynBase );
474+ fieldData . merged .add (KEY_DYNAMIC_BASE , dynBase );
437475 }
438- if (origin .indexFlags != null ) {
439- merged .add (KEY_INDEX_FLAGS , origin .indexFlags );
476+ if (fieldData .indexFlags != null ) {
477+ fieldData . merged .add (KEY_INDEX_FLAGS , fieldData .indexFlags );
440478 }
441- } else {
442- // Subsequent shards: validate consistency
443- validateFieldAttr (
444- fieldName ,
445- KEY_TYPE ,
446- fi .getType (),
447- origin .fieldInfo .getType (),
448- shardAddr ,
449- origin .shardAddr );
450- validateFieldAttr (
451- fieldName ,
452- KEY_SCHEMA_FLAGS ,
453- fi .getSchema (),
454- origin .fieldInfo .getSchema (),
455- shardAddr ,
456- origin .shardAddr );
457- validateFieldAttr (
458- fieldName ,
459- KEY_DYNAMIC_BASE ,
460- fi .getExtras ().get (KEY_DYNAMIC_BASE ),
461- origin .fieldInfo .getExtras ().get (KEY_DYNAMIC_BASE ),
462- shardAddr ,
463- origin .shardAddr );
464-
465- Object indexFlags = fi .getExtras ().get (KEY_INDEX_FLAGS );
466- if (indexFlags != null ) {
467- if (origin .indexFlags == null ) {
468- origin .indexFlags = indexFlags ;
469- origin .indexFlagsShardAddr = shardAddr ;
470- merged .add (KEY_INDEX_FLAGS , indexFlags );
471- } else {
472- validateFieldAttr (
473- fieldName ,
474- KEY_INDEX_FLAGS ,
475- indexFlags ,
476- origin .indexFlags ,
477- shardAddr ,
478- origin .indexFlagsShardAddr );
479- }
479+ } else if (indexFlags != null ) {
480+ // Subsequent shards: validate index flags consistency
481+ if (fieldData .indexFlags == null ) {
482+ fieldData .indexFlags = indexFlags ;
483+ fieldData .indexFlagsShardAddr = shardAddr ;
484+ fieldData .merged .add (KEY_INDEX_FLAGS , indexFlags );
485+ } else {
486+ validateFieldAttr (
487+ fieldName , KEY_INDEX_FLAGS , indexFlags , fieldData .indexFlags ,
488+ shardAddr , fieldData .indexFlagsShardAddr );
480489 }
481490 }
482491
483492 Long docsAsLong = fi .getDocsAsLong ();
484493 if (docsAsLong != null && docsAsLong > 0 ) {
485- merged .compute (
494+ fieldData . merged .compute (
486495 KEY_DOCS_AS_LONG , (key , val ) -> val == null ? docsAsLong : (Long ) val + docsAsLong );
487496 }
488497 }
489498
490- /** Validates that a schema-derived attribute value is identical across shards. */
499+ /** Validates that a field attribute value is identical across shards. */
491500 private void validateFieldAttr (
492501 String fieldName ,
493502 String attrName ,
494503 Object currentVal ,
495- Object originVal ,
504+ Object expectedVal ,
496505 String currentShardAddr ,
497- String originShardAddr ) {
498- if (currentVal == null && originVal == null ) {
506+ String expectedShardAddr ) {
507+ if (currentVal == null && expectedVal == null ) {
499508 return ;
500509 }
501510 String currentStr = currentVal != null ? currentVal .toString () : null ;
502- String originStr = originVal != null ? originVal .toString () : null ;
503- if (!Objects .equals (currentStr , originStr )) {
511+ String expectedStr = expectedVal != null ? expectedVal .toString () : null ;
512+ if (!Objects .equals (currentStr , expectedStr )) {
504513 throw new SolrException (
505514 ErrorCode .SERVER_ERROR ,
506515 "Field '"
507516 + fieldName
508517 + "' has inconsistent '"
509518 + attrName
510519 + "' across shards: '"
511- + originStr
520+ + expectedStr
512521 + "' (from "
513- + originShardAddr
522+ + expectedShardAddr
514523 + ") vs '"
515524 + currentStr
516525 + "' (from "
0 commit comments