Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions redisvl/redis/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def parse_vector_attrs(attrs):

return normalized

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

# Special handling for UNF:
# - For NUMERIC fields, Redis always adds UNF when SORTABLE is present
# - For TEXT fields, UNF is only present when explicitly set
# We only set unf=True for TEXT fields to avoid false positives
if "UNF" in attrs:
if field_type == "TEXT":
parsed_attrs["unf"] = True
attrs.remove("UNF")

for redis_attr, python_attr in boolean_attrs.items():
if redis_attr in attrs:
parsed_attrs[python_attr] = True
attrs.remove(redis_attr)

# Handle UNF which is associated with SORTABLE
if "UNF" in attrs:
attrs.remove("UNF") # UNF present on sortable numeric fields only

try:
# Parse remaining attributes as key-value pairs starting from index 6
parsed_attrs.update(
Expand All @@ -182,7 +188,7 @@ def parse_attrs(attrs):
if field_attrs[5] == "VECTOR":
field["attrs"] = parse_vector_attrs(field_attrs)
else:
field["attrs"] = parse_attrs(field_attrs)
field["attrs"] = parse_attrs(field_attrs, field_type=field_attrs[5])
# append field
schema_fields.append(field)

Expand Down
53 changes: 50 additions & 3 deletions redisvl/schema/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ class BaseFieldAttributes(BaseModel):
"""Enable faster result sorting on the field at runtime"""
index_missing: bool = Field(default=False)
"""Allow indexing and searching for missing values (documents without the field)"""
no_index: bool = Field(default=False)
"""Store field without indexing it (requires sortable=True or field is ignored)"""


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


class TagFieldAttributes(BaseFieldAttributes):
Expand All @@ -96,7 +100,8 @@ class TagFieldAttributes(BaseFieldAttributes):
class NumericFieldAttributes(BaseFieldAttributes):
"""Numeric field attributes"""

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


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

return RedisTextField(name, **kwargs)
# Add NOINDEX if enabled
if self.attrs.no_index: # type: ignore
kwargs["no_index"] = True

field = RedisTextField(name, **kwargs)

# Add UNF support (only when sortable)
# UNF must come before NOINDEX in the args_suffix
if self.attrs.unf and self.attrs.sortable: # type: ignore
if "NOINDEX" in field.args_suffix:
# Insert UNF before NOINDEX
noindex_idx = field.args_suffix.index("NOINDEX")
field.args_suffix.insert(noindex_idx, "UNF")
else:
# No NOINDEX, append normally
field.args_suffix.append("UNF")

return field


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

# Add NOINDEX if enabled
if self.attrs.no_index: # type: ignore
kwargs["no_index"] = True

return RedisTagField(name, **kwargs)


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

return RedisNumericField(name, **kwargs)
# Add NOINDEX if enabled
if self.attrs.no_index: # type: ignore
kwargs["no_index"] = True

field = RedisNumericField(name, **kwargs)

# Add UNF support (only when sortable)
# UNF must come before NOINDEX in the args_suffix
if self.attrs.unf and self.attrs.sortable: # type: ignore
if "NOINDEX" in field.args_suffix:
# Insert UNF before NOINDEX
noindex_idx = field.args_suffix.index("NOINDEX")
field.args_suffix.insert(noindex_idx, "UNF")
else:
# No NOINDEX, append normally
field.args_suffix.append("UNF")

return field


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

# Add NOINDEX if enabled
if self.attrs.no_index: # type: ignore
kwargs["no_index"] = True

return RedisGeoField(name, **kwargs)


Expand Down
Loading
Loading