Skip to content

Commit 6f26e98

Browse files
committed
Merge pull request #294 from dhermes/query-operator-fix-dict-random-order
Makes operator checking deterministic in Query.filter.
2 parents f7d0441 + 1fb43bd commit 6f26e98

2 files changed

Lines changed: 49 additions & 9 deletions

File tree

gcloud/datastore/query.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ class Query(object):
4646
"""
4747

4848
OPERATORS = {
49-
'<': datastore_pb.PropertyFilter.LESS_THAN,
5049
'<=': datastore_pb.PropertyFilter.LESS_THAN_OR_EQUAL,
51-
'>': datastore_pb.PropertyFilter.GREATER_THAN,
5250
'>=': datastore_pb.PropertyFilter.GREATER_THAN_OR_EQUAL,
51+
'<': datastore_pb.PropertyFilter.LESS_THAN,
52+
'>': datastore_pb.PropertyFilter.GREATER_THAN,
5353
'=': datastore_pb.PropertyFilter.EQUAL,
5454
}
5555
"""Mapping of operator strings and their protobuf equivalents."""
@@ -132,12 +132,16 @@ def filter(self, expression, value):
132132
property_name, operator = None, None
133133
expression = expression.strip()
134134

135-
for operator_string in self.OPERATORS:
136-
if expression.endswith(operator_string):
137-
operator = self.OPERATORS[operator_string]
138-
property_name = expression[0:-len(operator_string)].strip()
135+
# Use None to split on *any* whitespace.
136+
expr_pieces = expression.rsplit(None, 1)
137+
if len(expr_pieces) == 2:
138+
property_name, operator = expr_pieces
139+
property_name = property_name.strip()
139140

140-
if not operator or not property_name:
141+
# If no whitespace in `expression`, `operator` will be `None` and
142+
# self.OPERATORS[None] will be `None` as well.
143+
pb_op_enum = self.OPERATORS.get(operator)
144+
if pb_op_enum is None:
141145
raise ValueError('Invalid expression: "%s"' % expression)
142146

143147
# Build a composite filter AND'd together.
@@ -147,7 +151,7 @@ def filter(self, expression, value):
147151
# Add the specific filter
148152
property_filter = composite_filter.filter.add().property_filter
149153
property_filter.property.name = property_name
150-
property_filter.operator = operator
154+
property_filter.operator = pb_op_enum
151155

152156
# Set the value to filter on based on the type.
153157
helpers._set_protobuf_value(property_filter.value, value)

gcloud/datastore/test_query.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,21 +63,57 @@ def test_to_protobuf_w_kind(self):
6363
kq_pb, = list(q_pb.kind)
6464
self.assertEqual(kq_pb.name, _KIND)
6565

66+
def test_filter_w_no_operator(self):
67+
query = self._makeOne()
68+
self.assertRaises(ValueError, query.filter, 'firstname', 'John')
69+
6670
def test_filter_w_unknown_operator(self):
6771
query = self._makeOne()
6872
self.assertRaises(ValueError, query.filter, 'firstname ~~', 'John')
6973

7074
def test_filter_w_known_operator(self):
75+
from gcloud.datastore import datastore_v1_pb2 as datastore_pb
76+
7177
query = self._makeOne()
7278
after = query.filter('firstname =', u'John')
7379
self.assertFalse(after is query)
7480
self.assertTrue(isinstance(after, self._getTargetClass()))
7581
q_pb = after.to_protobuf()
76-
self.assertEqual(q_pb.filter.composite_filter.operator, 1) # AND
82+
self.assertEqual(q_pb.filter.composite_filter.operator,
83+
datastore_pb.CompositeFilter.AND)
7784
f_pb, = list(q_pb.filter.composite_filter.filter)
7885
p_pb = f_pb.property_filter
7986
self.assertEqual(p_pb.property.name, 'firstname')
8087
self.assertEqual(p_pb.value.string_value, u'John')
88+
self.assertEqual(p_pb.operator, datastore_pb.PropertyFilter.EQUAL)
89+
90+
def test_filter_w_all_operators(self):
91+
from gcloud.datastore import datastore_v1_pb2 as datastore_pb
92+
93+
query = self._makeOne()
94+
query = query.filter('leq_prop <=', u'val1')
95+
query = query.filter('geq_prop >=', u'val2')
96+
query = query.filter('lt_prop <', u'val3')
97+
query = query.filter('gt_prop >', u'val4')
98+
query = query.filter('eq_prop =', u'val5')
99+
100+
query_pb = query.to_protobuf()
101+
pb_values = [
102+
('leq_prop', 'val1',
103+
datastore_pb.PropertyFilter.LESS_THAN_OR_EQUAL),
104+
('geq_prop', 'val2',
105+
datastore_pb.PropertyFilter.GREATER_THAN_OR_EQUAL),
106+
('lt_prop', 'val3', datastore_pb.PropertyFilter.LESS_THAN),
107+
('gt_prop', 'val4', datastore_pb.PropertyFilter.GREATER_THAN),
108+
('eq_prop', 'val5', datastore_pb.PropertyFilter.EQUAL),
109+
]
110+
query_filter = query_pb.filter.composite_filter.filter
111+
for filter_pb, pb_value in zip(query_filter, pb_values):
112+
name, val, filter_enum = pb_value
113+
prop_filter = filter_pb.property_filter
114+
self.assertEqual(prop_filter.property.name, name)
115+
self.assertEqual(prop_filter.value.string_value, val)
116+
self.assertEqual(prop_filter.operator, filter_enum)
81117

82118
def test_filter_w_known_operator_and_entity(self):
83119
import operator

0 commit comments

Comments
 (0)