예제 #1
0
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
예제 #2
0
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
예제 #3
0
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)
예제 #4
0
    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
예제 #5
0
 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)
예제 #6
0
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))
예제 #7
0
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
예제 #8
0
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
예제 #9
0
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
예제 #10
0
 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
예제 #11
0
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
예제 #12
0
 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
예제 #13
0
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")
예제 #14
0
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)
예제 #15
0
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
예제 #16
0
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
예제 #17
0
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
예제 #18
0
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
예제 #19
0
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
예제 #20
0
# 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)