-
Notifications
You must be signed in to change notification settings - Fork 14
test: assert the client parses execution plans, and fix a plan parser bug #267
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
Merged
Merged
Changes from 3 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
da00ddd
fix: only forward accepted kwargs from Is_Cluster to redis.Redis
Naseem77 f1f20a9
test: cover Is_Cluster kwargs filtering
Naseem77 1ebf438
Merge branch 'main' into fix/async-is-cluster-unknown-kwargs
Naseem77 6332dc5
fix: attach every sibling operation to its parent when parsing a plan
Naseem77 5f4e73c
test: assert the client parses execution plans, not which plan the en…
Naseem77 49aa012
test: use a slower query so the slowlog actually records it
Naseem77 bab62f6
Merge branch 'naseem77-align-py-tests-with-engine' into fix/async-is-…
Naseem77 f7c3de7
Merge pull request #262 from FalkorDB/fix/async-is-cluster-unknown-kw…
Naseem77 afa2edf
test: assert exact execution plans where both engines agree
Naseem77 c0bdfb9
Merge branch 'main' into naseem77-align-py-tests-with-engine
galshubeli e78b3dd
Merge branch 'main' into naseem77-align-py-tests-with-engine
Naseem77 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| """Engine-agnostic assertions for execution plans. | ||
|
|
||
| Which operations a query compiles into is the engine's business and it changes | ||
| between engine versions. The client's job is to issue GRAPH.EXPLAIN / | ||
| GRAPH.PROFILE and turn the reply into an operation tree, so that is what these | ||
| helpers check: every line of the raw reply became exactly one operation, nested | ||
| exactly as deep as that line was indented. | ||
| """ | ||
|
|
||
| from typing import Iterator, Optional, Tuple | ||
|
|
||
| from falkordb.execution_plan import ExecutionPlan, Operation | ||
|
|
||
| INDENT = " " | ||
|
|
||
|
|
||
| def iter_operations(op: Operation, depth: int = 0) -> Iterator[Tuple[int, Operation]]: | ||
| """Yields (depth, operation) for the whole tree, depth first.""" | ||
|
|
||
| yield depth, op | ||
| for child in op.children: | ||
| yield from iter_operations(child, depth + 1) | ||
|
|
||
|
|
||
| def assert_parsed_plan( | ||
| plan: ExecutionPlan, | ||
| min_operations: int = 1, | ||
| expect_args: bool = False, | ||
| ) -> None: | ||
| """Asserts the client parsed an execution plan reply correctly.""" | ||
|
|
||
| root = plan.structured_plan | ||
| assert isinstance(root, Operation) | ||
|
|
||
| parsed = list(iter_operations(root)) | ||
| lines = [line for line in plan.plan if line.strip()] | ||
|
|
||
| # every line of the raw reply became exactly one operation | ||
| assert len(parsed) == len(lines) | ||
| assert len(parsed) >= min_operations | ||
|
|
||
| for (depth, op), line in zip(parsed, lines): | ||
| # the operation sits as deep in the tree as its line was indented, | ||
| # which is what makes this a test of the parser rather than the engine | ||
| assert depth == (len(line) - len(line.lstrip())) // len(INDENT) | ||
|
|
||
| # indentation and the argument separator were stripped off the name | ||
| assert isinstance(op.name, str) | ||
| assert op.name == op.name.strip() | ||
| assert op.name != "" | ||
| assert "|" not in op.name | ||
| assert op.name == line.split("|")[0].strip() | ||
|
|
||
| assert op.args is None or isinstance(op.args, str) | ||
| assert isinstance(op.children, list) | ||
|
|
||
| if expect_args: | ||
| assert any(op.args for _, op in parsed) | ||
|
|
||
|
|
||
| def assert_parsed_profile( | ||
| plan: ExecutionPlan, | ||
| min_operations: int = 1, | ||
| expect_args: bool = False, | ||
| records_produced: Optional[int] = None, | ||
| ) -> None: | ||
| """Asserts the client parsed a profile reply, statistics included.""" | ||
|
|
||
| assert_parsed_plan(plan, min_operations, expect_args) | ||
|
|
||
| parsed = list(iter_operations(plan.structured_plan)) | ||
| for _, op in parsed: | ||
| assert op.profile_stats is not None | ||
| assert isinstance(op.records_produced, int) | ||
| assert op.records_produced >= 0 | ||
| assert isinstance(op.execution_time, float) | ||
| assert op.execution_time >= 0 | ||
|
|
||
| if records_produced is not None: | ||
| # how many rows the query yields is a property of the query, not of the | ||
| # engine, so the client must report it whichever engine answered | ||
| assert max(op.records_produced for _, op in parsed) == records_produced | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,8 @@ | |
|
|
||
| from falkordb.asyncio import FalkorDB | ||
|
|
||
| from .plan_helpers import assert_parsed_profile | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_profile(): | ||
|
|
@@ -14,20 +16,9 @@ async def test_profile(): | |
|
|
||
| plan = await g.profile("UNWIND range(0, 3) AS x RETURN x") | ||
|
|
||
| results_op = plan.structured_plan | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. even the number of operations might change from one implementation to another. |
||
| assert results_op.name == "Results" | ||
| assert len(results_op.children) == 1 | ||
| assert results_op.profile_stats.records_produced == 4 | ||
|
|
||
| project_op = results_op.children[0] | ||
| assert project_op.name == "Project" | ||
| assert len(project_op.children) == 1 | ||
| assert project_op.profile_stats.records_produced == 4 | ||
|
|
||
| unwind_op = project_op.children[0] | ||
| assert unwind_op.name == "Unwind" | ||
| assert len(unwind_op.children) == 0 | ||
| assert unwind_op.profile_stats.records_produced == 4 | ||
| # which operations the query compiles into is up to the engine, the client | ||
| # is responsible for parsing the plan and its statistics | ||
| assert_parsed_profile(plan, min_operations=2, records_produced=4) | ||
|
|
||
| # close the connection pool | ||
| await pool.aclose() | ||
|
|
@@ -43,31 +34,7 @@ async def test_cartesian_product_profile(): | |
|
|
||
| plan = await g.profile("MATCH (a), (b) RETURN *") | ||
|
|
||
| results_op = plan.structured_plan | ||
| assert results_op.name == "Results" | ||
| assert len(results_op.children) == 1 | ||
| assert results_op.profile_stats.records_produced == 0 | ||
|
|
||
| project_op = results_op.children[0] | ||
| assert project_op.name == "Project" | ||
| assert len(project_op.children) == 1 | ||
| assert project_op.profile_stats.records_produced == 0 | ||
|
|
||
| cp_op = project_op.children[0] | ||
| assert cp_op.name == "Cartesian Product" | ||
| assert len(cp_op.children) == 2 | ||
| assert cp_op.profile_stats.records_produced == 0 | ||
|
|
||
| scan_a_op = cp_op.children[0] | ||
| scan_b_op = cp_op.children[1] | ||
|
|
||
| assert scan_a_op.name == "All Node Scan" | ||
| assert len(scan_a_op.children) == 0 | ||
| assert scan_a_op.profile_stats.records_produced == 0 | ||
|
|
||
| assert scan_b_op.name == "All Node Scan" | ||
| assert len(scan_b_op.children) == 0 | ||
| assert scan_b_op.profile_stats.records_produced == 0 | ||
| assert_parsed_profile(plan, min_operations=4, expect_args=True, records_produced=0) | ||
|
|
||
| # close the connection pool | ||
| await pool.aclose() | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.