SearchFeatureFlags.java
package redis.clients.jedis.util;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.UnifiedJedis;
/**
* Helper for toggling server-side Redis Search feature flags around an integration test class.
* <p>
* Several COLLECT-style features in Redis Search are gated behind
* {@code search-enable-unstable-features}; the server replies
* {@code SEARCH_QUERY_BAD `COLLECT` is unavailable when `ENABLE_UNSTABLE_FEATURES` is off} when the
* flag is off. This helper flips it on through the provided {@link UnifiedJedis} client.
* <p>
* For {@link redis.clients.jedis.RedisClusterClient} the {@code CONFIG SET} is automatically
* broadcast to every primary shard by the cluster executor, so a single call here is enough ��� no
* per-node iteration is required.
*/
public final class SearchFeatureFlags {
public static final String UNSTABLE_FEATURES_KEY = "search-enable-unstable-features";
private static final Logger log = LoggerFactory.getLogger(SearchFeatureFlags.class);
private static final int VERIFY_ATTEMPTS = 3;
private static final long VERIFY_BACKOFF_MILLIS = 100L;
private SearchFeatureFlags() {
}
/**
* Set {@code search-enable-unstable-features=yes} via the supplied client and return the previous
* value so callers can restore it later. Returns {@code null} when the flag is not configurable
* on the target Redis build ��� the caller should typically assume-skip in that case.
*/
public static String enableUnstable(UnifiedJedis client) {
return set(client, "yes");
}
/**
* Sets {@code search-enable-unstable-features} to {@code value} via the supplied client and
* verifies (with up to {@value #VERIFY_ATTEMPTS} retries) that the new value is observable
* through a follow-up {@code CONFIG GET}. The verification step matters for
* {@link redis.clients.jedis.RedisClusterClient} because the broadcast {@code CONFIG SET}
* propagates per-node and we want to be sure every shard has caught up before tests start running
* queries that depend on the flag.
* <p>
* Returns the previous value (or {@code null} when the flag isn't configurable, the call failed,
* or verification did not converge within the retry budget).
*/
public static String set(UnifiedJedis client, String value) {
String previous = readFlag(client);
if (previous == null) {
return null;
}
if (value.equalsIgnoreCase(previous)) {
return previous;
}
try {
client.configSet(UNSTABLE_FEATURES_KEY, value);
} catch (Exception e) {
log.debug("CONFIG SET {}={} failed", UNSTABLE_FEATURES_KEY, value, e);
return null;
}
for (int attempt = 1; attempt <= VERIFY_ATTEMPTS; attempt++) {
String observed = readFlag(client);
if (observed != null && value.equalsIgnoreCase(observed)) {
return previous;
}
log.debug("attempt {}/{} ��� {} still reads as {}", attempt, VERIFY_ATTEMPTS,
UNSTABLE_FEATURES_KEY, observed);
try {
Thread.sleep(VERIFY_BACKOFF_MILLIS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return null;
}
}
log.warn("CONFIG SET {}={} did not converge after {} retries", UNSTABLE_FEATURES_KEY, value,
VERIFY_ATTEMPTS);
return null;
}
private static String readFlag(UnifiedJedis client) {
try {
Map<String, String> reply = client.configGet(UNSTABLE_FEATURES_KEY);
return reply == null ? null : reply.get(UNSTABLE_FEATURES_KEY);
} catch (Exception e) {
log.debug("CONFIG GET {} failed", UNSTABLE_FEATURES_KEY, e);
return null;
}
}
}