def new_atoms(cycle: List[structures.Atom], q: structures.ConjunctiveQuery, rewriting_index: int, renaming: Dict[structures.AtomValue, structures.AtomValue]) -> \ Tuple[Tuple[structures.Atom, structures.FunctionalDependencySet, List[bool], bool], List[Tuple[structures.Atom, structures.FunctionalDependencySet, List[bool], bool]]]: """ Generates the atoms needed for the reduction :param cycle: Cycle being reduced :param q: A ConjunctiveQuery :param rewriting_index: Index used to enumerate the Datalog rules :param renaming: Necessary renaming :return: Atoms needed for the reduction """ _, x_0, _ = q.decompose_atom(cycle[0]) x_0_ren = algorithms.apply_renaming_to_atom_values(x_0, renaming) n_atoms = [] t_content = [] t_released = set() for atom in cycle: _, x, y = q.decompose_atom(atom) n = structures.Atom("N_" + atom.name, x + x_0_ren, atom.released) t_released = t_released.union(atom.released) fd = structures.FunctionalDependencySet() for var in x_0_ren: fd.add(structures.FunctionalDependency(x, var)) is_key = [True] * len(x) + [False] * len(x_0_ren) n_atoms.append((n, fd, is_key, True)) t_content += x t_content += y t = structures.Atom("T_" + str(rewriting_index), x_0_ren + t_content, t_released) fd = structures.FunctionalDependencySet() for var in t_content: fd.add(structures.FunctionalDependency(x_0_ren, var)) is_key = [True] * len(x_0_ren) + [False] * len(t_content) return (t, fd, is_key, False), n_atoms
def saturate(q: structures.ConjunctiveQuery, bad_fd: FrozenSet[structures.FunctionalDependency]) \ -> Tuple[structures.ConjunctiveQuery, List[structures.DatalogQuery]]: """ Saturates a non saturated query. :param q: A non saturated ConjunctiveQuery. :param bad_fd: Set of internal FD that makes q non saturated :return: The saturated query and a set of Datalog rules. """ n_index = 0 atoms = q.get_atoms() output = [] new_q = q for fd in bad_fd: content = list(fd.left) + [fd.right] n_atom = structures.Atom("N_" + str(n_index), content) fd_set = structures.FunctionalDependencySet() fd_set.add(fd) new_q = q.add_atom(n_atom, fd_set, [True] * len(fd.left) + [False], True) valuation = algorithms.generate_renaming(1, list(new_q.get_all_variables()))[0] n_rule = structures.DatalogQuery(n_atom) for atom in atoms: n_rule.add_atom(atom) bad_atom = structures.Atom("BadFact_" + str(n_index), content) n_rule.add_atom(bad_atom, True) bad_rule = structures.DatalogQuery(bad_atom) for atom in atoms: bad_rule.add_atom(atom) bad_rule.add_atom(algorithms.apply_renaming_to_atom(atom, valuation)) for var in fd.left: bad_rule.add_atom(structures.EqualityAtom(var, valuation[var])) bad_rule.add_atom(structures.EqualityAtom(fd.right, valuation[fd.right], True)) output += [n_rule, bad_rule] return new_q, output
def sequential_proof_rec(fd: FunctionalDependency, acc: Set[AtomValue], q: ConjunctiveQuery, current_sp: List[Atom], current_res: List[SequentialProof]) -> None: """ Recursive function used to compute sequential proofs for a given FD X -> z :param fd: A FD of the form X -> z :param acc: A set of Variables containing the variables implied by X with the current sequential proof :param q: A ConjunctiveQuery :param current_sp: Current sequential proof :param current_res: List that will contain all the sequential proofs for X -> z """ if fd.right in acc: sequential_proof = SequentialProof(fd, current_sp) to_remove = [] for sp in current_res: if sequential_proof.is_subset_of(sp): to_remove.append(sp) elif sp.is_subset_of(sequential_proof): return None for sp in to_remove: current_res.remove(sp) current_res.append(sequential_proof) else: for atom in [atom for atom in q.get_atoms() if atom not in current_sp]: if set(q.get_key_vars(atom)).issubset(acc): sequential_proof_rec(fd, acc.union(set(atom.variables())), q, current_sp + [atom], current_res)
def merge(self, other): """ Merges two PathQueryTest """ new_q = PathQueryTest(0) new_content = {**self.content, **other.content} new_q.top_sort = self.top_sort + other.top_sort ConjunctiveQuery.__init__(new_q, new_content) return new_q
def __init__(self, n, rel_base="E_", var_base="X_"): content = {} self.n = n for i in range(1, n + 1): x = AtomValue(var_base + str(i - 1), True) y = AtomValue(var_base + str(i), True) fd = FunctionalDependencySet() fd.add(FunctionalDependency(frozenset([x]), y)) atom = Atom(rel_base + str(i), [x, y]) content[atom] = (fd, [True, False], False) self.top_sort = [rel_base + str(i) for i in range(1, self.n + 1)] ConjunctiveQuery.__init__(self, content)
def atom_plus(atom: Atom, q: ConjunctiveQuery) -> Set[AtomValue]: """ Computes the plus q set of an atom (Read report for more information) :param atom: Atom whose plus will be computed :param q: A ConjunctiveQuery :return: The plus q set of variables of atom """ if q.is_atom_consistent(atom): return transitive_closure(set(q.get_key_vars(atom)), q.get_all_fd()) else: return transitive_closure(set(q.get_key_vars(atom)), q.get_all_fd(atom))
def fd_is_internal(fd: FunctionalDependency, q: ConjunctiveQuery) -> bool: """ Checks if a FD is internal :param fd: A FD :param q: A ConjunctiveQuery :return: True if fd is internal, otherwise returns False """ in_atom = False atoms = q.get_atoms() for atom in atoms: if fd.left.issubset(set(atom.variables())): in_atom = True if not in_atom: return False sps = sequential_proofs(fd, q) variables = fd.left.union({fd.right}) for sp in sps: valid = True for atom in sp.steps: for var in variables: if atom_attacks_variables(atom, var, q): valid = False if valid: return True return False
def gen_m_graph(q: ConjunctiveQuery) -> nx.DiGraph: """ Computes the M-graph of a given ConjunctiveQuery q. :param q: A ConjunctiveQuery :return: Generated M-Graph """ atoms = q.get_atoms() g = nx.DiGraph() for atom1 in atoms: g.add_node(atom1) closure = transitive_closure(set(atom1.variables()), q.get_consistent_fd()) for atom2 in [atom for atom in atoms if atom != atom1]: if set(q.get_key_vars(atom2)).issubset(closure): g.add_edge(atom1, atom2) return g
def gen_attack_graph(q: ConjunctiveQuery) -> nx.DiGraph: """ Computes the attack graph of a given ConjunctiveQuery q. :param q: A ConjunctiveQuery :return: Generated Attack Graph """ g = nx.DiGraph() atoms = q.get_atoms() for atom in atoms: g.add_node(atom) for atom in atoms: plus = atom_plus(atom, q) reachable = {atom} candidates = atoms - {atom} size = -1 while len(reachable) != size: size = len(reachable) new_reachable = set() for atom1 in reachable: for atom2 in candidates: intersection = set(atom1.variables()).intersection( set(atom2.variables())) without_plus = intersection - plus if without_plus: new_reachable.add(atom2) reachable = reachable.union(new_reachable) candidates = candidates - new_reachable for other in reachable - {atom}: g.add_edge(atom, other) return g
def make_safe(self, q: ConjunctiveQuery): """ Adds atoms to make safe the rule :param q: A ConjunctiveQuery """ i = 0 atoms = list(self.atoms) for var in self.head.all_variables(): k = 0 while k < len(atoms) and (not isinstance(atoms[k], Atom) or self.neg[atoms[k]] or var not in atoms[k].content): k += 1 if k == len(atoms): for atom in q.get_atoms(): content = atom.content if var in content: new_vars = generate_new_variables( "F", len(content) - 1, i) new_content = [] j = 0 for value in content: if value != var: new_content.append(new_vars[j]) j += 1 else: new_content.append(var) new_atom = Atom(atom.name, new_content) new_atom.released = set(new_content).intersection( self.head.content) self.add_atom(new_atom) break i += 1
def all_cycles_weak(attack_graph: nx.DiGraph, q: ConjunctiveQuery) -> bool: """ Returns True if the given Attack Graph contains no strong cycle :param attack_graph: An Attack Graph :param q: A ConjunctiveQuery :return: True if attack_graph contains no strong cycle, False if not """ cycles = nx.algorithms.simple_cycles(attack_graph) for cycle in cycles: full_cycle = cycle + [cycle[0]] for i in range(0, len(cycle)): atom1 = full_cycle[i] atom2 = full_cycle[i + 1] for var in q.get_key_vars(atom2): if var not in transitive_closure(set(q.get_key_vars(atom1)), q.get_all_fd()): return False return True
def __init__(self, q: ConjunctiveQuery, atom: Atom, index: int, is_last: bool, done: Set[Atom]): self.q = q self.atom = atom self.index = index self.v, self.x, self.y = q.decompose_atom(atom) _, self.vars_x, self.vars_y = q.decompose_atom(atom, True) self.vars_z, self.c = generate_z_and_c(self.x, self.y, self.vars_y, index) self.has_c = len(self.c) > 0 self.is_last = is_last self.frozen = q.free_vars self.new_frozen = [] for var in q.free_vars: self.new_frozen.append(var) for var in self.v: if var not in self.new_frozen: self.new_frozen.append(var) self.done = done
def parse_query(string): pattern = re.compile("\[[A-Za-z_,0-9]*\]:[A-Za-z_,\(\)\[\]\*0-9]*$") if pattern.match(string): try: free_var_body, query_body = string.split(":") free_var_body = free_var_body[1:-1] free_vars = parse_atoms_values(free_var_body) q = ConjunctiveQuery() for atom, fd_set, is_key, is_consistent in parse_atoms(query_body): q = q.add_atom(atom, fd_set, is_key, is_consistent) for value in free_vars: if value.var: q = q.release_variable(value) return q except MalformedQuery: raise else: raise MalformedQuery(string, "ConjunctiveQuery")
def atom_attacks_variables(atom: Atom, var: AtomValue, q: ConjunctiveQuery) -> bool: """ Returns True if the given atom attacks the given Variable :param atom: An Atom :param var: A Variable :param q: A ConjunctiveQuery :return: True if atom attacks var, else returns False """ n = Atom("N", [var]) q_new = q.add_atom(n, FunctionalDependencySet(), [True], False) g = gen_attack_graph(q_new) return g.has_edge(atom, n)
def is_self_join_free(q: ConjunctiveQuery) -> bool: """ Returns True is given bcq q is a sjfbcq :param q: A conjunctive query :return: True if q is a sjfbcq, otherwise returns False """ atoms = list(q.get_atoms()) for i in range(len(atoms)): atom = atoms[i] for other in atoms[:i] + atoms[i + 1:]: if atom.name == other.name: return False return True
def find_bad_internal_fd( q: ConjunctiveQuery) -> FrozenSet[FunctionalDependency]: """ Given a non saturated ConjunctiveQuery, returns the FunctionalDependencies culprit of it's non saturation. :param q: A ConjunctiveQuery. :return: A FrozenSet containing the FunctionalDependencies culprit of the non saturation of q. """ res = frozenset() possible_starts = [] possible_ends = [] for fd in q.get_all_fd().set: if fd.left not in possible_starts: possible_starts.append(fd.left) if fd.right not in possible_ends: possible_ends.append(fd.right) for left in possible_starts: for right in possible_ends: test_fd = FunctionalDependency(left, right) if fd_is_internal(test_fd, q): if right not in transitive_closure(set(left), q.get_consistent_fd()): res = res.union(frozenset([test_fd])) return res
def reduce_cycle(cycle: List[structures.Atom], q: structures.ConjunctiveQuery, rewriting_index: int) -> Tuple[structures.ConjunctiveQuery, List[structures.DatalogQuery]]: """ Reduces a cycle in the M-graph corresponding to an initial strong component in the attack graph of q. :param cycle: Cycle to be reduced :param q: A ConjunctiveQuery :param rewriting_index: Index used to enumerate the Datalog rules :return: A list of Datalog rules and a ConjunctiveQuery that corresponds to q\{atom} U {T} U {Nc} (Just as described in the report) """ rules = [] k = len(cycle) renamings = algorithms.generate_renaming(2 * k + 2, list(q.get_all_variables())) rules += [templates.EqQuery(atom, q) for atom in cycle] rules += [templates.NeqQuery(atom, q, renamings[0]) for atom in cycle] rules += garbage_set_rules(cycle, q, rewriting_index, renamings) rules += new_atoms_rules(cycle, q, rewriting_index, renamings) t, n_atoms = new_atoms(cycle, q, rewriting_index, renamings[0]) new_q = q.add_atom(*t) for n_atom in n_atoms: new_q = new_q.add_atom(*n_atom) for atom in cycle: new_q = new_q.remove_atom(atom) return new_q, rules
def rewrite_fo(q: structures.ConjunctiveQuery, atom: structures.Atom, is_last: bool, done: Set[structures.Atom], index) -> Tuple[structures.ConjunctiveQuery, List[structures.DatalogQuery]]: """ Rewrites CERTAINTY(q) in function of a given atom when CERTAINTY(q) is in FO. :param q: A sjfBCQ :param atom: Atom being rewrited :param is_last: True if q\{atom} has already been treated :param done: A set of the Atom that have already been treated by the rewriting process :param index: Index used to enumerate the Datalog rules :return: A list of Datalog rules and a ConjunctiveQuery that corresponds to q\{atom} where the variables in atoms are constants. """ rules = [] data = templates.RewritingData(q, atom, index, is_last, done) rules.append(templates.RewriteAtomQuery(data)) if data.has_c or not data.is_last: rules.append(templates.BadBlockQuery(data)) if data.has_c: rules.append(templates.GoodFactQuery(data)) new_q = q.remove_atom(atom) for var in data.v: new_q = new_q.release_variable(var) return new_q, rules
def new_atoms_rules(cycle: List[structures.Atom], q: structures.ConjunctiveQuery, rewriting_index : int, renamings: List[Dict[structures.AtomValue, structures.AtomValue]]) -> List[structures.DatalogQuery]: """ Generated the rules defining the new atoms added by the reduction :param cycle: Cycle being reduced :param q: A ConjunctiveQuery :param rewriting_index: Index used to enumerate the Datalog rules :param renamings: Necessary renamings :return: Rules defining the new atoms """ _, x_0, _ = q.decompose_atom(cycle[0]) rules = [] rules += [templates.KeepQuery(atom, q) for atom in cycle] rules += [templates.LinkQuery(atom, cycle, q, rewriting_index, renamings[0]) for atom in cycle] rules += [templates.TransBaseQuery(cycle, q, rewriting_index, renamings[0])] rules += [templates.TransRecQuery(cycle, q, rewriting_index, renamings)] if len(x_0) > 1: rules += [templates.LowerCompositeQuery(cycle, q, i, rewriting_index, renamings) for i in range(len(x_0))] else: rules += [templates.LowerSingleQuery(cycle, q, rewriting_index, renamings)] rules += [templates.IdentifiedByQuery(cycle, q, rewriting_index, renamings[0])] rules += [templates.NQuery(atom, cycle, q, rewriting_index, renamings[0]) for atom in cycle] rules += [templates.TQuery(cycle, q, rewriting_index, renamings[0])] return rules
# Initialize atoms # First parameter : Name of the relation # Second parameter : List of AtomValue in the atom atom_r = Atom("R", [x, a, y]) atom_s = Atom("S", [y, z]) # Initialize functional dependencies # First parameter : Left side of the FD (List of variables) # Second parameter : Right side of the FD (must be a single variable) fd1 = FunctionalDependencySet() fd1.add(FunctionalDependency([x], y)) fd2 = FunctionalDependencySet() fd2.add(FunctionalDependency([y], z)) # Initialize the conjunctive query q = ConjunctiveQuery() # Add atoms to q # First parameter : The atom to be added # Second parameter : The set of FD (must be a frozenset as in the example) # Third parameter : A List of booleans describing the "key positions" of the atom # Fourth Parameter : A boolean that must be True if the atom is consistent, False if not q = q.add_atom(atom_r, fd1, [True, True, False], False) q = q.add_atom(atom_s, fd2, [True, False], False) # Choose free variables (creates a new instance) q = q.release_variable(x) q = q.release_variable(y) # Launch rewriting program = rewrite(q)