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))
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