Skip to content

Commit 2433c47

Browse files
Chris Rossiandrewsg
authored andcommitted
Finish implementation of query.ParameterizedFunction. (#266)
It turns out that `query.ParameterizedFunction` wasn't finished in its implementation. It is meant to deal with the case, in GQL, where GQL functions are called. This PR also adds an implementation for the `LIST` GQL function and stubs for future impelemntations of `USER` and `KEY`. Remaining GQL functions needing to be implemented are unknown at this time. Fixes #258.
1 parent b1c8096 commit 2433c47

File tree

5 files changed

+142
-19
lines changed

5 files changed

+142
-19
lines changed

packages/google-cloud-ndb/google/cloud/ndb/_gql.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,9 @@ def _args_to_val(self, func, args):
667667
if func == "nop":
668668
return vals[0] # May be a Parameter
669669
pfunc = query_module.ParameterizedFunction(func, vals)
670-
return pfunc
670+
if pfunc.is_parameterized():
671+
return pfunc
672+
return pfunc.resolve({}, {})
671673

672674
def query_filters(self, model_class, filters):
673675
"""Get the filters in a format compatible with the Query constructor"""
@@ -681,6 +683,8 @@ def query_filters(self, model_class, filters):
681683
val = self._args_to_val(func, args)
682684
if isinstance(val, query_module.ParameterizedThing):
683685
node = query_module.ParameterNode(prop, op, val)
686+
elif op == "in":
687+
node = prop._IN(val)
684688
else:
685689
node = prop._comparison(op, val)
686690
filters.append(node)
@@ -762,3 +766,19 @@ def __eq__(self, other):
762766

763767
def __repr__(self):
764768
return "Literal(%s)" % repr(self._value)
769+
770+
771+
def _raise_not_implemented(func):
772+
def raise_inner(value):
773+
raise NotImplementedError(
774+
"GQL function {} is not implemented".format(func)
775+
)
776+
777+
return raise_inner
778+
779+
780+
FUNCTIONS = {
781+
"list": list,
782+
"user": _raise_not_implemented("user"),
783+
"key": _raise_not_implemented("key"),
784+
}

packages/google-cloud-ndb/google/cloud/ndb/query.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -362,24 +362,38 @@ class ParameterizedFunction(ParameterizedThing):
362362
"""
363363

364364
def __init__(self, func, values):
365-
self.__func = func
366-
self.__values = values
365+
self.func = func
366+
self.values = values
367+
368+
from google.cloud.ndb import _gql # avoid circular import
369+
370+
_func = _gql.FUNCTIONS.get(func)
371+
if _func is None:
372+
raise ValueError("Unknown GQL function: {}".format(func))
373+
self._func = _func
367374

368375
def __repr__(self):
369-
return "ParameterizedFunction(%r, %r)" % (self.__func, self.__values)
376+
return "ParameterizedFunction(%r, %r)" % (self.func, self.values)
370377

371378
def __eq__(self, other):
372379
if not isinstance(other, ParameterizedFunction):
373380
return NotImplemented
374-
return self.__func == other.__func and self.__values == other.__values
381+
return self.func == other.func and self.values == other.values
375382

376-
@property
377-
def func(self):
378-
return self.__func
383+
def is_parameterized(self):
384+
for value in self.values:
385+
if isinstance(value, Parameter):
386+
return True
387+
return False
379388

380-
@property
381-
def values(self):
382-
return self.__values
389+
def resolve(self, bindings, used):
390+
values = []
391+
for value in self.values:
392+
if isinstance(value, Parameter):
393+
value = value.resolve(bindings, used)
394+
values.append(value)
395+
396+
return self._func(values)
383397

384398

385399
class Node(object):

packages/google-cloud-ndb/tests/system/test_query.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,3 +1421,27 @@ class SomeKind(ndb.Model):
14211421
query = SomeKind.gql("WHERE foo = :1", 2)
14221422
results = query.fetch()
14231423
assert results[0].foo == 2
1424+
1425+
1426+
@pytest.mark.usefixtures("client_context")
1427+
def test_IN(ds_entity):
1428+
for i in range(5):
1429+
entity_id = test_utils.system.unique_resource_id()
1430+
ds_entity(KIND, entity_id, foo=i)
1431+
1432+
class SomeKind(ndb.Model):
1433+
foo = ndb.IntegerProperty()
1434+
1435+
eventually(SomeKind.query().fetch, _length_equals(5))
1436+
1437+
query = SomeKind.gql("where foo in (2, 3)").order(SomeKind.foo)
1438+
results = query.fetch()
1439+
assert len(results) == 2
1440+
assert results[0].foo == 2
1441+
assert results[1].foo == 3
1442+
1443+
query = SomeKind.gql("where foo in :1", [2, 3]).order(SomeKind.foo)
1444+
results = query.fetch()
1445+
assert len(results) == 2
1446+
assert results[0].foo == 2
1447+
assert results[1].foo == 3

packages/google-cloud-ndb/tests/unit/test__gql.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from google.cloud.ndb import exceptions
1919
from google.cloud.ndb import model
2020
from google.cloud.ndb import _gql as gql_module
21+
from google.cloud.ndb import query as query_module
2122

2223

2324
GQL_QUERY = """
@@ -329,12 +330,28 @@ class SomeKind(model.Model):
329330
@pytest.mark.usefixtures("in_context")
330331
def test_get_query_in():
331332
class SomeKind(model.Model):
332-
prop1 = model.StringProperty()
333+
prop1 = model.IntegerProperty()
333334

334335
gql = gql_module.GQL(
335336
"SELECT prop1 FROM SomeKind WHERE prop1 IN (1, 2, 3)"
336337
)
337338
query = gql.get_query()
339+
assert query.filters == query_module.OR(
340+
query_module.FilterNode("prop1", "=", 1),
341+
query_module.FilterNode("prop1", "=", 2),
342+
query_module.FilterNode("prop1", "=", 3),
343+
)
344+
345+
@staticmethod
346+
@pytest.mark.usefixtures("in_context")
347+
def test_get_query_in_parameterized():
348+
class SomeKind(model.Model):
349+
prop1 = model.StringProperty()
350+
351+
gql = gql_module.GQL(
352+
"SELECT prop1 FROM SomeKind WHERE prop1 IN (:1, :2, :3)"
353+
)
354+
query = gql.get_query()
338355
assert "'in'," in str(query.filters)
339356

340357
@staticmethod
@@ -346,3 +363,19 @@ class SomeKind(model.Model):
346363
gql = gql_module.GQL("SELECT __key__ FROM SomeKind WHERE prop1='a'")
347364
query = gql.get_query()
348365
assert query.default_options.keys_only is True
366+
367+
368+
class TestFUNCTIONS:
369+
@staticmethod
370+
def test_list():
371+
assert gql_module.FUNCTIONS["list"]((1, 2)) == [1, 2]
372+
373+
@staticmethod
374+
def test_user():
375+
with pytest.raises(NotImplementedError):
376+
gql_module.FUNCTIONS["user"]("any arg")
377+
378+
@staticmethod
379+
def test_key():
380+
with pytest.raises(NotImplementedError):
381+
gql_module.FUNCTIONS["key"]("any arg")

packages/google-cloud-ndb/tests/unit/test_query.py

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -299,29 +299,34 @@ class TestParameterizedFunction:
299299
@staticmethod
300300
def test_constructor():
301301
query = query_module.ParameterizedFunction(
302-
"user", query_module.Parameter(1)
302+
"user", [query_module.Parameter(1)]
303303
)
304304
assert query.func == "user"
305-
assert query.values == query_module.Parameter(1)
305+
assert query.values == [query_module.Parameter(1)]
306+
307+
@staticmethod
308+
def test_constructor_bad_function():
309+
with pytest.raises(ValueError):
310+
query_module.ParameterizedFunction("notafunc", ())
306311

307312
@staticmethod
308313
def test___repr__():
309314
query = query_module.ParameterizedFunction(
310-
"user", query_module.Parameter(1)
315+
"user", [query_module.Parameter(1)]
311316
)
312317
assert (
313-
query.__repr__() == "ParameterizedFunction('user', Parameter(1))"
318+
query.__repr__() == "ParameterizedFunction('user', [Parameter(1)])"
314319
)
315320

316321
@staticmethod
317322
def test___eq__parameter():
318323
query = query_module.ParameterizedFunction(
319-
"user", query_module.Parameter(1)
324+
"user", [query_module.Parameter(1)]
320325
)
321326
assert (
322327
query.__eq__(
323328
query_module.ParameterizedFunction(
324-
"user", query_module.Parameter(1)
329+
"user", [query_module.Parameter(1)]
325330
)
326331
)
327332
is True
@@ -330,10 +335,37 @@ def test___eq__parameter():
330335
@staticmethod
331336
def test___eq__no_parameter():
332337
query = query_module.ParameterizedFunction(
333-
"user", query_module.Parameter(1)
338+
"user", [query_module.Parameter(1)]
334339
)
335340
assert query.__eq__(42) is NotImplemented
336341

342+
@staticmethod
343+
def test_is_parameterized_True():
344+
query = query_module.ParameterizedFunction(
345+
"user", [query_module.Parameter(1)]
346+
)
347+
assert query.is_parameterized()
348+
349+
@staticmethod
350+
def test_is_parameterized_False():
351+
query = query_module.ParameterizedFunction("user", [1])
352+
assert not query.is_parameterized()
353+
354+
@staticmethod
355+
def test_is_parameterized_no_arguments():
356+
query = query_module.ParameterizedFunction("user", ())
357+
assert not query.is_parameterized()
358+
359+
@staticmethod
360+
def test_resolve():
361+
query = query_module.ParameterizedFunction(
362+
"list", [1, query_module.Parameter(2), query_module.Parameter(3)]
363+
)
364+
used = {}
365+
resolved = query.resolve({2: 4, 3: 6}, used)
366+
assert resolved == [1, 4, 6]
367+
assert used == {2: True, 3: True}
368+
337369

338370
class TestNode:
339371
@staticmethod

0 commit comments

Comments
 (0)