def __handle_primitive_event_or_nested_structure(self, tree_plan_leaf: TreePlanLeafNode, current_operator: PatternStructure, sliding_window: timedelta, parent: Node, consumption_policy: ConsumptionPolicy): """ Constructs a single leaf node or a subtree with nested structure according to the input parameters. """ if isinstance(current_operator, PrimitiveEventStructure): # the current operator is a primitive event - we should simply create a leaf event = current_operator if consumption_policy is not None and \ consumption_policy.should_register_event_type_as_single(False, event.type): parent.register_single_event_type(event.type) return LeafNode(sliding_window, tree_plan_leaf.event_index, event, parent) if isinstance(current_operator, UnaryStructure): # the current operator is a unary operator hiding a nested pattern structure unary_node = self.__create_internal_node_by_operator(current_operator, sliding_window, parent) nested_operator = current_operator.arg child = self.__construct_tree(nested_operator, Tree.__create_nested_structure(nested_operator), Tree.__get_operator_arg_list(nested_operator), sliding_window, unary_node, consumption_policy) unary_node.set_subtree(child) return unary_node # the current operator is a nested binary operator return self.__construct_tree(current_operator, Tree.__create_nested_structure(current_operator), current_operator.args, sliding_window, parent, consumption_policy)
def handle_new_partial_match(self, partial_match_source: Node): """ Internal node's update for a new partial match in one of the subtrees. """ if partial_match_source == self._left_subtree: other_subtree = self._right_subtree elif partial_match_source == self._right_subtree: other_subtree = self._left_subtree else: raise Exception() # should never happen new_partial_match = partial_match_source.get_last_unhandled_partial_match_by_parent( self) new_pm_key = partial_match_source.get_storage_unit().get_key_function() first_event_defs = partial_match_source.get_event_definitions_by_parent( self) other_subtree.clean_expired_partial_matches( new_partial_match.last_timestamp) partial_matches_to_compare = other_subtree.get_partial_matches( new_pm_key(new_partial_match)) second_event_defs = other_subtree.get_event_definitions_by_parent(self) self.clean_expired_partial_matches(new_partial_match.last_timestamp) # given a partial match from one subtree, for each partial match # in the other subtree we check for new partial matches in this node. self._try_create_new_matches(new_partial_match, partial_matches_to_compare, first_event_defs, second_event_defs)
def replace_subtree(self, old_node: Node, new_node: Node): """ Replaces the child of this node provided as old_node with new_node. """ left = self.get_left_subtree() right = self.get_right_subtree() if left == old_node: self.set_subtrees(new_node, right) elif right == old_node: self.set_subtrees(left, new_node) else: raise Exception("old_node must contain one of this node's children") new_node.add_parent(self)
def __find_and_merge_node_into_subtree(self, root: Node, node: Node): """ This method is trying to find node in the subtree of root (or an equivalent node). If such a node is found, it merges the equivalent nodes. """ if root.is_equivalent(node): self.__merge_nodes(root, node) return True elif isinstance(root, BinaryNode): return self.__find_and_merge_node_into_subtree(root.get_left_subtree(), node) or \ self.__find_and_merge_node_into_subtree(root.get_right_subtree(), node) elif isinstance(root, UnaryNode): return self.__find_and_merge_node_into_subtree(root.get_child(), node) return False
def __try_to_share_and_merge_nodes(self, root: Node, node: Node): """ This method is trying to share the node (and its subtree) and the tree of root. If the root and node are not equivalent, trying to share the children of node and root. """ if self.__find_and_merge_node_into_subtree(root, node): return True if isinstance(node, BinaryNode): left_merge = self.__try_to_share_and_merge_nodes(root, node.get_left_subtree()) if left_merge: return True return self.__try_to_share_and_merge_nodes(root, node.get_right_subtree()) if isinstance(node, UnaryNode): return self.__try_to_share_and_merge_nodes(root, node.get_child()) return False
def _validate_new_match(self, events_for_new_match: List[Event]): """ Validates the condition stored in this node on the given set of events. """ if not Node._validate_new_match(self, events_for_new_match): return False return self._condition.eval([e.payload for e in events_for_new_match])
def set_subtree(self, child: Node): """ Sets the child node of this node. """ self._child = child # only the positive child definitions should be applied on this node self._event_defs = child.get_positive_event_definitions()
def __handle_primitive_event(self, tree_plan_leaf: TreePlanLeafNode, primitive_event_structure: PatternStructure, pattern_params: PatternParameters, parent: Node, consumption_policy: ConsumptionPolicy): """ Creates a leaf node for a primitive events. """ # this is a temporary hack used until the procedure is modified to extract event details from tree plan leaves if isinstance(primitive_event_structure, NegationOperator): primitive_event_structure = primitive_event_structure.arg if not isinstance(primitive_event_structure, PrimitiveEventStructure): raise Exception("Illegal operator for a tree leaf: %s" % (primitive_event_structure, )) if consumption_policy is not None and \ consumption_policy.should_register_event_type_as_single(False, primitive_event_structure.type): parent.register_single_event_type(primitive_event_structure.type) return LeafNode(pattern_params, tree_plan_leaf.event_index, primitive_event_structure, parent)
def flush_pending_matches(self, last_timestamp: datetime = None): """ Releases the partial matches in the pending matches buffer. If the timestamp is provided, only releases expired matches. """ if last_timestamp is not None: self.__pending_partial_matches = sorted( self.__pending_partial_matches, key=lambda x: x.first_timestamp) count = find_partial_match_by_timestamp( self.__pending_partial_matches, last_timestamp - self._sliding_window) matches_to_flush = self.__pending_partial_matches[:count] self.__pending_partial_matches = self.__pending_partial_matches[ count:] else: matches_to_flush = self.__pending_partial_matches # since matches_to_flush could be expired, we need to temporarily disable timestamp checks Node._toggle_enable_partial_match_expiration(False) for partial_match in matches_to_flush: super()._add_partial_match(partial_match) Node._toggle_enable_partial_match_expiration(True)
def handle_new_partial_match(self, partial_match_source: Node): """ For positive partial matches, activates the flow of the superclass. For negative partial matches, does nothing for bounded events (as nothing should be done in this case), otherwise checks whether existing positive matches must be invalidated and handles them accordingly. """ if partial_match_source == self._positive_subtree: # a new positive partial match has arrived super().handle_new_partial_match(partial_match_source) return # a new negative partial match has arrived if not self.__is_unbounded: # no unbounded negatives - there is nothing to do return # this partial match contains unbounded negative events first_unbounded_node = self.get_first_unbounded_negative_node() positive_event_defs = first_unbounded_node.get_positive_event_definitions( ) unbounded_negative_partial_match = partial_match_source.get_last_unhandled_partial_match_by_parent( self) negative_event_defs = partial_match_source.get_event_definitions_by_parent( self) matches_to_keep = [] for positive_partial_match in first_unbounded_node.__pending_partial_matches: combined_event_list = self._merge_events_for_new_match( positive_event_defs, negative_event_defs, positive_partial_match.events, unbounded_negative_partial_match.events) if not self._validate_new_match(combined_event_list): # this positive match should still be kept matches_to_keep.append(positive_partial_match) first_unbounded_node.__pending_partial_matches = matches_to_keep
def __merge_nodes(self, node: Node, other: Node): """ Merge two nodes, and update all the required information """ # merges other into node if node.get_sliding_window() < other.get_sliding_window(): node.propagate_sliding_window(other.get_sliding_window()) node.add_pattern_ids(other.get_pattern_ids()) other_parents = other.get_parents() if other_parents is not None: for parent in other_parents: if isinstance(parent, UnaryNode): parent.replace_subtree(node) elif isinstance(parent, BinaryNode): parent.replace_subtree(other, node) else: # other is an output node in it's tree. the new output node of the old_tree is node if not node.is_output_node(): node.set_is_output_node(True) self.__output_nodes.append(node) # other is already in self.__output_nodes, therefore we need to remove it self.__output_nodes.remove(other) other_id = list(other.get_pattern_ids())[0] self.__pattern_to_output_node_dict[other_id] = node
def replace_subtree(self, child: Node): """ Replaces the child of this node with the given node. """ self.set_subtree(child) child.add_parent(self)