Skip to content

Rework codecs for redis search module - #3884

Open
viktoriya-kutsarova wants to merge 28 commits into
mainfrom
rework-codecs-for-redis-search-module
Open

Rework codecs for redis search module#3884
viktoriya-kutsarova wants to merge 28 commits into
mainfrom
rework-codecs-for-redis-search-module

Conversation

@viktoriya-kutsarova

@viktoriya-kutsarova viktoriya-kutsarova commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Updates the RediSearch API types to match how command data is encoded. Complete Redis keys keep K; schema identifiers, query tokens, index prefixes, and server-generated text use String and bypass the connection codec. The change also fixes warning decoding and RESP2 suggestion-score parsing.

Why

A schema field can be a hash field for ON HASH or a JSONPath for ON JSON, so it cannot consistently use the key codec. The same applies to query tokens and other RediSearch identifiers.

Changes

  • Removes generics from CreateArgs and argument types used for schema fields, queries, aggregation, hybrid search, highlighting, spell checking, and synonyms. These identifiers are sent raw; applications using a transforming key codec must pass an index prefix that matches the stored keys.
  • Keeps K for document keys in INKEYS, suggestion dictionary keys, and document ids returned in search replies.
  • Changes result field names and warnings to String.
  • Parses suggestion text and RESP2 scores as UTF-8 strings.
  • Changes FT.TAGVALS to return List<String>, matching FT.DICTDUMP and FT._LIST.
  • Updates the sync, async, reactive, cluster, Kotlin, and generated APIs to use the new types.

Testing

Codec-safety integration tests cover HASH and JSON with prefixing and structured key codecs. They verify that PREFIX is sent raw using the caller-provided physical prefix, while INKEYS still uses the key codec. Parser tests cover warnings, mixed field values, and RESP2 suggestion scores.

Risk

This is a breaking change to the experimental RediSearch APIs. Applications using transforming codecs must review schema identifiers and index prefixes when migrating.

Make sure that:

  • You have read the contribution guidelines.
  • You have created a feature request first to discuss your contribution intent. Please reference the feature request ticket number in the pull request.
  • You applied code formatting rules using the mvn formatter:format target. Don’t submit any formatting related changes.
  • You submit test cases (unit or integration tests) that back your changes.

Note

High Risk
Breaking change across the public RediSearch command, argument, and reply types (sync/async/reactive/cluster). Callers with custom codecs must re-check prefixes, queries, and result field types.

Overview
Aligns RediSearch encoding with Redis: schema names, queries, prefixes, and server text go as raw String, while document keys (INKEYS, suggestion dict keys, result ids) stay K.

RediSearchCommands (and async/reactive/cluster) drop the V type parameter. CreateArgs, field args, hybrid/aggregate/spellcheck/synonym helpers, and most replies (SearchReply<K>, AggregationReply<K>, SpellCheckResult, Suggestion) no longer use the value codec. Queries and dict/synonym terms are String; FT.TAGVALS / FT.DICTDUMP / FT._LIST return List<String>. Field names and warnings decode as UTF-8; field values stay raw bytes.

Command builders use args.add(...) instead of addValue for those tokens. Docs and tests cover HASH/JSON with prefixing codecs, warning parsing, and RESP2 suggestion scores. This is a breaking change to the experimental search APIs.

Reviewed by Cursor Bugbot for commit f5063dc. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 48187a5. Configure here.

}

final SearchReply.SearchResult<K, V> searchResult = new SearchReply.SearchResult<>(id);
final SearchReply.SearchResult<K> searchResult = new SearchReply.SearchResult<>(id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aggregation replies invent document id

Medium Severity

When parsing aggregation rows with withIds false, the RESP2 path still builds a result whose id is codec.decodeKey of the literal "0". The RESP3 path correctly uses a null id. Callers reading getId() on aggregate rows therefore see a fake "0" (or a codec-transformed form of it) instead of no id.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 48187a5. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, but pre-existing on main rather than introduced by this PR. The diff here only changes the generics around that line.

Neither protocol returns an id for aggregation rows, so the RESP2 path fabricates a "0" the server never sent, while RESP3 correctly yields null. It has gone unnoticed because no test in either protocol asserts getId() on aggregation rows.

The fix is initializing the id to null in SearchReplyParser.parseResults (aligning RESP2 with RESP3), plus a regression test in RediSearchAggregateIntegrationTests asserting aggregation rows expose null ids — inherited by the RESP2 subclass, so it runs under both protocols.

First I want to check this with @a-TODO-rov and @tishun.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 48187a53ab

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

);
Map<String, String> doc = new HashMap<>();
doc.put("title", "Wireless Headphones");
doc.put("embedding", embeddingStr);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Store vector fields as binary bytes

In this example the index is declared as a FLOAT32 vector and the query parameter below is converted to little-endian bytes, but the document's embedding is stored as Arrays.toString(embedding) through a Map<String, String>. RediSearch vector fields expect the stored hash value to be the binary vector blob (for FLOAT32, 4 bytes per dimension), so users following this guide will index an invalid/non-queryable vector and the KNN search below will not work as shown; use a binary value/byte-array write for the embedding field instead.

Useful? React with 👍 / 👎.

Comment on lines +323 to 324
Map<String, FieldValue> result = hybridResult.getFields();
if ("apple".equals(result.get("brand"))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Decode brand before branching in hybrid reducer test

After the reply fields were changed to FieldValue, result.get("brand") no longer returns a String, so both the Apple and Samsung branches are always skipped. This lets hybridWithReducerAvgMinMax pass without checking any AVG/MIN/MAX values as long as some result exists; compare result.get("brand").asString() and assert that the expected branches were exercised.

Useful? React with 👍 / 👎.

* @since 7.2
*/
@Experimental
HybridReply<K> ftHybrid(String index, HybridArgs args);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Import hybrid types in the command template

This new template declares ftHybrid with HybridReply and HybridArgs, but those types are not imported in the template's import block. The checked-in sync/async/reactive interfaces add the imports manually, so regenerating from this template would emit an interface that does not compile until the hybrid imports are added here as well.

Useful? React with 👍 / 👎.

* @param value the raw field value exactly as returned by the server. Must not be {@code null}.
* @return a {@link FieldValue} view over the given bytes
*/
public static FieldValue of(byte[] value) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add since tags to new FieldValue methods

This new public factory is part of the newly added FieldValue API, but its Javadoc omits @since 7.7. The repository requires @since on new public API, so generated docs will lack versioning for this entry point unless the tag is added.

AGENTS.md reference: AGENTS.md:L145-L146

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6d2ec3812

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

System.out.println("Key: " + result.getKey());
for (SearchReply.SearchResult<String> result : results.getResults()) {
System.out.println("Key: " + result.getId());
System.out.println("Title: " + result.getFields().get("title"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Unwrap FieldValue in search examples

With the new SearchReply API, getFields() now returns FieldValue instances instead of decoded strings. In the guide, users following this snippet will print the FieldValue object identity rather than the title; the other result-printing examples in this page have the same pattern. Decode textual fields with .asString() (or document .asBytes() for binary fields) so the updated public API is demonstrated correctly.

AGENTS.md reference: AGENTS.md:L40-L41

Useful? React with 👍 / 👎.

Comment on lines +79 to +83
public RediSearchPrefixingStringCodecSafetyIntegrationTests() {
RedisURI uri = RedisURI.Builder.redis("127.0.0.1").withPort(16379).build();
client = RedisClient.create(uri);
connection = client.connect(new PrefixingStringCodec(CODEC_PREFIX));
redis = connection.sync();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Move codec-safety clients out of the constructor

JUnit Jupiter uses per-method test instances unless the class is annotated with @TestInstance(PER_CLASS), but this constructor writes to static client/connection fields. Each test and parameterized invocation recreates and overwrites them, while @AfterAll closes only the last objects, leaving earlier Redis clients/connections running during the integration suite; the structured-key safety test uses the same pattern. Move this setup to @BeforeAll or make the fields instance-scoped and close them per test.

Useful? React with 👍 / 👎.

viktoriya-kutsarova and others added 26 commits August 24, 2026 15:56
…th String

# Conflicts:
#	.gitignore

# Conflicts:
#	src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
#	src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
#	src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterAsyncCommandsImpl.java
#	src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterReactiveCommandsImpl.java
#	src/main/kotlin/io/lettuce/core/api/coroutines/RediSearchCoroutinesCommandsImpl.kt
#	src/main/templates/io/lettuce/core/api/RediSearchCommands.java
…pdate all affected files.

Add additional unit and integration tests for the WITHSCORE param.
@a-TODO-rov
a-TODO-rov force-pushed the rework-codecs-for-redis-search-module branch from b6d2ec3 to a605ccf Compare August 24, 2026 12:59

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f5063dc096

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

private static RedisCommands<String, String> redis;

public RediSearchPrefixingStringCodecSafetyIntegrationTests() {
RedisURI uri = RedisURI.Builder.redis("127.0.0.1").withPort(16379).build();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Read the Redis endpoint from TestSettings

When integration tests run against a configured non-default host, port, or TLS topology, this constructor ignores the test settings and always connects to loopback port 16379, so both newly added codec-safety test classes can fail before reaching their assertions. Build the URI from TestSettings/TlsSettings instead of hard-coding the endpoint.

AGENTS.md reference: AGENTS.md:L135-L136

Useful? React with 👍 / 👎.

*
* @param <K> Key type.
* @param <V> Value type.
* @param Key type.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove invalid @PARAM tags after dropping generics

After GroupBy became non-generic, this @param Key tag refers to no declared parameter, which the Javadoc tool treats as an error and can break API-documentation generation. Remove this tag and the equivalent stale tags in SortBy, SortProperty, and their factory methods.

AGENTS.md reference: AGENTS.md:L153-L154

Useful? React with 👍 / 👎.

Comment on lines +78 to 79
public SugAddArgs payload(String payload) {
this.payload = payload;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve binary suggestion payloads

When callers store a non-UTF-8 FT.SUGADD PAYLOAD—previously supported with a binary value codec—the new String signature removes any way to supply the original bytes, and build() always UTF-8-encodes the value; SuggestionParser similarly UTF-8-decodes returned payloads. Keep a binary payload path, such as a byte[] overload and a byte-preserving result accessor, so opaque suggestion metadata still round-trips.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants