Skip to content

Implement FT.INFO command#3599

Open
jruaux wants to merge 9 commits into
mainfrom
ft-info-command
Open

Implement FT.INFO command#3599
jruaux wants to merge 9 commits into
mainfrom
ft-info-command

Conversation

@jruaux
Copy link
Copy Markdown
Contributor

@jruaux jruaux commented Dec 28, 2025

This PR implements the FT.INFO command to retrieve index information from RediSearch.

The implementation returns a IndexInfo object.

Changes:

  • Add IndexInfo class with index information including:
    • Basic index information (name, document counts, terms, records)
    • Index options and definition
    • Schema attributes
    • Size statistics (memory usage)
    • Indexing statistics (progress, failures, timing)
    • Garbage collection statistics
    • Cursor statistics
    • Dialect usage statistics
    • Error statistics
  • Add IndexInfoParser supporting both RESP2 and RESP3 protocols
  • Implement ftInfo across all API layers (sync, async, reactive, coroutines)
  • Add unit tests for IndexInfo class (11 tests, all passing)
  • Update integration tests

Testing:
All unit tests pass. Integration tests require a Redis instance with RediSearch module.


Note

Medium Risk
Adds a new RediSearch command and a fairly complex RESP2/RESP3 response parser with new public model types, which could affect client compatibility and parsing correctness across Redis versions.

Overview
Adds support for the RediSearch FT.INFO command across sync/async/reactive/coroutines APIs, wiring it through RediSearchCommandBuilder and introducing the FT_INFO CommandType.

Introduces a new IndexInfo model (with nested typed stats/field definitions and a forward-compatible additionalFields map) plus an IndexInfoParser that parses both RESP2 and RESP3 responses. Adds unit tests covering option/definition/field/stat parsing scenarios and edge cases.

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

@jruaux jruaux changed the title Add FT.INFO command with strongly-typed IndexInfo response Implement FT.INFO command Dec 28, 2025
@jruaux jruaux marked this pull request as draft December 29, 2025 07:48
@jruaux jruaux marked this pull request as ready for review December 29, 2025 21:25
@tishun
Copy link
Copy Markdown
Collaborator

tishun commented Feb 27, 2026

@a-TODO-rov went over the review and did some polishing myself:

  • added @since anntations for 7.5
  • fixed a few minor bugs in the parser
  • executed the apigenerator to include the interface everywhere (was missing 2 files)
  • etc.

IMHO we are good to go

@jit-ci
Copy link
Copy Markdown

jit-ci Bot commented Feb 27, 2026

🛡️ Jit Security Scan Results

CRITICAL HIGH MEDIUM

✅ No security findings were detected in this PR


Security scan by Jit

private IndexInfo.TextField<V> createTextField(V identifier, V attribute, boolean sortable, boolean unNormalizedForm,
boolean noIndex, boolean indexEmpty, boolean indexMissing, Map<String, Object> attributeMap,
Map<String, Object> additionalFields) {
Double weight = parseDouble(attributeMap.get(FLAG_WEIGHT));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

TextField weight always 0.0, never null when absent

Medium Severity

createTextField uses parseDouble(attributeMap.get(FLAG_WEIGHT)) which returns primitive double (defaulting to 0.0 when the key is absent). This gets autoboxed to Double.valueOf(0.0), so TextField.getWeight() never returns null — contradicting its documented contract of "or null if not set." The getDouble helper method already exists and correctly returns null for missing keys, so it looks like it was meant to be used here instead of parseDouble.

Additional Locations (1)

Fix in Cursor Fix in Web

&& !key.equals(FLAG_NOINDEX) && !key.equals(FLAG_INDEXEMPTY) && !key.equals(FLAG_INDEXMISSING)) {
vectorAttributes.put(key, entry.getValue());
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

RESP3 flags key leaks into vector attributes

Low Severity

In createVectorField, the exclusion list when building vectorAttributes does not include ATTR_FLAGS ("flags"). In RESP3, the attribute map contains a flags key with a List value (e.g., ["SORTABLE", "NOINDEX"]). This list is incorrectly included in vectorAttributes alongside actual vector attributes like DIM and DISTANCE_METRIC, polluting the map that VectorField.getAttributes() returns to callers.

Fix in Cursor Fix in Web

@jruaux
Copy link
Copy Markdown
Contributor Author

jruaux commented Apr 28, 2026

@tishun Can we include this in the next release?

@a-TODO-rov
Copy link
Copy Markdown
Contributor

Hey, @jruaux
Me and @tishun have spent some time on this PR on a multiple occasions and i personally don't think its ready to be merged. Here are some of the problems i noticed so far:

  1. This command has very complex response and its very different between RESP2/RESP3. I am concerned that this highly detailed parsing you are proposing is a huge overhead and might have some hidden bugs in it.
    Good example i think are the FIELDS - especially how the flags are being parsed - codesnippet:
for (int i = 0; i < list.size(); i += 2) {
    if (i + 1 < list.size()) {
        String key = decodeStringAsString(list.get(i));
        Object val = list.get(i + 1);
        // Handle special case where SORTABLE has a flag as its value (e.g., SORTABLE NOSTEM)
        // In this case, we need to add both SORTABLE and the flag (NOSTEM) as separate keys
        if (FLAG_SORTABLE.equals(key)) {
            attributeMap.put(key, val);
            String valStr = decodeStringAsString(val);
            // Check if the value is a known flag
            if (isFieldFlag(valStr)) {
                attributeMap.put(valStr, valStr);
            }
        } else {
            attributeMap.put(key, val);
        }
    }
}

How will this function behave for this response:

 7) attributes
 8) 1)  1) identifier
        2) title
        3) attribute
        4) product_title
        5) type
        6) TEXT
        7) WEIGHT
        8) "2"
        9) SORTABLE
       10) NOSTEM
       11) WITHSUFFIXTRIE

Will WITHSUFFIXTRIE be true ? This response is from testFtInfoCommand() integration test BTW.

  1. We have ongoing initiative to start promoting the Search API to production and start removing the Experimental tags. For this to happen however we needed to revisit the codecs being used in Search. It seems they are unnecessary, even worse they might introduce bugs in some scenarios. So this PR aims to remove them where not applicable. However i see your PR is still using them - we need to evaluate if they are correctly used, but from first glance, i would say no.

jruaux and others added 6 commits May 21, 2026 13:23
Implements FT.INFO command to retrieve index information from RediSearch.
Returns a strongly-typed IndexInfo object instead of Map<String, Object>
for better type safety and developer experience.

Changes:
- Add IndexInfo class with comprehensive index metadata
- Add IndexInfoParser supporting RESP2 and RESP3 protocols
- Implement ftInfo across all API layers (sync, async, reactive, coroutines)
- Add unit tests for IndexInfo class
- Update integration tests
- Changed IndexInfo from IndexInfo<K, V> to IndexInfo<V>
- Index name is now always String type (decoded with StringCodec)
- Updated all ftInfo method signatures across all command interfaces
- Updated IndexInfoParser to decode index name as String
- Updated all tests to use new signature
- Removed IndexInfoParserUnitTests (integration tests provide better coverage)

All integration tests passing (testFtInfoCommand*)
- Handle case where parseListValue already parsed ComplexData into List
- Add support for String values in parseLong, parseDouble, and parseBoolean methods
- Fix parsing of field flags when SORTABLE has a flag as its value (e.g., SORTABLE NOSTEM)
- Update getBoolean to decode non-String flag values using decodeStringAsString
- Add isFieldFlag helper method to identify known field flags
@tishun tishun force-pushed the ft-info-command branch from 43e6638 to 6f30018 Compare May 21, 2026 10:23
- Fix RESP2 field attribute parsing to handle standalone boolean flags
  (SORTABLE, NOSTEM, WITHSUFFIXTRIE, etc.) which are returned as single
  elements, not key-value pairs
- Add case-insensitive handling for COORD_SYSTEM and ALGORITHM attributes
  (RESP2 returns lowercase, RESP3 returns uppercase)
- Normalize vector attribute keys to uppercase and numeric values to Long
  for consistent types across protocol versions
- Remove phonetic field from TextField as Redis FT.INFO never returns it
  even when set during index creation
- Fix test to use non-default language (FRENCH) since Redis only returns
  default_language when it differs from the default (english)
- Fix vector field DATA_TYPE attribute name (was incorrectly TYPE)
Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 and found 2 potential issues.

There are 4 total unresolved issues (including 2 from previous reviews).

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 30c52c9. Configure here.

if (isStandaloneFlag(valStr) && !isFlagWithValue(key)) {
attributeMap.put(key, key);
i++;
continue;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

RESP2 field parsing misidentifies values matching flag names

Medium Severity

In parseResp2FieldAttributes, the heuristic that checks if the next element is a standalone flag to treat the current element as standalone can misparse valid key-value pairs. If a field's attribute value happens to match a standalone flag name (e.g., a field aliased as "SORTABLE"), the parser incorrectly treats both the key and value as standalone flags instead of a key-value pair, losing the actual value.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 30c52c9. Configure here.

private boolean isFieldFlag(String str) {
return FLAG_NOSTEM.equals(str) || FLAG_UNF.equals(str) || FLAG_NOINDEX.equals(str) || FLAG_PHONETIC.equals(str)
|| FLAG_WITHSUFFIXTRIE.equals(str) || FLAG_INDEXEMPTY.equals(str) || FLAG_INDEXMISSING.equals(str);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unused isFieldFlag method is dead code

Low Severity

The isFieldFlag method is defined but never called anywhere in the codebase. It appears to be leftover code superseded by the isStandaloneFlag and isFlagWithValue methods which handle the same classification. Its flag list also differs from isStandaloneFlag (missing SORTABLE and CASESENSITIVE, includes PHONETIC), adding confusion about the intended semantics.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 30c52c9. Configure here.

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.

3 participants