-
Notifications
You must be signed in to change notification settings - Fork 3
[Refactor] Support Subquery for Query Parser #95
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
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds subquery support to the SQL QueryParser, refactoring parsing into a reusable parse_query_dict() flow and extending AST node support where needed.
Changes:
- Refactors
QueryParser.parse()to delegate to a newparse_query_dict()helper. - Adds parsing for subqueries in expressions (including
EXISTS) and in SELECT/FROM/JOIN contexts. - Updates AST node behavior to better support
SubqueryNodecomparisons and JOINs against subqueries.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
core/query_parser.py |
Implements recursive subquery parsing and refactors parsing into parse_query_dict(); extends FROM/JOIN/SELECT/expression parsing. |
core/ast/node.py |
Extends AST node equality/hashing for SubqueryNode and broadens JoinNode typing to allow subquery join targets. |
tests/test_query_parser.py |
Cleans up old commented tests and asserts subquery parsing via AST equality. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| qb_ast = parser.parse(sql) | ||
| # assert isinstance(qb_ast, QueryNode) | ||
|
|
||
| # Check WHERE clause has boolean logic | ||
| # where_clause = None | ||
| # for child in qb_ast.children: | ||
| # if child.type == NodeType.WHERE: | ||
| # where_clause = child | ||
| # break | ||
|
|
||
| # assert where_clause is not None | ||
| # condition = next(iter(where_clause.children)) | ||
| # assert isinstance(condition, OperatorNode) | ||
| # assert condition.name == "AND" | ||
|
|
||
|
|
||
| def test_parse_8(): | ||
| query = get_query(29) | ||
| sql = query['pattern'] | ||
|
|
||
| qb_ast = parser.parse(sql) | ||
| # assert isinstance(qb_ast, QueryNode) | ||
|
|
||
| # Check for UNION operation (this query has UNION) | ||
| # Check if the query contains UNION | ||
| # assert 'UNION' in sql.upper() | ||
|
|
||
| # Check for subqueries in WHERE clause | ||
| # where_clause = None | ||
| # for child in qb_ast.children: | ||
| # if child.type == NodeType.WHERE: | ||
| # where_clause = child | ||
| # break | ||
|
|
||
| # assert where_clause is not None | ||
|
|
||
|
|
||
| def test_parse_9(): | ||
| query = get_query(31) | ||
| sql = query['pattern'] | ||
|
|
||
| qb_ast = parser.parse(sql) | ||
| # assert isinstance(qb_ast, QueryNode) | ||
|
|
||
| # Check SELECT clause has complex aggregation | ||
| # select_clause = None | ||
| # for child in qb_ast.children: | ||
| # if child.type == NodeType.SELECT: | ||
| # select_clause = child | ||
| # break | ||
|
|
||
| # assert select_clause is not None | ||
| # assert len(select_clause.children) == 3 | ||
|
|
||
| # Check for CASE statement | ||
| # for child in select_clause.children: | ||
| # if isinstance(child, FunctionNode) and child.name == "CASE": | ||
| # assert True | ||
| # break | ||
|
|
||
| # Check GROUP BY clause | ||
| # group_by_clause = None | ||
| # for child in qb_ast.children: | ||
| # if child.type == NodeType.GROUP_BY: | ||
| # group_by_clause = child | ||
| # break | ||
|
|
||
| # assert group_by_clause is not None | ||
|
|
||
|
|
||
| def test_parse_10(): | ||
| query = get_query(42) | ||
| sql = query['pattern'] | ||
|
|
||
| qb_ast = parser.parse(sql) | ||
| # assert isinstance(qb_ast, QueryNode) | ||
|
|
||
| # Check SELECT clause | ||
| # select_clause = None | ||
| # for child in qb_ast.children: | ||
| # if child.type == NodeType.SELECT: | ||
| # select_clause = child | ||
| # break | ||
|
|
||
| # assert select_clause is not None | ||
| # assert len(select_clause.children) == 2 | ||
|
|
||
| # Check WHERE clause has complex conditions | ||
| # where_clause = None | ||
| # for child in qb_ast.children: | ||
| # if child.type == NodeType.WHERE: | ||
| # where_clause = child | ||
| # break | ||
|
|
||
| # assert where_clause is not None | ||
|
|
||
| # Check GROUP BY clause | ||
| # group_by_clause = None | ||
| # for child in qb_ast.children: | ||
| # if child.type == NodeType.GROUP_BY: | ||
| # group_by_clause = child | ||
| # break | ||
|
|
||
| # assert group_by_clause is not None | ||
| # assert len(group_by_clause.children) == 2 No newline at end of file | ||
| assert qb_ast == expected_ast No newline at end of file |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Current tests cover a subquery only in the WHERE/IN case. This PR also adds new parsing behavior for subqueries in SELECT, FROM, JOIN targets, and for EXISTS (see parse_select(), parse_from(), and parse_expression()), but those paths are not exercised here. Adding focused tests for each newly supported subquery location would help prevent regressions and validate the mo_sql_parsing shape handling (especially JOIN-derived tables).
| return hash((super().__hash__(), self.value)) | ||
|
|
||
|
|
||
| class VarNode(Node): |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VarNode not implemented/used yet.
| self.name = _name | ||
|
|
||
|
|
||
| class VarSetNode(Node): |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VarSetNode not implemented/used yet.
colinthebomb1
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Overview:
This PR implements subquery support for the query parser.
Code Changes:
query_parser.pySubquerynode innode.py