Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Use server side != for queries. #950

Merged
merged 2 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Next Next commit
feat: Use server side != for queries.
  • Loading branch information
sorced-jim committed Feb 23, 2024
commit 3cc6d4392b2cb6d7f4608974633b0521edc960b5
2 changes: 2 additions & 0 deletions google/cloud/ndb/_datastore_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
"<=": query_pb2.PropertyFilter.Operator.LESS_THAN_OR_EQUAL,
">": query_pb2.PropertyFilter.Operator.GREATER_THAN,
">=": query_pb2.PropertyFilter.Operator.GREATER_THAN_OR_EQUAL,
"!=": query_pb2.PropertyFilter.Operator.NOT_EQUAL,
"IN": query_pb2.PropertyFilter.Operator.IN,
}

_KEY_NOT_IN_CACHE = object()
Expand Down
13 changes: 3 additions & 10 deletions google/cloud/ndb/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,8 +604,6 @@ class FilterNode(Node):
The constructor for this type may not always return a
:class:`FilterNode`. For example:

* The filter ``name != value`` is converted into
``(name > value) OR (name < value)`` (a :class:`DisjunctionNode`)
* The filter ``name in (value1, ..., valueN)`` is converted into
``(name = value1) OR ... OR (name = valueN)`` (also a
:class:`DisjunctionNode`)
Expand Down Expand Up @@ -639,11 +637,6 @@ def __new__(cls, name, opsymbol, value):
if isinstance(value, model.Key):
value = value._key

if opsymbol == _NE_OP:
node1 = FilterNode(name, _LT_OP, value)
node2 = FilterNode(name, _GT_OP, value)
return DisjunctionNode(node1, node2)

if opsymbol == _IN_OP:
if not isinstance(value, (list, tuple, set, frozenset)):
raise TypeError(
Expand Down Expand Up @@ -704,17 +697,17 @@ def _to_filter(self, post=False):
representation of the filter.

Raises:
NotImplementedError: If the ``opsymbol`` is ``!=`` or ``in``, since
NotImplementedError: If the ``opsymbol`` is ``in``, since
they should correspond to a composite filter. This should
never occur since the constructor will create ``OR`` nodes for
``!=`` and ``in``
``in``
"""
# Avoid circular import in Python 2.7
from google.cloud.ndb import _datastore_query

if post:
return None
if self._opsymbol in (_NE_OP, _IN_OP):
if self._opsymbol in (_IN_OP):
raise NotImplementedError(
"Inequality filters are not single filter "
"expressions and therefore cannot be converted "
Expand Down
13 changes: 5 additions & 8 deletions tests/unit/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,15 +479,12 @@ def test___eq__():
def test___ne__():
prop = model.Property("name", indexed=True)
value = 7.0
expected = query_module.DisjunctionNode(
query_module.FilterNode("name", "<", value),
query_module.FilterNode("name", ">", value),
)
expected = query_module.FilterNode("name", "!=", value)

or_node_left = prop != value
assert or_node_left == expected
or_node_right = value != prop
assert or_node_right == expected
ne_node_left = prop != value
assert ne_node_left == expected
ne_node_right = value != prop
assert ne_node_right == expected

@staticmethod
def test___lt__():
Expand Down
14 changes: 11 additions & 3 deletions tests/unit/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -656,11 +656,14 @@ def test_constructor_in_invalid_container():

@staticmethod
def test_constructor_ne():
or_node = query_module.FilterNode("a", "!=", 2.5)
ne_node = query_module.FilterNode("a", "!=", 2.5)

filter_node1 = query_module.FilterNode("a", "<", 2.5)
filter_node2 = query_module.FilterNode("a", ">", 2.5)
assert or_node == query_module.DisjunctionNode(filter_node1, filter_node2)
assert ne_node != query_module.DisjunctionNode(filter_node1, filter_node2)
assert ne_node._value == 2.5
assert ne_node._opsymbol == "!="
assert ne_node._name == "a"

@staticmethod
def test_pickling():
Expand Down Expand Up @@ -693,10 +696,15 @@ def test__to_filter_post():
filter_node = query_module.FilterNode("speed", ">=", 88)
assert filter_node._to_filter(post=True) is None

@staticmethod
def test__to_ne_filter_op():
filter_node = query_module.FilterNode("speed", "!=", 88)
assert filter_node._to_filter(post=True) is None

@staticmethod
def test__to_filter_bad_op():
filter_node = query_module.FilterNode("speed", ">=", 88)
filter_node._opsymbol = "!="
filter_node._opsymbol = "in"
with pytest.raises(NotImplementedError):
filter_node._to_filter()

Expand Down