FTHybridPostProcessingParams.java
package redis.clients.jedis.search.hybrid;
import redis.clients.jedis.CommandArguments;
import redis.clients.jedis.annots.Experimental;
import redis.clients.jedis.params.IParams;
import redis.clients.jedis.search.Apply;
import redis.clients.jedis.search.Filter;
import redis.clients.jedis.search.Limit;
import redis.clients.jedis.search.aggr.Group;
import redis.clients.jedis.search.aggr.SortedField;
import redis.clients.jedis.util.JedisAsserts;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.GROUPBY;
import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.LOAD;
import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.NOSORT;
import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.SORTBY;
/**
* Arguments for post-processing operations in FT.HYBRID command. Supports LOAD, GROUPBY, APPLY,
* SORTBY, FILTER, and LIMIT operations.
* <p>
* Operations are applied in a specific order:
* <ol>
* <li>LOAD - fields to load</li>
* <li>GROUPBY - grouping with reducers</li>
* <li>APPLY - computed fields</li>
* <li>SORTBY - sorting</li>
* <li>FILTER - filtering results</li>
* <li>LIMIT - pagination</li>
* </ol>
*/
@Experimental
public class FTHybridPostProcessingParams implements IParams {
private List<String> loadFields;
private boolean loadAll;
private Group groupBy;
private final List<Apply> applies = new ArrayList<>();
private SortedField[] sortByFields;
private boolean noSort;
private Filter filter;
private Limit limit;
private static final String LOAD_ALL = "*";
private FTHybridPostProcessingParams() {
}
/**
* @return a new {@link Builder} for {@link FTHybridPostProcessingParams}.
*/
public static Builder builder() {
return new Builder();
}
/**
* Builder for {@link FTHybridPostProcessingParams}.
*/
public static class Builder {
private final FTHybridPostProcessingParams instance = new FTHybridPostProcessingParams();
/**
* Build the {@link FTHybridPostProcessingParams} instance.
* @return the configured arguments
*/
public FTHybridPostProcessingParams build() {
return instance;
}
/**
* Set the fields to load in the results.
* <p>
* This method replaces any previous load configuration (including loadAll()). To load all
* fields, use {@link #loadAll()} instead.
* @param fields the field names to load (must not be empty)
* @return this builder
* @throws IllegalArgumentException if fields is null, empty, or contains "*"
*/
public Builder load(String... fields) {
JedisAsserts.notNull(fields, "Fields must not be null");
JedisAsserts.isTrue(fields.length > 0, "At least one field is required");
// Validate no wildcards in specific field list
for (String field : fields) {
JedisAsserts.notNull(field, "Field names cannot be null");
JedisAsserts.isFalse(LOAD_ALL.equals(field),
"Cannot use '*' in load(). Use loadAll() instead to load all fields.");
}
// Clear previous state and set new values
instance.loadAll = false;
instance.loadFields = Arrays.asList(fields);
return this;
}
/**
* Set to load all fields in the results using LOAD *.
* <p>
* This method replaces any previous load configuration (including specific fields).
* <p>
* Note: requires Redis version >= 8.6.0
* @return this builder
*/
public Builder loadAll() {
instance.loadAll = true;
instance.loadFields = null;
return this;
}
/**
* Add a GROUPBY operation using {@link Group} from the aggregation package.
* @param group the group operation with reducers
* @return this builder
*/
public Builder groupBy(Group group) {
instance.groupBy = group;
return this;
}
/**
* Add an APPLY operation.
* @param apply the apply operation
* @return this builder
*/
public Builder apply(Apply apply) {
instance.applies.add(apply);
return this;
}
/**
* Add a SORTBY operation using {@link SortedField} from the aggregation package.
* <p>
* Last call to {@link #sortBy(SortedField...)}/{@link #noSort()} wins.
* @param fields the sorted fields
* @return this builder
* @see Builder#noSort()
*/
public Builder sortBy(SortedField... fields) {
JedisAsserts.notNull(fields, "Sort by fields must not be null");
JedisAsserts.isTrue(fields.length > 0, "At least one field is required");
instance.sortByFields = fields;
instance.noSort = false;
return this;
}
/**
* Disable the default sorting by score. This adds the NOSORT keyword to the command.
* <p>
* Last call to {@link #sortBy(SortedField...)}/{@link #noSort()} wins.
* @return this builder
* @see Builder#sortBy(SortedField...)
*/
public Builder noSort() {
instance.noSort = true;
instance.sortByFields = null;
return this;
}
/**
* Add a FILTER operation.
* @param filter the filter operation
* @return this builder
*/
public Builder filter(Filter filter) {
instance.filter = filter;
return this;
}
/**
* Add a LIMIT operation.
* @param limit the limit operation
* @return this builder
*/
public Builder limit(Limit limit) {
instance.limit = limit;
return this;
}
}
@Override
public void addParams(CommandArguments args) {
// LOAD clause
if (loadAll || (loadFields != null && !loadFields.isEmpty())) {
args.add(LOAD);
if (loadAll) {
// Special case for LOAD *
args.add(LOAD_ALL);
} else {
args.add(loadFields.size());
for (String field : loadFields) {
// Add @ prefix if not already present
if (!field.startsWith("@")) {
args.add("@" + field);
} else {
args.add(field);
}
}
}
}
// GROUPBY - convert aggr.Group to CommandArguments
if (groupBy != null) {
List<Object> groupArgs = new ArrayList<>();
groupBy.addArgs(groupArgs);
args.add(GROUPBY);
args.addObjects(groupArgs);
}
for (Apply apply : applies) {
apply.addParams(args);
}
// SORTBY or NOSORT - mutually exclusive
if (noSort) {
args.add(NOSORT);
} else if (sortByFields != null && sortByFields.length > 0) {
args.add(SORTBY);
args.add(sortByFields.length * 2);
for (SortedField field : sortByFields) {
args.add(field.getField());
args.add(field.getOrder());
}
}
if (filter != null) {
filter.addParams(args);
}
if (limit != null) {
limit.addParams(args);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FTHybridPostProcessingParams that = (FTHybridPostProcessingParams) o;
return loadAll == that.loadAll && noSort == that.noSort
&& Objects.equals(loadFields, that.loadFields) && Objects.equals(groupBy, that.groupBy)
&& Objects.equals(applies, that.applies) && Arrays.equals(sortByFields, that.sortByFields)
&& Objects.equals(filter, that.filter) && Objects.equals(limit, that.limit);
}
@Override
public int hashCode() {
int result = Objects.hash(loadFields, loadAll, groupBy, applies, noSort, filter, limit);
result = 31 * result + Arrays.hashCode(sortByFields);
return result;
}
}