def __init__(self, graph): self.graph = graph self.supports_node_labels = self.graph.supports_node_labels self.entities = [] self.start_or_match_clause = StartOrMatch(self.graph) self.delete_rels_clause = [] self.delete_nodes_clause = [] self.parameters = {}
def __init__(self, graph): self.graph = graph self.supports_node_labels = self.graph.supports_node_labels self.entities = [] self.names = [] self.start_or_match_clause = StartOrMatch(self.graph) self.create_clause = [] self.create_unique_clause = [] self.return_clause = [] self.parameters = {}
def __init__(self, graph): self.graph = graph if self.graph.supports_optional_match: self.__delete_query = ( StartOrMatch(self.graph).node("a", "{A}").string + "OPTIONAL MATCH a-[r]-b " "DELETE r, a") else: self.__delete_query = ( StartOrMatch(self.graph).node("a", "{A}").string + "MATCH a-[r?]-b " "DELETE r, a")
def save(self, subj, node=None): """ Save an object to a database node. :param subj: the object to save :param node: the database node to save to (if omitted, will re-save to same node as previous save) """ if node is not None: subj.__node__ = node # naively copy properties from object to node props = {} for key, value in subj.__dict__.items(): if not key.startswith("_"): props[key] = value if hasattr(subj, "__node__"): subj.__node__.set_properties(props) self.graph.cypher.run( StartOrMatch(self.graph).node("a", "{a}").string + "MATCH (a)-[r]->(b) DELETE r", {"a": subj.__node__}) else: subj.__node__, = self.graph.create(props) # write rels if hasattr(subj, "__rel__"): batch = WriteBatch(self.graph) for rel_type, rels in subj.__rel__.items(): for rel_props, endpoint in rels: end_node = self._get_node(endpoint) if end_node not in self.graph: raise ValueError(end_node) batch.create( (subj.__node__, rel_type, end_node, rel_props)) batch.run() return subj
def _node_delete_related(self): from py2neo.cypher.util import StartOrMatch graph = self.graph foreach_symbol = "|" if graph.supports_foreach_pipe else ":" statement = ( StartOrMatch(graph).node("a", "{a}").string + "MATCH (a)-[rels*0..]-(z) " "FOREACH(r IN rels %s DELETE r) " "DELETE a, z" % foreach_symbol ) graph.cypher.post(statement, {"a": self._id})
def _relationship_update_properties(self, properties): if self.bound: from py2neo.cypher.lang import cypher_escape from py2neo.cypher.util import StartOrMatch graph = self.graph query, params = [StartOrMatch(graph).node("a", "{A}").string], {"A": self._id} for i, (key, value) in enumerate(properties.items()): value_tag = "V" + str(i) query.append("SET a.%s={" + value_tag + "}" % cypher_escape(key)) params[value_tag] = value query.append("RETURN a") rel = graph.cypher.execute_one("\n".join(query), params) self._properties = rel.__metadata__["data"] else: self._properties.update(properties)
def _path__create_query(self, graph, unique): from py2neo.cypher.util import StartOrMatch start_or_match_clause = StartOrMatch(graph) path, values, params = [], [], {} def append_node(i, node): if node is None: path.append("(n{0})".format(i)) values.append("n{0}".format(i)) elif node.bound: path.append("(n{0})".format(i)) start_or_match_clause.node("n%s" % i, "{i%s}" % i) params["i{0}".format(i)] = node._id values.append("n{0}".format(i)) else: path.append("(n{0} {{p{0}}})".format(i)) params["p{0}".format(i)] = node.properties values.append("n{0}".format(i)) def append_rel(i, rel): if rel.properties: path.append("-[r{0}:`{1}` {{q{0}}}]->".format(i, rel.type)) params["q{0}".format(i)] = rel.properties values.append("r{0}".format(i)) else: path.append("-[r{0}:`{1}`]->".format(i, rel.type)) values.append("r{0}".format(i)) append_node(0, self._Path__nodes[0]) for i, rel in enumerate(self._Path__rels): append_rel(i, rel) append_node(i + 1, self._Path__nodes[i + 1]) clauses = [] if len(start_or_match_clause) > 0: clauses.append(start_or_match_clause.string) if unique: clauses.append("CREATE UNIQUE p={0}".format("".join(path))) else: clauses.append("CREATE p={0}".format("".join(path))) clauses.append("RETURN p") query = "\n".join(clauses) return query, params
def _create_query(graph, p, unique=False): start_or_match_clause = StartOrMatch(graph) path, values, params = [], [], {} def append_node(i, node): if node is None: path.append("(n{0})".format(i)) values.append("n{0}".format(i)) elif node.bound: path.append("(n{0})".format(i)) start_or_match_clause.node("n%s" % i, "{i%s}" % i) params["i{0}".format(i)] = node._id values.append("n{0}".format(i)) else: path.append("(n{0} {{p{0}}})".format(i)) params["p{0}".format(i)] = node.properties values.append("n{0}".format(i)) def append_rel(i, rel): if rel.properties: path.append("-[r{0}:`{1}` {{q{0}}}]->".format(i, rel.type)) params["q{0}".format(i)] = PropertySet(rel.properties) values.append("r{0}".format(i)) else: path.append("-[r{0}:`{1}`]->".format(i, rel.type)) values.append("r{0}".format(i)) append_node(0, p.nodes[0]) for i, rel in enumerate(p.rels): append_rel(i, rel) append_node(i + 1, p.nodes[i + 1]) clauses = [] if start_or_match_clause: clauses.append(start_or_match_clause.string) if unique: clauses.append("CREATE UNIQUE p={0}".format("".join(path))) else: clauses.append("CREATE p={0}".format("".join(path))) clauses.append("RETURN p") query = "\n".join(clauses) return query, params
class DeleteStatement(object): """ Builder for a Cypher DELETE statement. """ #: The graph against which this statement is to be executed. graph = None #: The parameters to inject into this statement. parameters = None def __init__(self, graph): self.graph = graph self.supports_node_labels = self.graph.supports_node_labels self.entities = [] self.start_or_match_clause = StartOrMatch(self.graph) self.delete_rels_clause = [] self.delete_nodes_clause = [] self.parameters = {} def __repr__(self): return self.string def __str__(self): return xstr(self.__unicode__()) def __unicode__(self): return self.string def __contains__(self, entity): return any(e is entity for e in self.entities) @property def string(self): """ The full Cypher statement as a string. """ clauses = [] if self.start_or_match_clause: clauses.append(self.start_or_match_clause.string.rstrip()) if self.delete_rels_clause: clauses.append("DELETE " + ",".join(self.delete_rels_clause)) if self.delete_nodes_clause: clauses.append("DELETE " + ",".join(self.delete_nodes_clause)) return "\n".join(clauses) def post(self): return self.graph.cypher.post(self.string, self.parameters) def execute(self): """ Execute this statement. """ if self.string: self.post().close() def delete(self, entity): """ Append an entity to the DELETE clause of this statement. :arg entity: The entity to delete. """ entity = Graph.cast(entity) index = len(self.entities) name = _(index) if isinstance(entity, Node): self._delete_node(entity, name) elif isinstance(entity, Relationship): self._delete_relationship(entity, name) elif isinstance(entity, Path): self._delete_path(entity, name) self.entities.append(entity) def _delete_node(self, node, name): if node.bound: self.start_or_match_clause.node(name, "{%s}" % name) self.delete_nodes_clause.append(name) self.parameters[name] = node._id def _delete_relationship(self, relationship, name): if relationship.bound: self.start_or_match_clause.relationship(name, "{%s}" % name) self.delete_rels_clause.append(name) self.parameters[name] = relationship._id def _delete_path(self, path, name): for i, rel in enumerate(path.relationships): self._delete_relationship(rel, name + "r" + ustr(i)) for i, node in enumerate(path.nodes): self._delete_node(node, name + "n" + ustr(i))
def refresh(self): from py2neo.cypher.util import StartOrMatch graph = self.graph statement = StartOrMatch(graph).node("a", "{a}").string + "RETURN a" graph.cypher.execute(statement, {"a": self}) self._Node__stale.clear()
class CreateStatement(object): """ Builder for a Cypher CREATE/CREATE UNIQUE statement. """ #: The graph against which this statement is to be executed. graph = None #: The parameters to inject into this statement. parameters = None def __init__(self, graph): self.graph = graph self.supports_node_labels = self.graph.supports_node_labels self.entities = [] self.names = [] self.start_or_match_clause = StartOrMatch(self.graph) self.create_clause = [] self.create_unique_clause = [] self.return_clause = [] self.parameters = {} def __repr__(self): return self.string def __str__(self): return xstr(self.__unicode__()) def __unicode__(self): return self.string def __contains__(self, entity): return any(e is entity for e in self.entities) @property def string(self): """ The full Cypher statement as a string. """ clauses = [self.start_or_match_clause.string] if self.create_clause: clauses.append("CREATE " + ",".join(self.create_clause)) if self.create_unique_clause: clauses.append("CREATE UNIQUE " + ",".join(self.create_unique_clause)) if self.return_clause: clauses.append("RETURN " + ",".join(self.return_clause)) return "\n".join(clauses).strip() def post(self): return self.graph.cypher.post(self.string, self.parameters) def execute(self): """ Execute this statement. :return: A tuple of created entities. """ if not self.entities: return () raw = self.post().content columns = raw["columns"] data = raw["data"] dehydrated = dict(zip(columns, data[0])) for i, entity in enumerate(self.entities): node_names, rel_names = self.names[i] if isinstance(entity, Node): metadata = dehydrated[node_names[0]] entity.bind(metadata["self"], metadata) elif isinstance(entity, Relationship): metadata = dehydrated[rel_names[0]] entity.bind(metadata["self"], metadata) elif isinstance(entity, Path): for j, node in enumerate(entity.nodes): metadata = dehydrated[node_names[j]] node.bind(metadata["self"], metadata) for j, rel in enumerate(entity.rels): metadata = dehydrated[rel_names[j]] rel.bind(metadata["self"], metadata) return tuple(self.entities) def create(self, entity): """ Append an entity to the CREATE clause of this statement. :arg entity: The entity to create. """ entity = Graph.cast(entity) index = len(self.entities) name = _(index) if isinstance(entity, Node): self.names.append(self._create_node(entity, name)) elif isinstance(entity, Path): self.names.append(self._create_path(entity, name, unique=False)) else: raise TypeError("Cannot create entity of type %s" % type(entity).__name__) self.entities.append(entity) def create_unique(self, entity): """ Append an entity to the CREATE UNIQUE clause of this statement. :arg entity: The entity to add. """ entity = Graph.cast(entity) index = len(self.entities) name = _(index) if isinstance(entity, Path): if len(entity) == 0: raise ValueError("Cannot create unique path with zero length") if not any(node.bound or node in self for node in entity.nodes): raise ValueError("At least one node must be bound to create a unique path") self.names.append(self._create_path(entity, name, unique=True)) else: raise TypeError("Cannot create unique entity of type %s" % type(entity).__name__) self.entities.append(entity) def _node_pattern(self, node, name, full): template = "{name}" kwargs = {"name": name} if full: if node.labels and self.supports_node_labels: template += "{labels}" kwargs["labels"] = "".join(":" + cypher_escape(label) for label in node.labels) if node.properties: template += " {{{name}}}" self.parameters[name] = node.properties return "(" + template.format(**kwargs) + ")" def _create_node(self, node, name): if node.bound: self.start_or_match_clause.node(name, "{" + name + "}") self.parameters[name] = node._id else: self.create_clause.append(self._node_pattern(node, name, full=True)) self.return_clause.append(name) return [name], [] def _create_path_nodes(self, path, name, unique): node_names = [] for i, node in enumerate(path.nodes): if isinstance(node, NodePointer): node_names.append(_(node.address)) # Switch out node with object from elsewhere in entity list nodes = list(path.nodes) try: target_node = self.entities[node.address] except IndexError: raise IndexError("Node pointer {%s} out of range" % node.address) if not isinstance(target_node, Node): raise ValueError("Pointer {%s} does not refer to a node" % node.address) nodes[i] = target_node path._Path__nodes = tuple(nodes) elif node in self: node_name = _(self.entities.index(node)) node_names.append(node_name) elif unique and not node.bound: node_name = name + "n" + ustr(i) node_names.append(node_name) self.return_clause.append(node_name) else: node_name = name + "n" + ustr(i) node_names.append(node_name) self._create_node(node, node_name) return node_names def _create_path(self, path, name, unique): node_names = self._create_path_nodes(path, name, unique) rel_names = [] for i, rel in enumerate(path.rels): rel_name = name + "r" + ustr(i) rel_names.append(rel_name) if rel.bound: self.start_or_match_clause.relationship(rel_name, "{" + rel_name + "}") self.parameters[rel_name] = rel._id else: if rel.properties: template = "{start}-[{name}:{type} {{{name}}}]->{end}" self.parameters[rel_name] = rel.properties else: template = "{start}-[{name}:{type}]->{end}" start_index, end_index = i, i + 1 if isinstance(rel, Rev): start_index, end_index = end_index, start_index start_node = path.nodes[start_index] end_node = path.nodes[end_index] start = self._node_pattern(start_node, node_names[start_index], full=(unique and not start_node.bound and start_node not in self)) end = self._node_pattern(end_node, node_names[end_index], full=(unique and not end_node.bound and end_node not in self)) kwargs = {"start": start, "name": rel_name, "type": cypher_escape(rel.type), "end": end} if unique: self.create_unique_clause.append(template.format(**kwargs)) else: self.create_clause.append(template.format(**kwargs)) self.return_clause.append(rel_name) return node_names, rel_names
class CreateStatement(object): """ Builder for a Cypher CREATE/CREATE UNIQUE statement. """ #: The graph against which this statement is to be executed. graph = None #: The parameters to inject into this statement. parameters = None def __init__(self, graph): self.graph = graph self.supports_node_labels = self.graph.supports_node_labels self.entities = [] self.names = [] self.start_or_match_clause = StartOrMatch(self.graph) self.create_clause = [] self.create_unique_clause = [] self.return_clause = [] self.parameters = {} def __repr__(self): return self.string def __str__(self): return xstr(self.__unicode__()) def __unicode__(self): return self.string def __contains__(self, entity): return any(e is entity for e in self.entities) @property def string(self): """ The full Cypher statement as a string. """ clauses = [self.start_or_match_clause.string] if self.create_clause: clauses.append("CREATE " + ",".join(self.create_clause)) if self.create_unique_clause: clauses.append("CREATE UNIQUE " + ",".join(self.create_unique_clause)) if self.return_clause: clauses.append("RETURN " + ",".join(self.return_clause)) return "\n".join(clauses).strip() def post(self): return self.graph.cypher.post(self.string, self.parameters) def execute(self): """ Execute this statement. :return: A tuple of created entities. """ if not self.entities: return () raw = self.post().content columns = raw["columns"] data = raw["data"] dehydrated = dict(zip(columns, data[0])) for i, entity in enumerate(self.entities): node_names, rel_names = self.names[i] if isinstance(entity, Node): metadata = dehydrated[node_names[0]] entity.bind(metadata["self"], metadata) elif isinstance(entity, Relationship): metadata = dehydrated[rel_names[0]] entity.bind(metadata["self"], metadata) elif isinstance(entity, Path): for j, node in enumerate(entity.nodes): metadata = dehydrated[node_names[j]] node.bind(metadata["self"], metadata) for j, rel in enumerate(entity.rels): metadata = dehydrated[rel_names[j]] rel.bind(metadata["self"], metadata) return tuple(self.entities) def create(self, entity): """ Append an entity to the CREATE clause of this statement. :arg entity: The entity to create. """ entity = Graph.cast(entity) index = len(self.entities) name = _(index) if isinstance(entity, Node): self.names.append(self._create_node(entity, name)) elif isinstance(entity, Path): self.names.append(self._create_path(entity, name, unique=False)) else: raise TypeError("Cannot create entity of type %s" % type(entity).__name__) self.entities.append(entity) def create_unique(self, entity): """ Append an entity to the CREATE UNIQUE clause of this statement. :arg entity: The entity to add. """ entity = Graph.cast(entity) index = len(self.entities) name = _(index) if isinstance(entity, Path): if len(entity) == 0: raise ValueError("Cannot create unique path with zero length") if not any(node.bound or node in self for node in entity.nodes): raise ValueError( "At least one node must be bound to create a unique path") self.names.append(self._create_path(entity, name, unique=True)) else: raise TypeError("Cannot create unique entity of type %s" % type(entity).__name__) self.entities.append(entity) def _node_pattern(self, node, name, full): template = "{name}" kwargs = {"name": name} if full: if node.labels and self.supports_node_labels: template += "{labels}" kwargs["labels"] = "".join(":" + cypher_escape(label) for label in node.labels) if node.properties: template += " {{{name}}}" self.parameters[name] = node.properties return "(" + template.format(**kwargs) + ")" def _create_node(self, node, name): if node.bound: self.start_or_match_clause.node(name, "{" + name + "}") self.parameters[name] = node._id else: self.create_clause.append(self._node_pattern(node, name, full=True)) self.return_clause.append(name) return [name], [] def _create_path_nodes(self, path, name, unique): node_names = [] for i, node in enumerate(path.nodes): if isinstance(node, NodePointer): node_names.append(_(node.address)) # Switch out node with object from elsewhere in entity list nodes = list(path.nodes) try: target_node = self.entities[node.address] except IndexError: raise IndexError("Node pointer {%s} out of range" % node.address) if not isinstance(target_node, Node): raise ValueError("Pointer {%s} does not refer to a node" % node.address) nodes[i] = target_node path._Path__nodes = tuple(nodes) elif node in self: node_name = _(self.entities.index(node)) node_names.append(node_name) elif unique and not node.bound: node_name = name + "n" + ustr(i) node_names.append(node_name) self.return_clause.append(node_name) else: node_name = name + "n" + ustr(i) node_names.append(node_name) self._create_node(node, node_name) return node_names def _create_path(self, path, name, unique): node_names = self._create_path_nodes(path, name, unique) rel_names = [] for i, rel in enumerate(path.rels): rel_name = name + "r" + ustr(i) rel_names.append(rel_name) if rel.bound: self.start_or_match_clause.relationship( rel_name, "{" + rel_name + "}") self.parameters[rel_name] = rel._id else: if rel.properties: template = "{start}-[{name}:{type} {{{name}}}]->{end}" self.parameters[rel_name] = rel.properties else: template = "{start}-[{name}:{type}]->{end}" start_index, end_index = i, i + 1 if isinstance(rel, Rev): start_index, end_index = end_index, start_index start_node = path.nodes[start_index] end_node = path.nodes[end_index] start = self._node_pattern(start_node, node_names[start_index], full=(unique and not start_node.bound and start_node not in self)) end = self._node_pattern(end_node, node_names[end_index], full=(unique and not end_node.bound and end_node not in self)) kwargs = { "start": start, "name": rel_name, "type": cypher_escape(rel.type), "end": end } if unique: self.create_unique_clause.append(template.format(**kwargs)) else: self.create_clause.append(template.format(**kwargs)) self.return_clause.append(rel_name) return node_names, rel_names
def _node_isolate(self): from py2neo.cypher.util import StartOrMatch graph = self.graph query = StartOrMatch(graph).node("a", "{a}").string + "MATCH a-[r]-b DELETE r" graph.cypher.post(query, {"a": self._id})