Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

func merge Parameter does not correspond #914

Open
lifetruth-liu opened this issue Aug 18, 2021 · 2 comments
Open

func merge Parameter does not correspond #914

lifetruth-liu opened this issue Aug 18, 2021 · 2 comments
Labels

Comments

@lifetruth-liu
Copy link

in database.py

class Graph(object):
    def merge(self, subgraph, label=None, *property_keys):
        ...
        self.update(lambda tx: tx.merge(subgraph, label, *property_keys))

class Transaction(object):
    def merge(self, subgraph, primary_label=None, primary_key=None):
        pass
@technige
Copy link
Collaborator

Note to self: there are two possible ways to fix this...

  1. Extend Transaction.merge to permit multiple keys
  2. Limit Graph.merge to only permit a single key

The choice will be dependent on the underlying functionality that is available. If Neo4j supports multiple keys in all contexts here, then option 1 is fine. If not, option 2 will have to suffice.

@smartwang
Copy link

monkey patch:

from py2neo import Graph, Node, Relationship, Transaction, Subgraph

from py2neo.cypher.queries import (
    unwind_merge_nodes_query,
    unwind_merge_relationships_query,
)
from py2neo.cypher import cypher_join
class UniquenessError(Exception):
    """ Raised when a condition assumed to be unique is determined
    non-unique.
    """

def __db_merge__(self, tx, primary_label=None, *primary_key):
    """ 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 not self._is_bound(node, graph):
            # Determine primary label
            if node.__primarylabel__ is not None:
                p_label = node.__primarylabel__
            elif node.__model__ is not None:
                p_label = node.__model__.__primarylabel__ or primary_label
            else:
                p_label = primary_label
            # Determine primary key
            if node.__primarykey__ is not None:
                p_key = node.__primarykey__
            elif node.__model__ is not None:
                p_key = node.__model__.__primarykey__ or primary_key
            else:
                p_key = primary_key
            # Add node to the node dictionary
            key = (p_label, frozenset(node.labels), *p_key)
            node_dict.setdefault(key, []).append(node)

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

    for (pl, labels, *pk), 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]

def merge(self, subgraph, primary_label=None, *primary_key):
    try:
        merge = subgraph.__db_merge__
    except AttributeError:
        raise TypeError("No method defined to merge object %r" % subgraph)
    else:
        merge(self, primary_label, *primary_key)


Subgraph.__db_merge__ = __db_merge__
Transaction.merge = merge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants