Skip to content

Commit

Permalink
Merge pull request #33 from Accenture/develop
Browse files Browse the repository at this point in the history
new clause - UNION
  • Loading branch information
Roei-Levi authored Aug 30, 2023
2 parents 0fe7420 + 1de4d60 commit 9e23b63
Show file tree
Hide file tree
Showing 17 changed files with 115 additions and 29 deletions.
50 changes: 37 additions & 13 deletions src/cymple/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ def return_literal(self, literal: str):
return ReturnAvailable(self.query + ret)

def return_mapping(self, mappings: List[Mapping]):
"""Concatenate a RETURN statement for mutiple objects.
"""Concatenate a RETURN statement for multiple objects.
:param mappings: The mapping (or a list of mappings) of db property names to code names, to be returned
:type mappings: List[Mapping]
Expand Down Expand Up @@ -719,6 +719,26 @@ def skip(self, skip_count: Union[int, str]):
return SkipAvailable(self.query + ret)


class Union(Query):
"""A class for representing a "UNION" clause."""

def union(self):
"""Combines the results of two or more queries. Duplicates are removed.
:return: A Query object with a query that contains the new clause.
:rtype: UnionAvailable
"""
return UnionAvailable(self.query + f' UNION')

def union_all(self):
"""Combines the results of two or more queries including duplicates.
:return: A Query object with a query that contains the new clause.
:rtype: UnionAvailable
"""
return UnionAvailable(self.query + f' UNION ALL')


class Unwind(Query):
"""A class for representing a "UNWIND" clause."""

Expand Down Expand Up @@ -838,23 +858,23 @@ class CaseWhenAvailable(QueryStartAvailable, Unwind, Where, CaseWhen, Return, Se
"""A class decorator declares a CaseWhen is available in the current query."""


class CreateAvailable(Node):
class CreateAvailable(Node, Union):
"""A class decorator declares a Create is available in the current query."""


class DeleteAvailable(Return, CaseWhen):
class DeleteAvailable(Return, CaseWhen, Union):
"""A class decorator declares a Delete is available in the current query."""


class LimitAvailable(QueryStartAvailable, Unwind, Where, CaseWhen, Return, Set, Skip):
class LimitAvailable(QueryStartAvailable, Unwind, Where, CaseWhen, Return, Set, Skip, Union):
"""A class decorator declares a Limit is available in the current query."""


class MatchAvailable(Node, Return, OperatorStart):
"""A class decorator declares a Match is available in the current query."""


class MergeAvailable(NodeAfterMerge, Return, OperatorStart):
class MergeAvailable(NodeAfterMerge, Return, OperatorStart, Union):
"""A class decorator declares a Merge is available in the current query."""


Expand Down Expand Up @@ -882,11 +902,11 @@ class OperatorStartAvailable(QueryStartAvailable, Node, OperatorEnd):
"""A class decorator declares a OperatorStart is available in the current query."""


class OrderByAvailable(Limit, Skip):
class OrderByAvailable(Limit, Skip, Union):
"""A class decorator declares a OrderBy is available in the current query."""


class ProcedureAvailable(Yield, Return, QueryStartAvailable):
class ProcedureAvailable(Yield, Return, QueryStartAvailable, Union):
"""A class decorator declares a Procedure is available in the current query."""


Expand All @@ -898,26 +918,30 @@ class RelationAfterMergeAvailable(NodeAfterMerge):
"""A class decorator declares a RelationAfterMerge is available in the current query."""


class RemoveAvailable(Set, Return):
class RemoveAvailable(Set, Return, Union):
"""A class decorator declares a Remove is available in the current query."""


class ReturnAvailable(QueryStartAvailable, Unwind, Return, Limit, Skip, OrderBy):
class ReturnAvailable(QueryStartAvailable, Unwind, Return, Limit, Skip, OrderBy, Union):
"""A class decorator declares a Return is available in the current query."""


class SetAvailable(QueryStartAvailable, Set, Remove, Unwind, Return):
class SetAvailable(QueryStartAvailable, Set, Remove, Unwind, Return, Union):
"""A class decorator declares a Set is available in the current query."""


class SetAfterMergeAvailable(QueryStartAvailable, OnCreate, OnMatch, SetAfterMerge, Unwind, Return):
class SetAfterMergeAvailable(QueryStartAvailable, OnCreate, OnMatch, SetAfterMerge, Unwind, Return, Union):
"""A class decorator declares a SetAfterMerge is available in the current query."""


class SkipAvailable(QueryStartAvailable, Unwind, Where, CaseWhen, Return, Set, Remove, Limit):
class SkipAvailable(QueryStartAvailable, Unwind, Where, CaseWhen, Return, Set, Remove, Limit, Union):
"""A class decorator declares a Skip is available in the current query."""


class UnionAvailable(Call, Create, Delete, Match, Merge, Remove, Return, Set, Unwind, With):
"""A class decorator declares a Union is available in the current query."""


class UnwindAvailable(QueryStartAvailable, Unwind, Return, Create, Remove):
"""A class decorator declares a Unwind is available in the current query."""

Expand All @@ -934,7 +958,7 @@ class YieldAvailable(QueryStartAvailable, Node, Where, Return):
"""A class decorator declares a Yield is available in the current query."""


class AnyAvailable(Call, CaseWhen, Create, Delete, Limit, Match, Merge, Node, NodeAfterMerge, OnCreate, OnMatch, OperatorEnd, OperatorStart, OrderBy, Procedure, QueryStart, Relation, RelationAfterMerge, Remove, Return, Set, SetAfterMerge, Skip, Unwind, Where, With, Yield):
class AnyAvailable(Call, CaseWhen, Create, Delete, Limit, Match, Merge, Node, NodeAfterMerge, OnCreate, OnMatch, OperatorEnd, OperatorStart, OrderBy, Procedure, QueryStart, Relation, RelationAfterMerge, Remove, Return, Set, SetAfterMerge, Skip, Union, Unwind, Where, With, Yield):
"""A class decorator declares anything is available in the current query."""


Expand Down
3 changes: 2 additions & 1 deletion src/cymple/internal/declarations/create.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"clause_name": "CREATE",
"successors": [
"Node"
"Node",
"Union"
]
}
3 changes: 2 additions & 1 deletion src/cymple/internal/declarations/delete.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
],
"successors": [
"Return",
"CaseWhen"
"CaseWhen",
"Union"
]
}
3 changes: 2 additions & 1 deletion src/cymple/internal/declarations/limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"CaseWhen",
"Return",
"Set",
"Skip"
"Skip",
"Union"
]
}
3 changes: 2 additions & 1 deletion src/cymple/internal/declarations/merge.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"successors": [
"NodeAfterMerge",
"Return",
"OperatorStart"
"OperatorStart",
"Union"
]
}
3 changes: 2 additions & 1 deletion src/cymple/internal/declarations/order_by.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
],
"successors": [
"Limit",
"Skip"
"Skip",
"Union"
]
}
3 changes: 2 additions & 1 deletion src/cymple/internal/declarations/procedure.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"successors": [
"Yield",
"Return",
"QueryStartAvailable"
"QueryStartAvailable",
"Union"
]
}
3 changes: 2 additions & 1 deletion src/cymple/internal/declarations/remove.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
],
"successors": [
"Set",
"Return"
"Return",
"Union"
]
}
5 changes: 3 additions & 2 deletions src/cymple/internal/declarations/return.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
{
"name": "return_mapping",
"docstring_summary": "Concatenate a RETURN statement for mutiple objects.",
"docstring_summary": "Concatenate a RETURN statement for multiple objects.",
"args": {
"mappings": {
"type": "List[Mapping]",
Expand All @@ -28,6 +28,7 @@
"Return",
"Limit",
"Skip",
"OrderBy"
"OrderBy",
"Union"
]
}
3 changes: 2 additions & 1 deletion src/cymple/internal/declarations/set.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"Set",
"Remove",
"Unwind",
"Return"
"Return",
"Union"
]
}
3 changes: 2 additions & 1 deletion src/cymple/internal/declarations/set_after_merge.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"OnMatch",
"SetAfterMerge",
"Unwind",
"Return"
"Return",
"Union"
]
}
3 changes: 2 additions & 1 deletion src/cymple/internal/declarations/skip.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"Return",
"Set",
"Remove",
"Limit"
"Limit",
"Union"
]
}
25 changes: 25 additions & 0 deletions src/cymple/internal/declarations/union.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"clause_name": "UNION",
"methods": [
{
"name": "union",
"docstring_summary": "Combines the results of two or more queries. Duplicates are removed."
},
{
"name": "union_all",
"docstring_summary": "Combines the results of two or more queries including duplicates."
}
],
"successors": [
"Call",
"Create",
"Delete",
"Match",
"Merge",
"Remove",
"Return",
"Set",
"Unwind",
"With"
]
}
6 changes: 6 additions & 0 deletions src/cymple/internal/overloads/union.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def union(self):
return UnionAvailable(self.query + f' UNION')


def union_all(self):
return UnionAvailable(self.query + f' UNION ALL')
2 changes: 1 addition & 1 deletion src/cymple/version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Version information for Cymple"""

__version__: str = "0.10.0"
__version__: str = "0.11.0"
11 changes: 8 additions & 3 deletions tests/unit/test_clauses.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,11 @@
'ORDER BY (Desc)': qb.match().node(ref_name='n').return_literal('n.name, n.age').order_by("n.name", False),
'CREATE': qb.reset().create().node(ref_name='n').return_literal('n'),
'REMOVE': qb.reset().match().node(ref_name='n').remove('n.name').return_literal('n.age, n.name'),
'REMOVE (list)': qb.reset().match().node(ref_name='n').remove(['n.age', 'n.name']).return_literal('n.age, n.name')

'REMOVE (list)': qb.reset().match().node(ref_name='n').remove(['n.age', 'n.name']).return_literal('n.age, n.name'),
'UNION': qb.match().node(ref_name='n', labels="Actor").return_mapping([('n.name', 'name')]).union().match().node(
ref_name='n', labels="Movie").return_mapping([('n.title', 'name')]),
'UNION (all)': qb.match().node(ref_name='n', labels="Actor").return_mapping(
[('n.name', 'name')]).union_all().match().node(ref_name='n', labels="Movie").return_mapping([('n.title', 'name')])
}

expected = {
Expand Down Expand Up @@ -110,7 +113,9 @@
'ORDER BY (Desc)': 'MATCH (n) RETURN n.name, n.age ORDER BY n.name DESC',
'CREATE': 'CREATE (n) RETURN n',
'REMOVE': 'MATCH (n) REMOVE n.name RETURN n.age, n.name',
'REMOVE (list)': 'MATCH (n) REMOVE n.age, n.name RETURN n.age, n.name'
'REMOVE (list)': 'MATCH (n) REMOVE n.age, n.name RETURN n.age, n.name',
'UNION': 'MATCH (n: Actor) RETURN n.name as name UNION MATCH (n: Movie) RETURN n.title as name',
'UNION (all)': 'MATCH (n: Actor) RETURN n.name as name UNION ALL MATCH (n: Movie) RETURN n.title as name',
}


Expand Down
15 changes: 15 additions & 0 deletions tests/unit/test_real_use_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,18 @@ def test_cypher_set_unescaped_after_merge():
.get())

assert actual_query == expected_query

def test_multiple_where_1():
expected_query = 'MATCH (p) WHERE p.lock IS NULL OR p.value IS NULL'
actual_query = str(QueryBuilder().match().node(ref_name='p').where_multiple({'p.lock': 'NULL', 'p.value': 'NULL'}, 'IS', ' OR ', escape=False))
assert actual_query == expected_query

def test_multiple_where_2():
expected_query = 'MATCH (n: Person) WHERE n.age=32 AND NOT n:Teacher'
actual_query = str(QueryBuilder().match().node('Person', ref_name='n').where_literal('n.age=32 AND NOT n:Teacher'))
assert actual_query == expected_query

def test_with_list():
expected_query = 'MATCH (n) WITH n as n_test WITH [n_test] as n_list RETURN n_list as n_test'
actual_query = str(QueryBuilder().match().node(ref_name='n').with_('n as n_test').with_('[n_test] as n_list').return_mapping(('n_list', 'n_test')))
assert actual_query == expected_query

0 comments on commit 9e23b63

Please sign in to comment.