@@ -138,6 +138,33 @@ def test_comparison_condition_unknown_operator():
138138 _parse_comparison_condition (condition )
139139
140140
141+ @pytest .mark .parametrize ("operator" , ["like" , "not like" ])
142+ def test_comparison_condition_like_operator_requires_str_value (operator ):
143+ # AlloyDB-specific: LIKE / NOT LIKE require a string operand.
144+ condition = {"field" : "meta.name" , "operator" : operator , "value" : 123 }
145+ with pytest .raises (FilterError , match = "must be a str when using" ):
146+ _parse_comparison_condition (condition )
147+
148+
149+ def test_logical_condition_not_in_is_parenthesized_when_and_combined ():
150+ # `not in` renders an internal `OR` (`IS NULL OR != ALL`). Without surrounding
151+ # parentheses that `OR` escapes an enclosing `AND` (AND binds tighter than OR in
152+ # SQL), so an AND-combined `not in` would wrongly match documents.
153+ condition = {
154+ "operator" : "AND" ,
155+ "conditions" : [
156+ {"field" : "meta.number" , "operator" : "==" , "value" : 5 },
157+ {"field" : "meta.author" , "operator" : "not in" , "value" : ["John" , "Jane" ]},
158+ ],
159+ }
160+ query , values = _parse_logical_condition (condition )
161+ assert isinstance (query , Composed )
162+
163+ expected_sql = "((meta->>'number')::integer = %s AND (meta->>'author' IS NULL OR meta->>'author' != ALL(%s)))"
164+ assert _render (query ) == expected_sql
165+ assert values == [5 , [["John" , "Jane" ]]]
166+
167+
141168def test_logical_condition_missing_operator ():
142169 condition = {"conditions" : []}
143170 with pytest .raises (FilterError ):
0 commit comments