Skip to content

Fix the - and ~ search prefixes to cover every child - #371

Closed
simpleqt wants to merge 1 commit into
valkey-io:mainfrom
simpleqt:fix/search-disjunct-parens
Closed

simpleqt wants to merge 1 commit into
valkey-io:mainfrom
simpleqt:fix/search-disjunct-parens

Conversation

@simpleqt

Copy link
Copy Markdown
Contributor

Fix the - and ~ search prefixes to cover every child

disjunct() and optional() build a prefix node, but asked the parent for its
string without parens:

class DisjunctNode(IntersectNode):
    def to_string(self, with_parens=None):
        with_parens = self._should_use_paren(with_parens)
        ret = super().to_string(with_parens=False)   # <- drops the parens
        if with_parens:
            return "(-" + ret + ")"
        else:
            return "-" + ret

so the prefix landed on the first child only:

before                                            after
disjunct("foo", "bar")      '(-foo bar)'          '-(foo bar)'
OptionalNode("foo", "bar")  '(~foo bar)'          '~(foo bar)'
disjunct(union(...), "baz") '(-(foo|bar) baz)'    '-((foo|bar) baz)'
disjunct("foo")             '-foo'                '-foo'          (unchanged)

RediSearch binds the - of (-foo bar) to foo alone, so that query reads as
"require bar, exclude foo" — not the "all of its children must evaluate to
false"
this node documents. The same applies to ~, where it boosts only the
first child instead of the group. The result is a silently different query with
no error.

The parent already owns this decision — Node._should_use_paren returns
len(self.params) > 1 when the caller has no preference — so the fix is to stop
overriding it and let the parent add the parens, passing the caller's choice
through:

return "-" + super().to_string(with_parens)

Testing

tests/test_search_querystring_nodes.py (new, serverless — the search test
modules carry a module-level skip because they need a running service):

  • on the untouched base: 7 failed, 2 passed — assert '(-foo bar)' == '-(foo bar)'
    for both prefixes, plus the nested-union, disjunct_union and composition cases
  • with the fix: 9 passed, and together with the other serverless suites
    (tests/test_helpers.py, tests/test_parsers) → 19 passed
  • the two that pass on both are the single-child cases, which were already right

The tests also pin the explicit cases (to_string(True) / to_string(False)),
which is what stops this from regressing into the opposite mistake of never
grouping.

ruff at the version the repo pins (0.17.0: check and format --check) is
clean on both files.

No existing test asserted the old strings (checked with git grep), and the only
in-repo consumer of these nodes is the query builder, so nothing else changes.

Note: my open PR #370 also edits valkey/commands/search/querystring.py, at
the unrelated geo() helper. The two changes touch different functions, so
whichever lands second should merge cleanly, but say the word if you would rather
have them as one change and I will combine them.

AI assistance: this change was produced with AI assistance (an autonomous coding
agent); the analysis, the reproduction, the before/after test runs and the lint
runs were executed locally, and a human reviewed the result before submission.

disjunct() and optional() asked the parent node for its string without
parens, so the prefix landed on the first child only: disjunct('foo', 'bar')
rendered '(-foo bar)' and RediSearch read that as 'require bar, exclude
foo' instead of 'exclude both'. The parent already adds parens for a node
with more than one child, so let it.

Signed-off-by: simpleqt <2015900818@qq.com>
Copilot AI balanced review requested due to automatic review settings October 21, 2026 16:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@simpleqt

simpleqt commented Nov 9, 2026

Copy link
Copy Markdown
Contributor Author

Closing this to get my open pull requests in this repository down to a sane number: I had 8 open here at once, which is more than anyone should be asked to review. The branch stays in my fork if it is ever useful - happy to reopen on request.

@simpleqt simpleqt closed this Nov 9, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in valkey-py planning Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants