diff --git a/nutkit/protocol/responses.py b/nutkit/protocol/responses.py index f86f46914..74d4ae28d 100644 --- a/nutkit/protocol/responses.py +++ b/nutkit/protocol/responses.py @@ -335,7 +335,11 @@ def __init__(self, text, parameters): class Bookmarks: - """Represents an array of bookmarks.""" + """ + Represents an array of bookmarks. + + `bookmarks` is an array of bookmarks (str). + """ def __init__(self, bookmarks): self.bookmarks = bookmarks diff --git a/tests/stub/bookmarks/scripts/v3/bookmarks.script b/tests/stub/bookmarks/scripts/v3/bookmarks.script new file mode 100644 index 000000000..84e37b81a --- /dev/null +++ b/tests/stub/bookmarks/scripts/v3/bookmarks.script @@ -0,0 +1,27 @@ +!: BOLT 3 + +A: HELLO {"{}": "*"} +*: RESET + +{{ + C: RUN "RETURN 1 AS n" {} {#BM_IN#, "[mode]": "w"} + S: SUCCESS {"fields": ["n"]} + {{ + C: PULL_ALL + ---- + C: DISCARD_ALL + }} + S: SUCCESS {"type": "w", "bookmark": "#BM_OUT#"} +---- + C: RUN "RETURN 1 AS n" {} {#BM_IN#, "mode": "r"} + S: SUCCESS {"fields": ["n"]} + {{ + C: PULL_ALL + ---- + C: DISCARD_ALL + }} + S: SUCCESS {"type": "r", "bookmark": "#BM_OUT#"} +}} + +*: RESET +?: GOODBYE diff --git a/tests/stub/bookmarks/scripts/v3/bookmarks_tx.script b/tests/stub/bookmarks/scripts/v3/bookmarks_tx.script new file mode 100644 index 000000000..2dc9b2a24 --- /dev/null +++ b/tests/stub/bookmarks/scripts/v3/bookmarks_tx.script @@ -0,0 +1,33 @@ +!: BOLT 3 + +A: HELLO {"{}": "*"} +*: RESET + +{{ + C: BEGIN {#BM_IN#, "[mode]": "w"} + S: SUCCESS {} + C: RUN "RETURN 1 AS n" {} {} + S: SUCCESS {"fields": ["n"]} + {{ + C: PULL_ALL + ---- + C: DISCARD_ALL + }} + S: SUCCESS {"type": "w"} +---- + C: BEGIN {#BM_IN#, "mode": "r"} + S: SUCCESS {} + C: RUN "RETURN 1 AS n" {} {} + S: SUCCESS {"fields": ["n"]} + {{ + C: PULL_ALL + ---- + C: DISCARD_ALL + }} + S: SUCCESS {"type": "r"} +}} +C: COMMIT +S: SUCCESS {"bookmark": "#BM_OUT#"} + +*: RESET +?: GOODBYE diff --git a/tests/stub/bookmarks/scripts/v3/send_and_receive_bookmark_read_tx.script b/tests/stub/bookmarks/scripts/v3/send_and_receive_bookmark_read_tx.script deleted file mode 100644 index a5e185ced..000000000 --- a/tests/stub/bookmarks/scripts/v3/send_and_receive_bookmark_read_tx.script +++ /dev/null @@ -1,14 +0,0 @@ -!: BOLT 3 - -A: HELLO {"{}": "*"} -*: RESET -C: BEGIN {"bookmarks": ["neo4j:bookmark:v1:tx42"], "mode": "r"} -S: SUCCESS {} -C: RUN "MATCH (n) RETURN n.name AS name" {} {} - PULL_ALL -S: SUCCESS {"fields": ["name"]} - SUCCESS {} -C: COMMIT -S: SUCCESS {"bookmark": "neo4j:bookmark:v1:tx4242"} -*: RESET -?: GOODBYE diff --git a/tests/stub/bookmarks/scripts/v3/send_and_receive_bookmark_write_tx.script b/tests/stub/bookmarks/scripts/v3/send_and_receive_bookmark_write_tx.script deleted file mode 100644 index edd3599c9..000000000 --- a/tests/stub/bookmarks/scripts/v3/send_and_receive_bookmark_write_tx.script +++ /dev/null @@ -1,14 +0,0 @@ -!: BOLT 3 - -A: HELLO {"{}": "*"} -*: RESET -C: BEGIN {"bookmarks{}": #BOOKMARKS# } -S: SUCCESS {} -C: RUN "MATCH (n) RETURN n.name AS name" {} {} - PULL_ALL -S: SUCCESS {"fields": ["name"]} - SUCCESS {} -C: COMMIT -S: SUCCESS {"bookmark": "neo4j:bookmark:v1:tx4242"} -*: RESET -?: GOODBYE diff --git a/tests/stub/bookmarks/scripts/v3/send_bookmark_write_tx.script b/tests/stub/bookmarks/scripts/v3/send_bookmark_write_tx.script deleted file mode 100644 index 9295e8213..000000000 --- a/tests/stub/bookmarks/scripts/v3/send_bookmark_write_tx.script +++ /dev/null @@ -1,14 +0,0 @@ -!: BOLT 3 - -A: HELLO {"{}": "*"} -*: RESET -C: BEGIN {} -S: SUCCESS {} -C: RUN "RETURN 1 as n" {} {} - PULL_ALL -S: SUCCESS {"fields": ["n"]} - SUCCESS {"type": "w"} -C: COMMIT -S: SUCCESS {"bookmark": "bm"} -*: RESET -?: GOODBYE diff --git a/tests/stub/bookmarks/scripts/v4/bookmarks.script b/tests/stub/bookmarks/scripts/v4/bookmarks.script new file mode 100644 index 000000000..a663bd9c0 --- /dev/null +++ b/tests/stub/bookmarks/scripts/v4/bookmarks.script @@ -0,0 +1,27 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET + +{{ + C: RUN "RETURN 1 AS n" {} {#BM_IN#, "[mode]": "w"} + S: SUCCESS {"fields": ["n"]} + {{ + C: PULL {"n": {"Z": "*"}, "[qid]": -1} + ---- + C: DISCARD {"n": {"Z": "*"}, "[qid]": -1} + }} + S: SUCCESS {"type": "w", "bookmark": "#BM_OUT#"} +---- + C: RUN "RETURN 1 AS n" {} {#BM_IN#, "mode": "r"} + S: SUCCESS {"fields": ["n"]} + {{ + C: PULL {"n": {"Z": "*"}, "[qid]": -1} + ---- + C: DISCARD {"n": {"Z": "*"}, "[qid]": -1} + }} + S: SUCCESS {"type": "r", "bookmark": "#BM_OUT#"} +}} + +*: RESET +?: GOODBYE diff --git a/tests/stub/bookmarks/scripts/v4/bookmarks_tx.script b/tests/stub/bookmarks/scripts/v4/bookmarks_tx.script new file mode 100644 index 000000000..dde10dba7 --- /dev/null +++ b/tests/stub/bookmarks/scripts/v4/bookmarks_tx.script @@ -0,0 +1,33 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET + +{{ + C: BEGIN {#BM_IN#, "[mode]": "w"} + S: SUCCESS {} + C: RUN "RETURN 1 AS n" {} {} + S: SUCCESS {"fields": ["n"]} + {{ + C: PULL {"n": {"Z": "*"}, "[qid]": -1} + ---- + C: DISCARD {"n": {"Z": "*"}, "[qid]": -1} + }} + S: SUCCESS {"type": "w"} +---- + C: BEGIN {#BM_IN#, "mode": "r"} + S: SUCCESS {} + C: RUN "RETURN 1 AS n" {} {} + S: SUCCESS {"fields": ["n"]} + {{ + C: PULL {"n": {"Z": "*"}, "[qid]": -1} + ---- + C: DISCARD {"n": {"Z": "*"}, "[qid]": -1} + }} + S: SUCCESS {"type": "r"} +}} +C: COMMIT +S: SUCCESS {"bookmark": "#BM_OUT#"} + +*: RESET +?: GOODBYE diff --git a/tests/stub/bookmarks/scripts/v4/send_and_receive_bookmark_read_tx.script b/tests/stub/bookmarks/scripts/v4/send_and_receive_bookmark_read_tx.script deleted file mode 100644 index d3eb62169..000000000 --- a/tests/stub/bookmarks/scripts/v4/send_and_receive_bookmark_read_tx.script +++ /dev/null @@ -1,14 +0,0 @@ -!: BOLT 4 - -A: HELLO {"{}": "*"} -*: RESET -C: BEGIN {"bookmarks": ["neo4j:bookmark:v1:tx42"], "mode": "r"} -S: SUCCESS {} -C: RUN "MATCH (n) RETURN n.name AS name" {} {} -S: SUCCESS {"fields": ["name"]} -C: PULL {"n": 1000} -S: SUCCESS {} -C: COMMIT -S: SUCCESS {"bookmark": "neo4j:bookmark:v1:tx4242"} -*: RESET -?: GOODBYE diff --git a/tests/stub/bookmarks/scripts/v4/send_and_receive_bookmark_write_tx.script b/tests/stub/bookmarks/scripts/v4/send_and_receive_bookmark_write_tx.script deleted file mode 100644 index 5622d32a2..000000000 --- a/tests/stub/bookmarks/scripts/v4/send_and_receive_bookmark_write_tx.script +++ /dev/null @@ -1,14 +0,0 @@ -!: BOLT 4 - -A: HELLO {"{}": "*"} -*: RESET -C: BEGIN {"bookmarks{}": #BOOKMARKS# } -S: SUCCESS {} -C: RUN "MATCH (n) RETURN n.name AS name" {} {} -S: SUCCESS {"fields": ["name"]} -C: PULL {"n": 1000} -S: SUCCESS {} -C: COMMIT -S: SUCCESS {"bookmark": "neo4j:bookmark:v1:tx4242"} -*: RESET -?: GOODBYE diff --git a/tests/stub/bookmarks/scripts/v4/send_bookmark_write_tx.script b/tests/stub/bookmarks/scripts/v4/send_bookmark_write_tx.script deleted file mode 100644 index d6057f40b..000000000 --- a/tests/stub/bookmarks/scripts/v4/send_bookmark_write_tx.script +++ /dev/null @@ -1,14 +0,0 @@ -!: BOLT 4 - -A: HELLO {"{}": "*"} -*: RESET -C: BEGIN {} -S: SUCCESS {} -C: RUN "RETURN 1 as n" {} {} -S: SUCCESS {"fields": ["n"]} -C: PULL {"n": 1000} -S: SUCCESS {"type": "w"} -C: COMMIT -S: SUCCESS {"bookmark": "bm"} -*: RESET -?: GOODBYE diff --git a/tests/stub/bookmarks/test_bookmarks_v4.py b/tests/stub/bookmarks/test_bookmarks_v4.py index 738a7ff07..3caf4f448 100644 --- a/tests/stub/bookmarks/test_bookmarks_v4.py +++ b/tests/stub/bookmarks/test_bookmarks_v4.py @@ -1,3 +1,6 @@ +from contextlib import contextmanager +import json + from nutkit import protocol as types from nutkit.frontend import Driver from nutkit.protocol import AuthorizationToken @@ -25,75 +28,153 @@ def tearDown(self): self._server.reset() super().tearDown() - def test_bookmarks_can_be_set(self): - def test(): - bookmarks = ["bm:%i" % (i + 1) for i in range(bm_count)] - session = self._driver.session(mode[0], bookmarks=bookmarks) - self.assertEqual(session.last_bookmarks(), bookmarks) + @contextmanager + def _new_server(self, tx, bms_in, bm_out): + script = "bookmarks_tx.script" if tx else "bookmarks.script" + vars_ = {} + if bms_in: + vars_["#BM_IN#"] = '"bookmarks{}": %s' % json.dumps(bms_in) + else: + vars_["#BM_IN#"] = '"[bookmarks]": []' + assert bm_out + vars_["#BM_OUT#"] = str(bm_out) + self._server.start( + path=self.script_path(self.version_dir, script), + vars_=vars_ + ) + try: + yield self._server + finally: + self._server.reset() + + @contextmanager + def _new_driver(self): + if self._driver: + self._driver.close() + uri = "bolt://%s" % self._server.address + auth = AuthorizationToken("basic", principal="", credentials="") + self._driver = Driver(self._backend, uri, auth) + try: + yield self._driver + finally: + self._driver.close() + self._driver = None + + def test_bookmarks_on_unused_sessions_are_returned(self): + def test(mode_, bm_count_): + bookmarks = ["bm:%i" % (i + 1) for i in range(bm_count_)] + session = self._driver.session(mode_[0], bookmarks=bookmarks) + self.assertEqual(sorted(session.last_bookmarks()), + sorted(bookmarks)) session.close() for mode in ("read", "write"): - # TODO: decide what we expect to happen when multiple bookmarks are - # passed in: return all or only the last one? - for bm_count in (0, 1): + for bm_count in (0, 1, 2): with self.subTest(mode + "_%i_bookmarks" % bm_count): - test() + test(mode, bm_count) + + def test_bookmarks_session_run(self): + def test(mode_, bm_count_, check_bms_pre_query_, consume_): + bookmarks = ["bm:%i" % (i + 1) for i in range(bm_count_)] + with self._new_server(tx=False, bms_in=bookmarks, + bm_out="bm:re") as server: + with self._new_driver() as driver: + session = driver.session(mode_[0], bookmarks=bookmarks) + if check_bms_pre_query_: + self.assertEqual(sorted(session.last_bookmarks()), + sorted(bookmarks)) + res = session.run("RETURN 1 AS n") + if consume_: + res.consume() + self.assertEqual(session.last_bookmarks(), ["bm:re"]) + session.close() + server.done() - # Tests that a committed transaction can return the last bookmark - def test_last_bookmark(self): - self._server.start( - path=self.script_path(self.version_dir, - "send_bookmark_write_tx.script") - ) - session = self._driver.session("w") - tx = session.begin_transaction() - list(tx.run("RETURN 1 as n")) - tx.commit() - bookmarks = session.last_bookmarks() - session.close() - self._driver.close() - self._server.done() - - self.assertEqual(bookmarks, ["bm"]) - - def test_send_and_receive_bookmarks_read_tx(self): - self._server.start( - path=self.script_path(self.version_dir, - "send_and_receive_bookmark_read_tx.script") - ) - session = self._driver.session( - access_mode="r", - bookmarks=["neo4j:bookmark:v1:tx42"] - ) - tx = session.begin_transaction() - result = tx.run("MATCH (n) RETURN n.name AS name") - result.next() - tx.commit() - bookmarks = session.last_bookmarks() - - self.assertEqual(bookmarks, ["neo4j:bookmark:v1:tx4242"]) - self._server.done() + for mode in ("read", "write"): + for bm_count in (0, 1, 2): + for check_bms_pre_query in (False, True): + # TODO: make a decision if consume should be triggered + # implicitly or not. + for consume in (False, True)[1:]: + with self.subTest(mode + "_%i_bookmarks%s%s" % ( + bm_count, + "_check_bms_pre_query" if check_bms_pre_query + else "", + "_consume" if consume else "_no_consume" + )): + test(mode, bm_count, check_bms_pre_query, consume) + + def test_bookmarks_tx_run(self): + def test(mode_, bm_count_, check_bms_pre_query_, consume_): + bookmarks = ["bm:%i" % (i + 1) for i in range(bm_count_)] + with self._new_server(tx=True, bms_in=bookmarks, + bm_out="bm:re") as server: + with self._new_driver() as driver: + session = driver.session(mode_[0], bookmarks=bookmarks) + if check_bms_pre_query_: + self.assertEqual(sorted(session.last_bookmarks()), + sorted(bookmarks)) + tx = session.begin_transaction() + res = tx.run("RETURN 1 AS n") + if consume_: + res.consume() + if check_bms_pre_query_: + self.assertEqual(sorted(session.last_bookmarks()), + sorted(bookmarks)) + tx.commit() + self.assertEqual(session.last_bookmarks(), ["bm:re"]) + session.close() + server.done() - def test_send_and_receive_bookmarks_write_tx(self): - self._server.start( - path=self.script_path(self.version_dir, - "send_and_receive_bookmark_write_tx.script"), - vars_={ - "#BOOKMARKS#": '["neo4j:bookmark:v1:tx42"]' - } - ) - session = self._driver.session( - access_mode="w", - bookmarks=["neo4j:bookmark:v1:tx42"] - ) - tx = session.begin_transaction() - result = tx.run("MATCH (n) RETURN n.name AS name") - result.next() - tx.commit() - bookmarks = session.last_bookmarks() + for mode in ("read", "write"): + for bm_count in (0, 1, 2): + for check_bms_pre_query in (False, True): + for consume in (False, True): + with self.subTest(mode + "_%i_bookmarks%s%s" % ( + bm_count, + "_check_bms_pre_query" if check_bms_pre_query + else "", + "_consume" if consume else "_no_consume" + )): + test(mode, bm_count, check_bms_pre_query, consume) + + def test_bookmarks_tx_func(self): + def work_consume(tx): + res = tx.run("RETURN 1 AS n") + res.consume() + + def work_no_consume(tx): + tx.run("RETURN 1 AS n") + + def test(mode_, bm_count_, check_bms_pre_query_, consume_): + bookmarks = ["bm:%i" % (i + 1) for i in range(bm_count_)] + with self._new_server(tx=True, bms_in=bookmarks, + bm_out="bm:re") as server: + with self._new_driver() as driver: + session = driver.session(mode_[0], bookmarks=bookmarks) + if check_bms_pre_query_: + self.assertEqual(sorted(session.last_bookmarks()), + sorted(bookmarks)) + work = work_consume if consume_ else work_no_consume + if mode == "write": + session.write_transaction(work) + else: + session.read_transaction(work) + self.assertEqual(session.last_bookmarks(), ["bm:re"]) + session.close() + server.done() - self.assertEqual(bookmarks, ["neo4j:bookmark:v1:tx4242"]) - self._server.done() + for mode in ("read", "write"): + for bm_count in (0, 1, 2): + for check_bms_pre_query in (False, True): + for consume in (False, True): + with self.subTest(mode + "_%i_bookmarks%s%s" % ( + bm_count, + "_check_bms_pre_query" if check_bms_pre_query + else "", + "_consume" if consume else "_no_consume" + )): + test(mode, bm_count, check_bms_pre_query, consume) def test_sequence_of_writing_and_reading_tx(self): self._server.start( @@ -123,32 +204,3 @@ def test_sequence_of_writing_and_reading_tx(self): self.assertEqual(bookmarks, ["neo4j:bookmark:v1:tx424242"]) self._server.done() - - def test_send_and_receive_multiple_bookmarks_write_tx(self): - self._server.start( - path=self.script_path(self.version_dir, - "send_and_receive_bookmark_write_tx.script"), - vars_={ - "#BOOKMARKS#": '["neo4j:bookmark:v1:tx42", ' - '"neo4j:bookmark:v1:tx43", ' - '"neo4j:bookmark:v1:tx44", ' - '"neo4j:bookmark:v1:tx45", ' - '"neo4j:bookmark:v1:tx46"] ' - } - ) - session = self._driver.session( - access_mode="w", - bookmarks=[ - "neo4j:bookmark:v1:tx42", "neo4j:bookmark:v1:tx43", - "neo4j:bookmark:v1:tx44", "neo4j:bookmark:v1:tx45", - "neo4j:bookmark:v1:tx46" - ] - ) - tx = session.begin_transaction() - result = tx.run("MATCH (n) RETURN n.name AS name") - result.next() - tx.commit() - bookmarks = session.last_bookmarks() - - self.assertEqual(bookmarks, ["neo4j:bookmark:v1:tx4242"]) - self._server.done()