@Override
 protected FieldSortBuilder mutate(FieldSortBuilder original) throws IOException {
   FieldSortBuilder mutated = new FieldSortBuilder(original);
   int parameter = randomIntBetween(0, 5);
   switch (parameter) {
     case 0:
       mutated.setNestedPath(
           randomValueOtherThan(
               original.getNestedPath(), () -> randomAsciiOfLengthBetween(1, 10)));
       break;
     case 1:
       mutated.setNestedFilter(
           randomValueOtherThan(original.getNestedFilter(), () -> randomNestedFilter()));
       break;
     case 2:
       mutated.sortMode(
           randomValueOtherThan(original.sortMode(), () -> randomFrom(SortMode.values())));
       break;
     case 3:
       mutated.unmappedType(
           randomValueOtherThan(original.unmappedType(), () -> randomAsciiOfLengthBetween(1, 10)));
       break;
     case 4:
       mutated.missing(randomValueOtherThan(original.missing(), () -> randomFrom(missingContent)));
       break;
     case 5:
       mutated.order(randomValueOtherThan(original.order(), () -> randomFrom(SortOrder.values())));
       break;
     default:
       throw new IllegalStateException("Unsupported mutation.");
   }
   return mutated;
 }
  public FieldSortBuilder randomFieldSortBuilder() {
    String fieldName =
        rarely() ? FieldSortBuilder.DOC_FIELD_NAME : randomAsciiOfLengthBetween(1, 10);
    FieldSortBuilder builder = new FieldSortBuilder(fieldName);
    if (randomBoolean()) {
      builder.order(randomFrom(SortOrder.values()));
    }

    if (randomBoolean()) {
      builder.missing(randomFrom(missingContent));
    }

    if (randomBoolean()) {
      builder.unmappedType(randomAsciiOfLengthBetween(1, 10));
    }

    if (randomBoolean()) {
      builder.sortMode(randomFrom(SortMode.values()));
    }

    if (randomBoolean()) {
      builder.setNestedFilter(randomNestedFilter());
    }

    if (randomBoolean()) {
      builder.setNestedPath(randomAsciiOfLengthBetween(1, 10));
    }

    return builder;
  }
 static {
   PARSER.declareField(
       constructorArg(), Script::parse, ScriptField.SCRIPT, ValueType.OBJECT_OR_STRING);
   PARSER.declareField(
       constructorArg(), p -> ScriptSortType.fromString(p.text()), TYPE_FIELD, ValueType.STRING);
   PARSER.declareString((b, v) -> b.order(SortOrder.fromString(v)), ORDER_FIELD);
   PARSER.declareString((b, v) -> b.sortMode(SortMode.fromString(v)), SORTMODE_FIELD);
   PARSER.declareString(ScriptSortBuilder::setNestedPath, NESTED_PATH_FIELD);
   PARSER.declareObject(
       ScriptSortBuilder::setNestedFilter, SortBuilder::parseNestedFilter, NESTED_FILTER_FIELD);
 }
  public static GeoDistanceSortBuilder randomGeoDistanceSortBuilder() {
    String fieldName = randomAsciiOfLengthBetween(1, 10);
    GeoDistanceSortBuilder result = null;

    int id = randomIntBetween(0, 2);
    switch (id) {
      case 0:
        int count = randomIntBetween(1, 10);
        String[] geohashes = new String[count];
        for (int i = 0; i < count; i++) {
          geohashes[i] = RandomGeoGenerator.randomPoint(random()).geohash();
        }

        result = new GeoDistanceSortBuilder(fieldName, geohashes);
        break;
      case 1:
        GeoPoint pt = RandomGeoGenerator.randomPoint(random());
        result = new GeoDistanceSortBuilder(fieldName, pt.getLat(), pt.getLon());
        break;
      case 2:
        result = new GeoDistanceSortBuilder(fieldName, points(new GeoPoint[0]));
        break;
      default:
        throw new IllegalStateException("one of three geo initialisation strategies must be used");
    }
    if (randomBoolean()) {
      result.geoDistance(geoDistance(result.geoDistance()));
    }
    if (randomBoolean()) {
      result.unit(randomValueOtherThan(result.unit(), () -> randomFrom(DistanceUnit.values())));
    }
    if (randomBoolean()) {
      result.order(randomFrom(SortOrder.values()));
    }
    if (randomBoolean()) {
      result.sortMode(randomValueOtherThan(SortMode.SUM, () -> randomFrom(SortMode.values())));
    }
    if (randomBoolean()) {
      result.setNestedFilter(randomNestedFilter());
    }
    if (randomBoolean()) {
      result.setNestedPath(
          randomValueOtherThan(result.getNestedPath(), () -> randomAsciiOfLengthBetween(1, 10)));
    }
    if (randomBoolean()) {
      result.validation(
          randomValueOtherThan(
              result.validation(), () -> randomFrom(GeoValidationMethod.values())));
    }

    return result;
  }
 @Override
 protected GeoDistanceSortBuilder mutate(GeoDistanceSortBuilder original) throws IOException {
   GeoDistanceSortBuilder result = new GeoDistanceSortBuilder(original);
   int parameter = randomIntBetween(0, 8);
   switch (parameter) {
     case 0:
       while (Arrays.deepEquals(original.points(), result.points())) {
         GeoPoint pt = RandomGeoGenerator.randomPoint(random());
         result.point(pt.getLat(), pt.getLon());
       }
       break;
     case 1:
       result.points(points(original.points()));
       break;
     case 2:
       result.geoDistance(geoDistance(original.geoDistance()));
       break;
     case 3:
       result.unit(randomValueOtherThan(result.unit(), () -> randomFrom(DistanceUnit.values())));
       break;
     case 4:
       result.order(randomValueOtherThan(original.order(), () -> randomFrom(SortOrder.values())));
       break;
     case 5:
       result.sortMode(
           randomValueOtherThanMany(
               Arrays.asList(SortMode.SUM, result.sortMode())::contains,
               () -> randomFrom(SortMode.values())));
       break;
     case 6:
       result.setNestedFilter(
           randomValueOtherThan(original.getNestedFilter(), () -> randomNestedFilter()));
       break;
     case 7:
       result.setNestedPath(
           randomValueOtherThan(result.getNestedPath(), () -> randomAsciiOfLengthBetween(1, 10)));
       break;
     case 8:
       result.validation(
           randomValueOtherThan(
               result.validation(), () -> randomFrom(GeoValidationMethod.values())));
       break;
   }
   return result;
 }
  @Override
  public SortFieldAndFormat build(QueryShardContext context) throws IOException {
    final SearchScript searchScript =
        context.getSearchScript(script, ScriptContext.Standard.SEARCH, Collections.emptyMap());

    MultiValueMode valueMode = null;
    if (sortMode != null) {
      valueMode = MultiValueMode.fromString(sortMode.toString());
    }
    boolean reverse = (order == SortOrder.DESC);
    if (valueMode == null) {
      valueMode = reverse ? MultiValueMode.MAX : MultiValueMode.MIN;
    }

    final Nested nested = resolveNested(context, nestedPath, nestedFilter);
    final IndexFieldData.XFieldComparatorSource fieldComparatorSource;
    switch (type) {
      case STRING:
        fieldComparatorSource =
            new BytesRefFieldComparatorSource(null, null, valueMode, nested) {
              LeafSearchScript leafScript;

              @Override
              protected SortedBinaryDocValues getValues(LeafReaderContext context)
                  throws IOException {
                leafScript = searchScript.getLeafSearchScript(context);
                final BinaryDocValues values =
                    new BinaryDocValues() {
                      final BytesRefBuilder spare = new BytesRefBuilder();

                      @Override
                      public BytesRef get(int docID) {
                        leafScript.setDocument(docID);
                        spare.copyChars(leafScript.run().toString());
                        return spare.get();
                      }
                    };
                return FieldData.singleton(values, null);
              }

              @Override
              protected void setScorer(Scorer scorer) {
                leafScript.setScorer(scorer);
              }
            };
        break;
      case NUMBER:
        fieldComparatorSource =
            new DoubleValuesComparatorSource(null, Double.MAX_VALUE, valueMode, nested) {
              LeafSearchScript leafScript;

              @Override
              protected SortedNumericDoubleValues getValues(LeafReaderContext context)
                  throws IOException {
                leafScript = searchScript.getLeafSearchScript(context);
                final NumericDoubleValues values =
                    new NumericDoubleValues() {
                      @Override
                      public double get(int docID) {
                        leafScript.setDocument(docID);
                        return leafScript.runAsDouble();
                      }
                    };
                return FieldData.singleton(values, null);
              }

              @Override
              protected void setScorer(Scorer scorer) {
                leafScript.setScorer(scorer);
              }
            };
        break;
      default:
        throw new QueryShardException(
            context, "custom script sort type [" + type + "] not supported");
    }

    return new SortFieldAndFormat(
        new SortField("_script", fieldComparatorSource, reverse), DocValueFormat.RAW);
  }