Skip to content

Commit 2660e2f

Browse files
committed
feat: add native support for UNF and NOINDEX field attributes (#374)
Implements native support for Redis field attributes UNF (un-normalized form) and NOINDEX to provide more control over field indexing and sorting behavior. BREAKING CHANGE: None - all changes are backward compatible with default values - Add `no_index` attribute to BaseFieldAttributes for all field types - Add `unf` attribute to TextFieldAttributes and NumericFieldAttributes - Both attributes default to False maintaining backward compatibility Field Support: - TextField: Supports both no_index and unf attributes - NumericField: Supports both no_index and unf attributes - TagField: Supports no_index attribute - GeoField: Supports no_index attribute Technical Implementation: - NOINDEX implemented via redis-py's native no_index parameter - UNF added via args_suffix manipulation with proper ordering - Both attributes require sortable=True to take effect - Special parsing logic handles Redis auto-adding UNF to sortable numeric fields Fixes #374
1 parent 81f7e85 commit 2660e2f

File tree

4 files changed

+724
-9
lines changed

4 files changed

+724
-9
lines changed

redisvl/redis/connection.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def parse_vector_attrs(attrs):
136136

137137
return normalized
138138

139-
def parse_attrs(attrs):
139+
def parse_attrs(attrs, field_type=None):
140140
# 'SORTABLE', 'NOSTEM' don't have corresponding values.
141141
# Their presence indicates boolean True
142142
# TODO 'WITHSUFFIXTRIE' is another boolean attr, but is not returned by ft.info
@@ -150,17 +150,23 @@ def parse_attrs(attrs):
150150
"SORTABLE": "sortable",
151151
"INDEXMISSING": "index_missing",
152152
"INDEXEMPTY": "index_empty",
153+
"NOINDEX": "no_index",
153154
}
154155

156+
# Special handling for UNF:
157+
# - For NUMERIC fields, Redis always adds UNF when SORTABLE is present
158+
# - For TEXT fields, UNF is only present when explicitly set
159+
# We only set unf=True for TEXT fields to avoid false positives
160+
if "UNF" in attrs:
161+
if field_type == "TEXT":
162+
parsed_attrs["unf"] = True
163+
attrs.remove("UNF")
164+
155165
for redis_attr, python_attr in boolean_attrs.items():
156166
if redis_attr in attrs:
157167
parsed_attrs[python_attr] = True
158168
attrs.remove(redis_attr)
159169

160-
# Handle UNF which is associated with SORTABLE
161-
if "UNF" in attrs:
162-
attrs.remove("UNF") # UNF present on sortable numeric fields only
163-
164170
try:
165171
# Parse remaining attributes as key-value pairs starting from index 6
166172
parsed_attrs.update(
@@ -182,7 +188,7 @@ def parse_attrs(attrs):
182188
if field_attrs[5] == "VECTOR":
183189
field["attrs"] = parse_vector_attrs(field_attrs)
184190
else:
185-
field["attrs"] = parse_attrs(field_attrs)
191+
field["attrs"] = parse_attrs(field_attrs, field_type=field_attrs[5])
186192
# append field
187193
schema_fields.append(field)
188194

redisvl/schema/fields.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ class BaseFieldAttributes(BaseModel):
6363
"""Enable faster result sorting on the field at runtime"""
6464
index_missing: bool = Field(default=False)
6565
"""Allow indexing and searching for missing values (documents without the field)"""
66+
no_index: bool = Field(default=False)
67+
"""Store field without indexing it (requires sortable=True or field is ignored)"""
6668

6769

6870
class TextFieldAttributes(BaseFieldAttributes):
@@ -78,6 +80,8 @@ class TextFieldAttributes(BaseFieldAttributes):
7880
"""Used to perform phonetic matching during search"""
7981
index_empty: bool = Field(default=False)
8082
"""Allow indexing and searching for empty strings"""
83+
unf: bool = Field(default=False)
84+
"""Un-normalized form - disable normalization on sortable fields (only applies when sortable=True)"""
8185

8286

8387
class TagFieldAttributes(BaseFieldAttributes):
@@ -96,7 +100,8 @@ class TagFieldAttributes(BaseFieldAttributes):
96100
class NumericFieldAttributes(BaseFieldAttributes):
97101
"""Numeric field attributes"""
98102

99-
pass
103+
unf: bool = Field(default=False)
104+
"""Un-normalized form - disable normalization on sortable fields (only applies when sortable=True)"""
100105

101106

102107
class GeoFieldAttributes(BaseFieldAttributes):
@@ -223,7 +228,24 @@ def as_redis_field(self) -> RedisField:
223228
if self.attrs.index_empty: # type: ignore
224229
kwargs["index_empty"] = True
225230

226-
return RedisTextField(name, **kwargs)
231+
# Add NOINDEX if enabled
232+
if self.attrs.no_index: # type: ignore
233+
kwargs["no_index"] = True
234+
235+
field = RedisTextField(name, **kwargs)
236+
237+
# Add UNF support (only when sortable)
238+
# UNF must come before NOINDEX in the args_suffix
239+
if self.attrs.unf and self.attrs.sortable: # type: ignore
240+
if "NOINDEX" in field.args_suffix:
241+
# Insert UNF before NOINDEX
242+
noindex_idx = field.args_suffix.index("NOINDEX")
243+
field.args_suffix.insert(noindex_idx, "UNF")
244+
else:
245+
# No NOINDEX, append normally
246+
field.args_suffix.append("UNF")
247+
248+
return field
227249

228250

229251
class TagField(BaseField):
@@ -253,6 +275,10 @@ def as_redis_field(self) -> RedisField:
253275
if self.attrs.index_empty: # type: ignore
254276
kwargs["index_empty"] = True
255277

278+
# Add NOINDEX if enabled
279+
if self.attrs.no_index: # type: ignore
280+
kwargs["no_index"] = True
281+
256282
return RedisTagField(name, **kwargs)
257283

258284

@@ -277,7 +303,24 @@ def as_redis_field(self) -> RedisField:
277303
if self.attrs.index_missing: # type: ignore
278304
kwargs["index_missing"] = True
279305

280-
return RedisNumericField(name, **kwargs)
306+
# Add NOINDEX if enabled
307+
if self.attrs.no_index: # type: ignore
308+
kwargs["no_index"] = True
309+
310+
field = RedisNumericField(name, **kwargs)
311+
312+
# Add UNF support (only when sortable)
313+
# UNF must come before NOINDEX in the args_suffix
314+
if self.attrs.unf and self.attrs.sortable: # type: ignore
315+
if "NOINDEX" in field.args_suffix:
316+
# Insert UNF before NOINDEX
317+
noindex_idx = field.args_suffix.index("NOINDEX")
318+
field.args_suffix.insert(noindex_idx, "UNF")
319+
else:
320+
# No NOINDEX, append normally
321+
field.args_suffix.append("UNF")
322+
323+
return field
281324

282325

283326
class GeoField(BaseField):
@@ -301,6 +344,10 @@ def as_redis_field(self) -> RedisField:
301344
if self.attrs.index_missing: # type: ignore
302345
kwargs["index_missing"] = True
303346

347+
# Add NOINDEX if enabled
348+
if self.attrs.no_index: # type: ignore
349+
kwargs["no_index"] = True
350+
304351
return RedisGeoField(name, **kwargs)
305352

306353

0 commit comments

Comments
 (0)