1
1
import inspect
2
2
import re
3
3
import string
4
+ import warnings
4
5
from dataclasses import dataclass
5
6
from typing import Any , Dict , List
6
7
from typing import Optional as TOptional
12
13
from neomodel .exceptions import MultipleNodesReturned
13
14
from neomodel .match_q import Q , QBase
14
15
from neomodel .properties import AliasProperty , ArrayProperty , Property
16
+ from neomodel .typing import Transformation
15
17
from neomodel .util import INCOMING , OUTGOING
16
18
17
19
CYPHER_ACTIONS_WITH_SIDE_EFFECT_EXPR = re .compile (r"(?i:MERGE|CREATE|DELETE|DETACH)" )
@@ -588,9 +590,11 @@ def build_traversal_from_path(
588
590
}
589
591
else :
590
592
existing_rhs_name = subgraph [part ][
591
- "rel_variable_name"
592
- if relation .get ("relation_filtering" )
593
- else "variable_name"
593
+ (
594
+ "rel_variable_name"
595
+ if relation .get ("relation_filtering" )
596
+ else "variable_name"
597
+ )
594
598
]
595
599
if relation ["include_in_return" ] and not already_present :
596
600
self ._additional_return (rel_ident )
@@ -838,32 +842,27 @@ def build_query(self) -> str:
838
842
query += " WITH "
839
843
query += self ._ast .with_clause
840
844
845
+ returned_items : list [str ] = []
841
846
if hasattr (self .node_set , "_intermediate_transforms" ):
842
847
for transform in self .node_set ._intermediate_transforms :
843
848
query += " WITH "
849
+ query += "DISTINCT " if transform .get ("distinct" ) else ""
844
850
injected_vars : list = []
845
851
# Reset return list since we'll probably invalidate most variables
846
852
self ._ast .return_clause = ""
847
853
self ._ast .additional_return = []
848
- for name , source in transform ["vars" ].items ():
849
- if type (source ) is str :
850
- injected_vars .append (f"{ source } AS { name } " )
851
- elif isinstance (source , RelationNameResolver ):
852
- result = self .lookup_query_variable (
853
- source .relation , return_relation = True
854
- )
855
- if not result :
856
- raise ValueError (
857
- f"Unable to resolve variable name for relation { source .relation } ."
858
- )
859
- injected_vars .append (f"{ result [0 ]} AS { name } " )
860
- elif isinstance (source , NodeNameResolver ):
861
- result = self .lookup_query_variable (source .node )
862
- if not result :
863
- raise ValueError (
864
- f"Unable to resolve variable name for node { source .node } ."
865
- )
866
- injected_vars .append (f"{ result [0 ]} AS { name } " )
854
+ for name , varprops in transform ["vars" ].items ():
855
+ source = varprops ["source" ]
856
+ if isinstance (source , (NodeNameResolver , RelationNameResolver )):
857
+ transformation = source .resolve (self )
858
+ else :
859
+ transformation = source
860
+ if varprops .get ("source_prop" ):
861
+ transformation += f".{ varprops ['source_prop' ]} "
862
+ transformation += f" AS { name } "
863
+ if varprops .get ("include_in_return" ):
864
+ returned_items += [name ]
865
+ injected_vars .append (transformation )
867
866
query += "," .join (injected_vars )
868
867
if not transform ["ordering" ]:
869
868
continue
@@ -879,7 +878,6 @@ def build_query(self) -> str:
879
878
ordering .append (item )
880
879
query += "," .join (ordering )
881
880
882
- returned_items : list [str ] = []
883
881
if hasattr (self .node_set , "_subqueries" ):
884
882
for subquery , return_set in self .node_set ._subqueries :
885
883
outer_primary_var = self ._ast .return_clause
@@ -978,7 +976,9 @@ async def _execute(self, lazy: bool = False, dict_output: bool = False):
978
976
]
979
977
query = self .build_query ()
980
978
results , prop_names = await adb .cypher_query (
981
- query , self ._query_params , resolve_objects = True
979
+ query ,
980
+ self ._query_params ,
981
+ resolve_objects = True ,
982
982
)
983
983
if dict_output :
984
984
for item in results :
@@ -1098,6 +1098,14 @@ class RelationNameResolver:
1098
1098
1099
1099
relation : str
1100
1100
1101
+ def resolve (self , qbuilder : AsyncQueryBuilder ) -> str :
1102
+ result = qbuilder .lookup_query_variable (self .relation , True )
1103
+ if result is None :
1104
+ raise ValueError (
1105
+ f"Unable to resolve variable name for relation { self .relation } "
1106
+ )
1107
+ return result [0 ]
1108
+
1101
1109
1102
1110
@dataclass
1103
1111
class NodeNameResolver :
@@ -1111,6 +1119,12 @@ class NodeNameResolver:
1111
1119
1112
1120
node : str
1113
1121
1122
+ def resolve (self , qbuilder : AsyncQueryBuilder ) -> str :
1123
+ result = qbuilder .lookup_query_variable (self .node )
1124
+ if result is None :
1125
+ raise ValueError (f"Unable to resolve variable name for node { self .node } " )
1126
+ return result [0 ]
1127
+
1114
1128
1115
1129
@dataclass
1116
1130
class BaseFunction :
@@ -1123,15 +1137,10 @@ def get_internal_name(self) -> str:
1123
1137
return self ._internal_name
1124
1138
1125
1139
def resolve_internal_name (self , qbuilder : AsyncQueryBuilder ) -> str :
1126
- if isinstance (self .input_name , NodeNameResolver ):
1127
- result = qbuilder .lookup_query_variable (self .input_name .node )
1128
- elif isinstance (self .input_name , RelationNameResolver ):
1129
- result = qbuilder .lookup_query_variable (self .input_name .relation , True )
1140
+ if isinstance (self .input_name , (NodeNameResolver , RelationNameResolver )):
1141
+ self ._internal_name = self .input_name .resolve (qbuilder )
1130
1142
else :
1131
- result = (str (self .input_name ), None )
1132
- if result is None :
1133
- raise ValueError (f"Unknown variable { self .input_name } used in Collect()" )
1134
- self ._internal_name = result [0 ]
1143
+ self ._internal_name = str (self .input_name )
1135
1144
return self ._internal_name
1136
1145
1137
1146
def render (self , qbuilder : AsyncQueryBuilder ) -> str :
@@ -1538,20 +1547,26 @@ async def subquery(
1538
1547
return self
1539
1548
1540
1549
def intermediate_transform (
1541
- self , vars : Dict [str , Any ], ordering : TOptional [list ] = None
1550
+ self ,
1551
+ vars : Dict [str , Transformation ],
1552
+ distinct : bool = False ,
1553
+ ordering : TOptional [list ] = None ,
1542
1554
) -> "AsyncNodeSet" :
1543
1555
if not vars :
1544
1556
raise ValueError (
1545
1557
"You must provide one variable at least when calling intermediate_transform()"
1546
1558
)
1547
- for name , source in vars .items ():
1559
+ for name , props in vars .items ():
1560
+ source = props ["source" ]
1548
1561
if type (source ) is not str and not isinstance (
1549
- source , (NodeNameResolver , RelationNameResolver )
1562
+ source , (NodeNameResolver , RelationNameResolver , RawCypher )
1550
1563
):
1551
1564
raise ValueError (
1552
1565
f"Wrong source type specified for variable '{ name } ', should be a string or an instance of NodeNameResolver or RelationNameResolver"
1553
1566
)
1554
- self ._intermediate_transforms .append ({"vars" : vars , "ordering" : ordering })
1567
+ self ._intermediate_transforms .append (
1568
+ {"vars" : vars , "distinct" : distinct , "ordering" : ordering }
1569
+ )
1555
1570
return self
1556
1571
1557
1572
0 commit comments