Skip to content
This repository was archived by the owner on Oct 9, 2023. It is now read-only.

Commit cae2f63

Browse files
Introduce further Query Options (#112)
## What is the goal of this PR? We introduce modified `infer`, and new `batch_size`, and `explain` options for queries: ``` Transaction.query("...", infer=Transaction.Options.SERVER_DEFAULT, explain=Transaction.Options.SERVER_DEFAULT, batch_size=Transaction.Options.SERVER_DEFAULT) ``` The default `SERVER_DEFAULT` value means that the server will automatically choose the default value for `infer`, `explain`, and `batch_size`. For reference, the server will default to `infer = True`, `explain = True`, and `batch_size = 50`. ** use `explain=true` if you want to retrieve explanations from your query ** This was introduced to ensure correct Explanations, without blowing up Transaction memory when not required. ## What are the changes implemented in this PR? * Use default value on `transaction.query` such that: - `infer=Transaction.Options.SERVER_DEFAULT` - `explain=Transaction.Options.SERVER_DEFAULT` - `batch_size=Transaction.Options.SERVER_DEFAULT` * Throw exceptions if the types of parameters are passed in are incorrect. * Tests for `explain` and `infer` flag
1 parent 9af4f2b commit cae2f63

File tree

6 files changed

+177
-20
lines changed

6 files changed

+177
-20
lines changed

dependencies/graknlabs/dependencies.bzl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ def graknlabs_grakn_core():
2828
git_repository(
2929
name = "graknlabs_grakn_core",
3030
remote = "https://github.com/graknlabs/grakn",
31-
commit = "05bcb88d38d1c63f1598699ac4d7e58449fd6726" # sync-marker: do not remove this comment, this is used for sync-dependencies by @graknlabs_grakn_core
31+
commit = "c3caeb7b626b41f0a0a8784af11840d682d9b17c" # sync-marker: do not remove this comment, this is used for sync-dependencies by @graknlabs_grakn_core
3232
)
3333

3434
def graknlabs_protocol():
3535
git_repository(
3636
name = "graknlabs_protocol",
3737
remote = "https://github.com/graknlabs/protocol",
38-
commit = "4ad2dfeaeca85f4ba2f2b42c263d3adf3e36acc3" # sync-marker: do not remove this comment, this is used for sync-dependencies by @graknlabs_protocol
38+
tag = "1.0.6" # sync-marker: do not remove this comment, this is used for sync-dependencies by @graknlabs_protocol
3939
)

grakn/client.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
from grakn.service.Session.util.enums import ValueType # user-facing ValueType enum
2323

24-
from grakn.service.Session.util.RequestBuilder import RequestBuilder
24+
from grakn.service.Session.util.RequestBuilder import RequestBuilder, QueryOptions
2525
from grakn.service.Session.util.enums import TxType as _TxType
2626
from grakn.service.Keyspace.KeyspaceService import KeyspaceService
2727
from grakn.service.Session.TransactionService import TransactionService
@@ -132,6 +132,8 @@ def write(self):
132132
class Transaction(object):
133133
""" Presents the Grakn interface to the user, actual work with GRPC happens in TransactionService """
134134

135+
Options = QueryOptions
136+
135137
def __init__(self, transaction_service):
136138
self._tx_service = transaction_service
137139
__init__.__annotations__ = {'transaction_service': TransactionService}
@@ -148,9 +150,9 @@ def __exit__(self, type, value, tb):
148150
#print("Closing Transaction due to exception: {0} \n traceback: \n {1}".format(type, tb))
149151
return False
150152

151-
def query(self, query, infer=True):
152-
""" Execute a Graql query, inference is optionally enabled """
153-
return self._tx_service.query(query, infer)
153+
def query(self, query, infer=Options.SERVER_DEFAULT, explain=Options.SERVER_DEFAULT, batch_size=Options.SERVER_DEFAULT):
154+
""" Execute a Graql query with query options"""
155+
return self._tx_service.query(query, infer, explain, batch_size)
154156
query.__annotations__ = {'query': str}
155157

156158
def commit(self):

grakn/service/Session/TransactionService.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ def __init__(self, session_id, tx_type, transaction_endpoint):
4343
# --- Passthrough targets ---
4444
# targets of top level Transaction class
4545

46-
def query(self, query, infer=True, batch_size=50):
46+
def query(self, query, infer, explain, batch_size):
4747
return Iterator(self._communicator,
48-
RequestBuilder.start_iterating_query(query, infer, batch_size),
48+
RequestBuilder.start_iterating_query(query, infer, explain, batch_size),
4949
ResponseReader.ResponseReader.get_query_results(self))
5050
query.__annotations__ = {'query': str}
5151

grakn/service/Session/util/RequestBuilder.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,24 @@
2323
import grakn_protocol.session.Answer_pb2 as answer_messages
2424
from grakn.service.Session.util import enums
2525
from grakn.service.Session.Concept import BaseTypeMapping
26+
from grakn.exception import GraknError
2627

28+
class QueryOptions(object):
29+
SERVER_DEFAULT = None
30+
BATCH_ALL = "all"
2731

2832
class RequestBuilder(object):
2933
""" Static methods for generating GRPC requests """
3034

3135
@staticmethod
32-
def _base_iterate_with_options(batch_size=None):
36+
def _base_iterate_with_options(batch_size):
3337
iter_options = transaction_messages.Transaction.Iter.Req.Options()
34-
if batch_size is None:
38+
if batch_size == QueryOptions.BATCH_ALL:
3539
iter_options.all = True
36-
else:
40+
elif type(batch_size) == int and batch_size > 0:
3741
iter_options.number = batch_size
42+
elif batch_size != QueryOptions.SERVER_DEFAULT:
43+
raise GraknError("batch_size parameter must either be an integer, SERVER_DEFAULT, or BATCH_ALL")
3844

3945
transaction_iter_req = transaction_messages.Transaction.Iter.Req()
4046
transaction_iter_req.options.CopyFrom(iter_options)
@@ -47,12 +53,26 @@ def iter_req_to_tx_req(grpc_iter_req):
4753
return transaction_req
4854

4955
@staticmethod
50-
def start_iterating_query(query, infer=True, batch_size=None):
56+
def _query_options(infer, explain):
57+
options_message = transaction_messages.Transaction.Query.Options()
58+
if infer != QueryOptions.SERVER_DEFAULT:
59+
if type(infer) == bool:
60+
options_message.inferFlag = infer
61+
else:
62+
raise GraknError("query 'infer' flag must be SERVER_DEFAULT or a boolean")
63+
if explain != QueryOptions.SERVER_DEFAULT:
64+
if type(explain) == bool:
65+
options_message.explainFlag = explain
66+
else:
67+
raise GraknError("query 'explain' flag must be SERVER_DEFAULT or a boolean")
68+
return options_message
69+
70+
@staticmethod
71+
def start_iterating_query(query, infer, explain, batch_size):
5172
query_message = transaction_messages.Transaction.Query.Iter.Req()
5273
query_message.query = query
53-
query_message.infer = transaction_messages.Transaction.Query.INFER.Value("TRUE") if infer else \
54-
transaction_messages.Transaction.Query.INFER.Value("FALSE")
55-
74+
query_options = RequestBuilder._query_options(infer, explain)
75+
query_message.options.CopyFrom(query_options)
5676
transaction_iter_req = RequestBuilder._base_iterate_with_options(batch_size)
5777
transaction_iter_req.query_iter_req.CopyFrom(query_message)
5878
return transaction_iter_req

tests/integration/test_answer.py

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,10 @@ def test_conceptmap_explanation(self):
209209
self.assertEqual(has_explanation, 6)
210210
client.keyspaces().delete("transitivity")
211211

212-
def test_get_explanation(self):
213-
with client.session("explanation") as local_session:
212+
213+
def test_get_explanation_has_rule(self):
214+
""" Test that explanations have rules attached """
215+
with client.session("explanation_has_rule") as local_session:
214216
tx = local_session.transaction().write()
215217
tx.query("define family-name sub attribute, value string;"
216218
"parenthood sub relation, relates parent, relates child;"
@@ -225,14 +227,69 @@ def test_get_explanation(self):
225227
tx.commit()
226228
tx = local_session.transaction().read()
227229

228-
answers = tx.query("match $x isa person, has family-name $f; get;")
230+
answers = tx.query("match $x isa person, has family-name $f; get;", explain=True)
229231
for x in answers:
230232
if x.has_explanation():
231233
explanation = x.explanation()
232234
self.assertIsNotNone(explanation.get_rule())
233235

234-
client.keyspaces().delete("explanation")
236+
client.keyspaces().delete("explanation_has_rule")
237+
238+
239+
def test_query_with_explain_false_no_explanations_available(self):
240+
with client.session("query_explain_false") as local_session:
241+
tx = local_session.transaction().write()
242+
tx.query("define family-name sub attribute, value string;"
243+
"parenthood sub relation, relates parent, relates child;"
244+
"person sub entity, has family-name, plays parent, plays child;"
245+
"family-name-inheritence sub rule,"
246+
"when { (parent: $p, child: $c) isa parenthood; $p has family-name $f; },"
247+
"then { $c has family-name $f; };")
248+
tx.query("insert $bob isa person, has family-name \"bobson\";"
249+
"$bobjr isa person;"
250+
"(parent: $bob, child: $bobjr) isa parenthood;")
251+
252+
tx.commit()
253+
tx = local_session.transaction().read()
254+
255+
answers = tx.query("match $x isa person, has family-name $f; get;", explain=False)
256+
for x in answers:
257+
self.assertFalse(x.has_explanation())
258+
259+
client.keyspaces().delete("query_explain_false")
260+
261+
262+
def test_explain_true_explanation_sub_explanation_exists(self):
263+
with client.session("query_explain_sub_explanation") as local_session:
264+
tx = local_session.transaction().write()
265+
tx.query("define family-name sub attribute, value string;"
266+
"parenthood sub relation, relates parent, relates child;"
267+
"fatherhood sub parenthood, relates father as parent, relates father-child as child;"
268+
"person sub entity, has family-name, plays parent, plays child, plays father, plays father-child, has gender;"
269+
"gender sub attribute, value string; "
270+
"family-name-inheritence sub rule,"
271+
"when { (parent: $p, child: $c) isa parenthood; $p has family-name $f; },"
272+
"then { $c has family-name $f; }; "
273+
"fatherhood-rule sub rule, "
274+
"when { $p isa person, has gender \"male\", has family-name $f; $c isa person, has family-name $f; }, "
275+
"then { (father: $p, father-child: $c) isa fatherhood; };")
276+
tx.query("insert $bob isa person, has family-name \"bobson\", has gender \"male\";"
277+
"$bobjr isa person;"
278+
"(parent: $bob, child: $bobjr) isa parenthood;")
279+
280+
tx.commit()
281+
tx = local_session.transaction().read()
282+
283+
answers = tx.query("match $m isa fatherhood; get;", explain=True)
284+
for x in answers:
285+
if x.has_explanation():
286+
explanation = x.explanation()
287+
self.assertIsNotNone(explanation.get_rule())
288+
sub_answers = explanation.get_answers()
289+
self.assertTrue(sub_answers[0].has_explanation())
290+
self.assertIsNotNone(sub_answers[0].explanation())
235291

292+
client.keyspaces().delete("query_explain_sub_explanation")
236293

237294
if __name__ == "__main__":
238295
with GraknServer():

tests/integration/test_grakn.py

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import unittest
2121
import uuid
2222
import grakn
23-
from grakn.client import GraknClient, ValueType
23+
from grakn.client import GraknClient, ValueType, Transaction
2424
from grakn.exception.GraknError import GraknError
2525
from grakn.service.Session.util.ResponseReader import Value
2626

@@ -204,6 +204,84 @@ def test_query_invalid_syntax(self):
204204
self.assertFalse(self.tx.is_open(), msg="Tx is not closed after invalid syntax")
205205

206206

207+
def test_query_infer_false(self):
208+
""" Test that when the infer flag is false, no inferred answers are returned """
209+
local_session = client.session("query_flag_infer_false")
210+
local_tx = local_session.transaction().write()
211+
local_tx.query("define person sub entity, has name; name sub attribute, value string; naming sub rule, when {$x isa person; $a isa name;}, then {$x has name $a;};").get()
212+
local_tx.query("insert $x isa person; $a \"John\" isa name;").get()
213+
local_tx.commit()
214+
local_tx = local_session.transaction().read()
215+
answers = list(local_tx.query("match $x isa person, has name $a; get;", infer=False).get())
216+
self.assertEquals(len(answers), 0)
217+
218+
219+
def test_query_infer_true(self):
220+
""" Test that when the infer flag is true, inferred answers are returned """
221+
local_session = client.session("query_flag_infer_true")
222+
local_tx = local_session.transaction().write()
223+
local_tx.query("define person sub entity, has name; name sub attribute, value string; naming sub rule, when {$x isa person; $a isa name;}, then {$x has name $a;};").get()
224+
local_tx.query("insert $x isa person; $a \"John\" isa name;").get()
225+
local_tx.commit()
226+
local_tx = local_session.transaction().read()
227+
answers = list(local_tx.query("match $x isa person, has name $a; get;", infer=True).get())
228+
self.assertEquals(len(answers), 1)
229+
230+
231+
def test_query_batch_all(self):
232+
""" Test that batch_size=ALL succeeds at retrieving all the answers """
233+
local_session = client.session("query_batch_size_all")
234+
local_tx = local_session.transaction().write()
235+
local_tx.query("define person sub entity, has name; name sub attribute, value string;")
236+
for i in range(100):
237+
local_tx.query("insert $x isa person, has name \"John-{0}\"; ".format(i))
238+
local_tx.commit()
239+
local_tx = local_session.transaction().read()
240+
answers = list(local_tx.query("match $x isa person, has name $a; get;", batch_size=Transaction.Options.BATCH_ALL).get())
241+
self.assertEquals(len(answers), 100)
242+
243+
244+
def test_query_batch_one(self):
245+
""" Test that batch_size=1 succeeds at retrieving all the answers """
246+
local_session = client.session("query_batch_size_one")
247+
local_tx = local_session.transaction().write()
248+
local_tx.query("define person sub entity, has name; name sub attribute, value string;")
249+
for i in range(100):
250+
local_tx.query("insert $x isa person, has name \"John-{0}\"; ".format(i))
251+
local_tx.commit()
252+
local_tx = local_session.transaction().read()
253+
answers = list(local_tx.query("match $x isa person, has name $a; get;", batch_size=1).get())
254+
self.assertEquals(len(answers), 100)
255+
256+
257+
def test_query_batch_nonpositive_throws(self):
258+
""" Test that batch_size="Nonsense" succeeds at retrieving all the answers """
259+
local_session = client.session("query_batch_size_nonpositive")
260+
local_tx = local_session.transaction().write()
261+
local_tx.query("define person sub entity, has name; name sub attribute, value string;")
262+
for i in range(100):
263+
local_tx.query("insert $x isa person, has name \"John-{0}\"; ".format(i))
264+
local_tx.commit()
265+
local_tx = local_session.transaction().read()
266+
with self.assertRaises(Exception):
267+
answers = list(local_tx.query("match $x isa person, has name $a; get;", batch_size=0).get())
268+
with self.assertRaises(Exception):
269+
answers = list(local_tx.query("match $x isa person, has name $a; get;", batch_size=-50).get())
270+
271+
272+
def test_query_batch_nonsense_throws(self):
273+
""" Test that batch_size="Nonsense" succeeds at retrieving all the answers """
274+
local_session = client.session("query_batch_size_nonsense")
275+
local_tx = local_session.transaction().write()
276+
local_tx.query("define person sub entity, has name; name sub attribute, value string;")
277+
for i in range(100):
278+
local_tx.query("insert $x isa person, has name \"John-{0}\"; ".format(i))
279+
local_tx.commit()
280+
local_tx = local_session.transaction().read()
281+
with self.assertRaises(Exception):
282+
answers = list(local_tx.query("match $x isa person, has name $a; get;", batch_size="nonsense").get())
283+
284+
207285
def test_query_tx_already_closed(self):
208286
self.tx.close()
209287
with self.assertRaises(GraknError):

0 commit comments

Comments
 (0)