Skip to content

Commit 8ccbf4f

Browse files
committed
fix: empty OR operand guard, LIKE error message, README negation typo
- Raise ValueError for empty OR operands ('laptop OR ', ' OR tablet') instead of crashing with IndexError - LIKE error message now says 'LIKE can only be used...' instead of 'like() can only be used...' since LIKE is not a function - Fix README table: negation of single-term FULLTEXT doesn't have parens - Add 2 new tests (370 total)
1 parent eb929ec commit 8ccbf4f

4 files changed

Lines changed: 24 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ Full-text search on TEXT fields with multiple search modes:
187187
| Proximity + order | `fulltext(title, 'gaming laptop', 2, true)` | `@title:(gaming laptop) => { $slop: 2; $inorder: true; }` |
188188
| Optional term | `fulltext(title, 'laptop ~gaming')` | `@title:(laptop ~gaming)` |
189189
| BM25 score | `SELECT score() AS relevance FROM idx` | `FT.SEARCH ... WITHSCORES` |
190-
| Negation | `NOT fulltext(title, 'refurbished')` | `-@title:(refurbished)` |
190+
| Negation | `NOT fulltext(title, 'refurbished')` | `-@title:refurbished` |
191191

192192
**Examples:**
193193

sql_redis/query_builder.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@ def build_text_condition(
148148
or_parts: list[str] = []
149149
for part in re.split(r"\s+[Oo][Rr]\s+", value):
150150
words = part.strip().split()
151+
if not words:
152+
raise ValueError(
153+
"Empty operand in OR expression — each side of OR "
154+
"must contain at least one search term."
155+
)
151156
if len(words) > 1:
152157
escaped = " ".join(self._escape_fulltext_term(w) for w in words)
153158
or_parts.append(f"({escaped})")

sql_redis/translator.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,13 @@ def _build_condition(self, condition: Condition, field_type: str | None) -> str:
197197
# only make sense for TEXT fields; silently falling through to TAG/NUMERIC
198198
# would produce incorrect queries.
199199
if condition.operator in ("FUZZY", "FULLTEXT", "LIKE") and field_type != "TEXT":
200+
op_display = (
201+
"LIKE"
202+
if condition.operator == "LIKE"
203+
else f"{condition.operator.lower()}()"
204+
)
200205
raise ValueError(
201-
f"{condition.operator.lower()}() can only be used on TEXT fields, "
206+
f"{op_display} can only be used on TEXT fields, "
202207
f"but '{condition.field}' is {field_type or 'unknown'}."
203208
)
204209

tests/test_query_builder.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,3 +678,15 @@ def test_or_both_multiword_operands_grouped(self):
678678
"title", "FULLTEXT", "gaming laptop OR android tablet"
679679
)
680680
assert result == "@title:((gaming laptop)|(android tablet))"
681+
682+
def test_or_trailing_empty_operand_raises(self):
683+
"""Trailing OR with empty operand raises ValueError."""
684+
builder = QueryBuilder()
685+
with pytest.raises(ValueError, match="Empty operand in OR expression"):
686+
builder.build_text_condition("title", "FULLTEXT", "laptop OR ")
687+
688+
def test_or_leading_empty_operand_raises(self):
689+
"""Leading OR with empty operand raises ValueError."""
690+
builder = QueryBuilder()
691+
with pytest.raises(ValueError, match="Empty operand in OR expression"):
692+
builder.build_text_condition("title", "FULLTEXT", " OR tablet")

0 commit comments

Comments
 (0)