@@ -426,11 +426,11 @@ def append(
426426 # So for those cases we need to construct the correct filters,
427427 # corresponding to the provided classes and value of `subclassing`.
428428 if ormclass == EntityTypes .NODE :
429- self ._add_node_type_filter (tag , classifiers , subclassing )
430- self ._add_process_type_filter (tag , classifiers , subclassing )
429+ self ._add_node_type_filter (tag , classifiers , subclassing , outerjoin = outerjoin )
430+ self ._add_process_type_filter (tag , classifiers , subclassing , outerjoin = outerjoin )
431431
432432 elif ormclass == EntityTypes .GROUP :
433- self ._add_group_type_filter (tag , classifiers , subclassing )
433+ self ._add_group_type_filter (tag , classifiers , subclassing , outerjoin = outerjoin )
434434
435435 # The order has to be first _add_node_type_filter and then add_filter.
436436 # If the user adds a query on the type column, it overwrites what I did
@@ -733,12 +733,15 @@ def _process_filters(filters: FilterType) -> Dict[str, Any]:
733733
734734 return processed_filters
735735
736- def _add_node_type_filter (self , tagspec : str , classifiers : List [Classifier ], subclassing : bool ):
736+ def _add_node_type_filter (
737+ self , tagspec : str , classifiers : List [Classifier ], subclassing : bool , outerjoin : bool = False
738+ ):
737739 """Add a filter based on node type.
738740
739741 :param tagspec: The tag, which has to exist already as a key in self._filters
740742 :param classifiers: a dictionary with classifiers
741743 :param subclassing: if True, allow for subclasses of the ormclass
744+ :param outerjoin: if True, wrap the filter with OR id IS NULL
742745 """
743746 if len (classifiers ) > 1 :
744747 # If a list was passed to QueryBuilder.append, this propagates to a list in the classifiers
@@ -748,14 +751,21 @@ def _add_node_type_filter(self, tagspec: str, classifiers: List[Classifier], sub
748751 else :
749752 entity_type_filter = _get_node_type_filter (classifiers [0 ], subclassing )
750753
751- self .add_filter (tagspec , {'node_type' : entity_type_filter })
754+ filters : dict = {'node_type' : entity_type_filter }
755+ if outerjoin :
756+ filters = {'or' : [filters , {'id' : None }]}
752757
753- def _add_process_type_filter (self , tagspec : str , classifiers : List [Classifier ], subclassing : bool ) -> None :
758+ self .add_filter (tagspec , filters )
759+
760+ def _add_process_type_filter (
761+ self , tagspec : str , classifiers : List [Classifier ], subclassing : bool , outerjoin : bool = False
762+ ) -> None :
754763 """Add a filter based on process type.
755764
756765 :param tagspec: The tag, which has to exist already as a key in self._filters
757766 :param classifiers: a dictionary with classifiers
758767 :param subclassing: if True, allow for subclasses of the process type
768+ :param outerjoin: if True, wrap the filter with OR id IS NULL
759769
760770 Note: This function handles the case when process_type_string is None.
761771 """
@@ -767,28 +777,41 @@ def _add_process_type_filter(self, tagspec: str, classifiers: List[Classifier],
767777 process_type_filter ['or' ].append (_get_process_type_filter (classifier , subclassing ))
768778
769779 if len (process_type_filter ['or' ]) > 0 :
770- self .add_filter (tagspec , {'process_type' : process_type_filter })
780+ filters : dict = {'process_type' : process_type_filter }
781+ if outerjoin :
782+ filters = {'or' : [filters , {'id' : None }]}
783+ self .add_filter (tagspec , filters )
771784
772785 elif classifiers [0 ].process_type_string is not None :
773786 process_type_filter = _get_process_type_filter (classifiers [0 ], subclassing )
774- self .add_filter (tagspec , {'process_type' : process_type_filter })
787+ filters = {'process_type' : process_type_filter }
788+ if outerjoin :
789+ filters = {'or' : [filters , {'id' : None }]}
790+ self .add_filter (tagspec , filters )
775791
776- def _add_group_type_filter (self , tagspec : str , classifiers : List [Classifier ], subclassing : bool ) -> None :
792+ def _add_group_type_filter (
793+ self , tagspec : str , classifiers : List [Classifier ], subclassing : bool , outerjoin : bool = False
794+ ) -> None :
777795 """Add a filter based on group type.
778796
779797 :param tagspec: The tag, which has to exist already as a key in self._filters
780798 :param classifiers: a dictionary with classifiers
781799 :param subclassing: if True, allow for subclasses of the ormclass
800+ :param outerjoin: if True, wrap the filter with OR id IS NULL
782801 """
783802 if len (classifiers ) > 1 :
784803 # If a list was passed to QueryBuilder.append, this propagates to a list in the classifiers
785- type_string_filter : dict = {'or' : []}
804+ entity_type_filter : dict = {'or' : []}
786805 for classifier in classifiers :
787- type_string_filter ['or' ].append (_get_group_type_filter (classifier , subclassing ))
806+ entity_type_filter ['or' ].append (_get_group_type_filter (classifier , subclassing ))
788807 else :
789- type_string_filter = _get_group_type_filter (classifiers [0 ], subclassing )
808+ entity_type_filter = _get_group_type_filter (classifiers [0 ], subclassing )
809+
810+ filters : dict = {'type_string' : entity_type_filter }
811+ if outerjoin :
812+ filters = {'or' : [filters , {'id' : None }]}
790813
791- self .add_filter (tagspec , { 'type_string' : type_string_filter } )
814+ self .add_filter (tagspec , filters )
792815
793816 def add_projection (self , tag_spec : Union [str , EntityClsType ], projection_spec : ProjectType ) -> None :
794817 r"""Adds a projection
0 commit comments