Ejemplo n.º 1
0
 def test_list_data(self, node_data_lists, node_keys):
     q, p = unwind_merge_nodes_query(node_data_lists, ("Person", "name"),
                                     keys=node_keys)
     assert q == ("UNWIND $data AS r\n"
                  "MERGE (_:Person {name:r[0]})\n"
                  "SET _ = {name: r[0], `family name`: r[1], age: r[2]}")
     assert p == {"data": node_data_lists}
Ejemplo n.º 2
0
 def test_with_extra_labels(self, node_data_dicts):
     q, p = unwind_merge_nodes_query(node_data_dicts, ("Person", "name"),
                                     labels=["Human", "Employee"])
     assert q == ("UNWIND $data AS r\n"
                  "MERGE (_:Person {name:r['name']})\n"
                  "SET _:Employee:Human\n"
                  "SET _ += r")
     assert p == {"data": node_data_dicts}
Ejemplo n.º 3
0
 def test_list_data_with_preservation(self, node_data_lists, node_keys):
     q, p = unwind_merge_nodes_query(node_data_lists, ("Person", "name"), keys=node_keys,
                                     preserve=["age"])
     assert q == ("UNWIND $data AS r\n"
                  "MERGE (_:Person {name:r[0]})\n"
                  "ON CREATE SET _ += {age: r[2]}\n"
                  "SET _ += {name: r[0], `family name`: r[1]}")
     assert p == {"data": node_data_lists}
Ejemplo n.º 4
0
 def test_with_two_merge_keys(self, node_data_dicts):
     q, p = unwind_merge_nodes_query(node_data_dicts,
                                     ("Person", "name", "family name"))
     assert q == (
         "UNWIND $data AS r\n"
         "MERGE (_:Person {name:r['name'], `family name`:r['family name']})\n"
         "SET _ = r")
     assert p == {"data": node_data_dicts}
Ejemplo n.º 5
0
    def __db_merge__(self, tx, primary_label=None, primary_key=None):
        """ Merge data into a remote :class:`.Graph` from this
        :class:`.Subgraph`.

        :param tx:
        :param primary_label:
        :param primary_key:
        """
        graph = tx.graph

        # Convert nodes into a dictionary of
        #   {(p_label, p_key, frozenset(labels)): [Node, Node, ...]}
        node_dict = {}
        for node in self.nodes:
            if node.graph is None:
                p_label = getattr(node, "__primarylabel__",
                                  None) or primary_label
                p_key = getattr(node, "__primarykey__", None) or primary_key
                key = (p_label, p_key, frozenset(node.labels))
                node_dict.setdefault(key, []).append(node)

        # Convert relationships into a dictionary of
        #   {rel_type: [Rel, Rel, ...]}
        rel_dict = {}
        for relationship in self.relationships:
            if relationship.graph is None:
                key = type(relationship).__name__
                rel_dict.setdefault(key, []).append(relationship)

        for (pl, pk, labels), nodes in node_dict.items():
            if pl is None or pk is None:
                raise ValueError(
                    "Primary label and primary key are required for MERGE operation"
                )
            pq = unwind_merge_nodes_query(map(dict, nodes), (pl, pk), labels)
            pq = cypher_join(pq, "RETURN id(_)")
            identities = [record[0] for record in tx.run(*pq)]
            if len(identities) > len(nodes):
                raise UniquenessError(
                    "Found %d matching nodes for primary label %r and primary "
                    "key %r with labels %r but merging requires no more than "
                    "one" % (len(identities), pl, pk, set(labels)))
            for i, identity in enumerate(identities):
                node = nodes[i]
                node.graph = graph
                node.identity = identity
                node._remote_labels = labels
        for r_type, relationships in rel_dict.items():
            data = map(
                lambda r:
                [r.start_node.identity,
                 dict(r), r.end_node.identity], relationships)
            pq = unwind_merge_relationships_query(data, r_type)
            pq = cypher_join(pq, "RETURN id(_)")
            for i, record in enumerate(tx.run(*pq)):
                relationship = relationships[i]
                relationship.graph = graph
                relationship.identity = record[0]
Ejemplo n.º 6
0
def merge_nodes(tx, data, merge_key, labels=None, keys=None):
    """ Merge nodes from an iterable sequence of raw node data.

    In a similar way to :meth:`.create_nodes`, the raw node `data` can
    be supplied as either lists (with `keys`) or dictionaries. This
    method however uses an ``UNWIND ... MERGE`` construct in the
    underlying Cypher query to create or update nodes depending
    on what already exists.

    The merge is performed on the basis of the label and keys
    represented by the `merge_key`, updating a node if that combination
    is already present in the graph, and creating a new node otherwise.
    As with :meth:`.create_nodes`, extra `labels` may also be
    specified; these will be applied to all nodes, pre-existing or new.
    The label included in the `merge_key` does not need to be
    separately included here.

    The example code below shows a simple merge based on a `Person`
    label and a `name` property:

        >>> from py2neo import Graph
        >>> from py2neo.data.operations import merge_nodes
        >>> g = Graph()
        >>> keys = ["name", "age"]
        >>> data = [
            ["Alice", 33],
            ["Bob", 44],
            ["Carol", 55],
            ["Carol", 66],
            ["Alice", 77],
        ]
        >>> merge_nodes(g.auto(), data, ("Person", "name"), keys=keys)
        >>> g.nodes.match("Person").count()
        3

    :param tx: :class:`.Transaction` in which to carry out this
        operation
    :param data: node data supplied as a list of lists (if `keys` are
        provided) or a list of dictionaries (if `keys` is :const:`None`)
    :param merge_key: tuple of (label, key1, key2...) on which to merge
    :param labels: additional labels to apply to the merged nodes
    :param keys: an optional set of keys for the supplied `data`
    """
    list(tx.run(*unwind_merge_nodes_query(data, merge_key, labels, keys)))
Ejemplo n.º 7
0
def merge_subgraph(tx, subgraph, p_label, p_key):
    """ Merge data into a remote :class:`.Graph` from a local
    :class:`.Subgraph`.

    :param tx:
    :param subgraph:
    :param p_label:
    :param p_key:
    :return:
    """
    graph = tx.graph
    for (pl, pk, labels), nodes in _node_merge_dict(
            p_label, p_key,
        (n for n in subgraph.nodes if n.graph is None)).items():
        if pl is None or pk is None:
            raise ValueError(
                "Primary label and primary key are required for MERGE operation"
            )
        pq = unwind_merge_nodes_query(map(dict, nodes), (pl, pk), labels)
        pq = cypher_join(pq, "RETURN id(_)")
        identities = [record[0] for record in tx.run(*pq)]
        if len(identities) > len(nodes):
            raise UniquenessError(
                "Found %d matching nodes for primary label %r and primary "
                "key %r with labels %r but merging requires no more than "
                "one" % (len(identities), pl, pk, set(labels)))
        for i, identity in enumerate(identities):
            node = nodes[i]
            node.graph = graph
            node.identity = identity
            node._remote_labels = labels
    for r_type, relationships in _rel_create_dict(
            r for r in subgraph.relationships if r.graph is None).items():
        data = map(
            lambda r: [r.start_node.identity,
                       dict(r), r.end_node.identity], relationships)
        pq = unwind_merge_relationships_query(data, r_type)
        pq = cypher_join(pq, "RETURN id(_)")
        for i, record in enumerate(tx.run(*pq)):
            relationship = relationships[i]
            relationship.graph = graph
            relationship.identity = record[0]
Ejemplo n.º 8
0
 def test_with_no_merge_keys(self, node_data_dicts, merge_key):
     q, p = unwind_merge_nodes_query(node_data_dicts, "Person")
     assert q == ("UNWIND $data AS r\n"
                  "MERGE (_:Person)\n"
                  "SET _ += r")
     assert p == {"data": node_data_dicts}
Ejemplo n.º 9
0
 def test_dict_data(self, node_data_dicts):
     q, p = unwind_merge_nodes_query(node_data_dicts, ("Person", "name"))
     assert q == ("UNWIND $data AS r\n"
                  "MERGE (_:Person {name:r['name']})\n"
                  "SET _ += r")
     assert p == {"data": node_data_dicts}
Ejemplo n.º 10
0
def merge_nodes(tx, data, merge_key, labels=None, keys=None):
    """ Merge nodes from an iterable sequence of raw node data.

    In a similar way to :func:`.create_nodes`, the raw node `data` can
    be supplied as either lists (with field `keys`) or as dictionaries.
    This method however uses an ``UNWIND ... MERGE`` construct in the
    underlying Cypher query to create or update nodes depending
    on what already exists.

    The merge is performed on the basis of the label and keys
    represented by the `merge_key`, updating a node if that combination
    is already present in the graph, and creating a new node otherwise.
    The value of this argument may take one of several forms and is
    used internally to construct an appropriate ``MERGE`` pattern. The
    table below gives examples of the values permitted, and how each is
    interpreted, using ``x`` as the input value from the source data.

    .. table::
        :widths: 40 60

        =================================================  ===========================================================
        Argument                                           ``MERGE`` Clause
        =================================================  ===========================================================
        ``("Person", "name")``                             ``MERGE (a:Person {name:x})``
        ``("Person", "name", "family name")``              ``MERGE (a:Person {name:x[0], `family name`:x[1]})``
        ``(("Person", "Female"), "name")``                 ``MERGE (a:Female:Person {name:x})``
        ``(("Person", "Female"), "name", "family name")``  ``MERGE (a:Female:Person {name:x[0], `family name`:x[1]})``
        =================================================  ===========================================================

    As with :func:`.create_nodes`, extra `labels` may also be
    specified; these will be applied to all nodes, pre-existing or new.
    The label included in the `merge_key` does not need to be
    separately included here.

    The example code below shows a simple merge based on a `Person`
    label and a `name` property:

        >>> from py2neo import Graph
        >>> from py2neo.bulk import merge_nodes
        >>> g = Graph()
        >>> keys = ["name", "age"]
        >>> data = [
            ["Alice", 33],
            ["Bob", 44],
            ["Carol", 55],
            ["Carol", 66],
            ["Alice", 77],
        ]
        >>> merge_nodes(g.auto(), data, ("Person", "name"), keys=keys)
        >>> g.nodes.match("Person").count()
        3

    :param tx: :class:`.Transaction` in which to carry out this
        operation
    :param data: node data supplied as a list of lists (if `keys` are
        provided) or a list of dictionaries (if `keys` is :const:`None`)
    :param merge_key: tuple of (label, key1, key2...) on which to merge
    :param labels: additional labels to apply to the merged nodes
    :param keys: optional set of keys for the supplied `data` (if
        supplied as value lists)
    """
    list(tx.run(*unwind_merge_nodes_query(data, merge_key, labels, keys)))