-
Notifications
You must be signed in to change notification settings - Fork 62
Description
Summary
TextField with index_missing=True, sortable=True, and unf=True fails at index creation due to incorrect field option ordering
When a RedisVL TextField is configured with all three of:
index_missing=Truesortable=Trueunf=True
the generated FT.CREATE field modifiers are ordered incorrectly:
- Generated:
TEXT ... SORTABLE UNF INDEXMISSING→ Redis rejects this. - Required:
TEXT ... INDEXMISSING SORTABLE UNF→ Redis accepts this.
RediSearch’s parser currently requires INDEXEMPTY and INDEXMISSING to appear before SORTABLE/UNF in the field definition (see redis/redis#5177). RedisVL’s TextField.as_redis_field() does not enforce that ordering, causing FT.CREATE to fail with:
ResponseError: Field 'INDEXMISSING' does not have a type.
A similar issue may affect other field types that support index_missing / index_empty and sortable / unf (e.g., NumericField, possibly TagField / GeoField).
Current Behavior
TextField.as_redis_field()(inredisvl/schema/fields.py) assembles kwargs for redis-py’sTextField, including:sortable(bool)index_missing(bool)index_empty(bool)no_index(bool)
- After constructing the redis-py
TextField, it then mutatesfield.args_suffixto insertUNFrelative toSORTABLE. - With
index_missing=True,sortable=True,unf=True, the resulting suffix is:["SORTABLE", "UNF", "INDEXMISSING"].
- This produces an
FT.CREATEfield definition equivalent to:... TEXT SORTABLE UNF INDEXMISSING,
which RediSearch parses as ifINDEXMISSINGwere a field name, hence:ResponseError: Field 'INDEXMISSING' does not have a type.
- There is a small script in the repo root (
test_unf_sortable.py) that demonstrates this; its final test case fails with precisely this error and prints the problematic suffix ordering.
Expected Behavior
- A
TextFieldwithindex_missing=True,sortable=True, andunf=Trueshould generate field modifiers in an order acceptable to RediSearch, e.g.:TEXT ... INDEXMISSING SORTABLE UNF.
- More generally, the canonical ordering should be:
[INDEXEMPTY] [INDEXMISSING] [SORTABLE [UNF]] [NOINDEX].
So that:
INDEXEMPTYandINDEXMISSINGalways precedeSORTABLE/UNF.UNFis only present if the field is sortable.NOINDEXappears at the end.
Reproduction Steps
-
Configure a
TextFieldin RedisVL with:sortable=Trueunf=Trueindex_missing=True
either directly or via anIndexSchemadefinition, for example (conceptually):- Text field:
work_experience_summary - Attributes:
{ "sortable": True, "unf": True, "index_missing": True }.
-
Inspect the generated redis-py field arguments:
- Call
field = TextField(...).as_redis_field(). - Inspect
field.redis_args()orfield.args_suffix.
- Call
-
Observed output (from
test_unf_sortable.py):- Base args:
["TEXT", "WEIGHT", 1]. - Suffix:
["SORTABLE", "UNF", "INDEXMISSING"].
- Base args:
-
Use this field in an index and call
SearchIndex.create()against a Redis instance with RediSearch ≥ 2.10 (e.g., Redis Stack7.4.0-v1). -
Actual result:
FT.CREATEfails with:ResponseError: Field 'INDEXMISSING' does not have a type.
-
Manually modifying the command to reorder the modifiers as:
TEXT INDEXMISSING SORTABLE UNF
produces a successfulFT.CREATE, confirming that the problem is purely the option ordering.
Actual Behavior
- RedisVL currently lets redis-py assemble
args_suffixsuch that:SORTABLEandUNFcome beforeINDEXMISSING.
- RediSearch’s parser expects
INDEXEMPTY/INDEXMISSINGbeforeSORTABLEand treatsINDEXMISSINGat the end as if it were a field name rather than an option. - Index creation fails whenever
index_missing=True,sortable=True, andunf=Trueare combined on the sameTextField.
Root Cause
- RediSearch parser limitation / bug (see AOF file corrupted after a power loss aof-load-truncated yes fails too redis#5177):
INDEXEMPTYandINDEXMISSINGmust appear beforeSORTABLEin theFT.CREATEfield definition.- Certain permutations of options that are “semantically OK” are rejected because of this ordering constraint.
- RedisVL’s
TextField.as_redis_field():- Enables
index_missing/index_emptyvia kwargs. - Then injects
UNFby inserting it relative toSORTABLE, without re-normalizing the full list of modifiers to satisfy RediSearch’s required order.
- Enables
- Similar logic exists in
NumericField.as_redis_field()and may require the same normalization.
Proposed Solution
-
Normalize
args_suffixordering inas_redis_field()- For
TextField:- After constructing the underlying redis-py
TextField, collect any of:INDEXEMPTY,INDEXMISSING,SORTABLE,UNF,NOINDEX.
- Remove them from
field.args_suffix, remember their presence, and then rebuild the suffix in a canonical order:- Other tokens (if any, not in this set), followed by:
INDEXEMPTY(if present),INDEXMISSING(if present),SORTABLE(if present),UNF(if sortable and requested),NOINDEX(if present).
- After constructing the underlying redis-py
- Apply similar logic to
NumericField.as_redis_field()(and any other field types that can combine these options).
- For
-
Respect
UNFconstraints- Ensure
UNFis only emitted ifsortable=True. - If
unf=Truebutsortable=False, dropUNFor raise a validation error.
- Ensure
-
Testing
- Unit tests for
TextField(andNumericFieldwhere applicable):- Validate that for combinations like:
index_missing=True,sortable=True,unf=True.index_empty=True,sortable=True,unf=True.- Both
index_empty=Trueandindex_missing=Truewithsortable=True,unf=True.
the resultingredis_args()putINDEXEMPTY/INDEXMISSINGbeforeSORTABLE/UNF.
- Validate that for combinations like:
- Integration tests with Redis:
- Create indices using these combinations via RedisVL.
- Assert that
SearchIndex.create()completes successfully withoutFT.CREATEparse errors.
- Unit tests for
-
Optional: centralize ordering rules
- To avoid duplication, consider a small helper function for normalizing field modifier order that can be reused across field types (
TextField,NumericField, etc.).
- To avoid duplication, consider a small helper function for normalizing field modifier order that can be reused across field types (