def drop_uniqueness_constraint(self, label, property_key): """ Remove the node uniqueness constraint for a given label and property key. """ cypher = "DROP CONSTRAINT ON (_:{}) ASSERT _.{} IS UNIQUE".format( cypher_escape(label), cypher_escape(property_key)) self.graph.run(cypher).close()
def __init__(self, subject, node, direction, relationship_type, related_class): assert isinstance(direction, int) and not isinstance(direction, bool) self.subject = subject self.node = node self.relationship_type = relationship_type self.related_class = related_class self.__related_objects = None if direction > 0: self.__match_args = { "nodes": (self.node, None), "r_type": relationship_type } self.__start_node = False self.__end_node = True self.__relationship_pattern = "(a)-[_:%s]->(b)" % cypher_escape( relationship_type) elif direction < 0: self.__match_args = { "nodes": (None, self.node), "r_type": relationship_type } self.__start_node = True self.__end_node = False self.__relationship_pattern = "(a)<-[_:%s]-(b)" % cypher_escape( relationship_type) else: self.__match_args = { "nodes": {self.node, None}, "r_type": relationship_type } self.__start_node = True self.__end_node = True self.__relationship_pattern = "(a)-[_:%s]-(b)" % cypher_escape( relationship_type)
def merge_nodes(tx, data, primary_label, primary_key, labels=None, keys=None): """ Merge nodes from an iterable sequence of raw node data. :param tx: :param data: list of properties :param primary_label: :param primary_key: :param labels: :param keys: :returns: """ if keys: # data is list of lists primary_index = keys.index(primary_key) fields = [Literal("r[%d]" % i) for i in range(len(keys))] set_rhs = cypher_repr(dict(zip(keys, fields))) else: # data is list of dicts primary_index = primary_key set_rhs = "r" cypher = ("UNWIND $data AS r " "MERGE (_:%s {%s:r[$key]}) " "SET _%s " "SET _ = %s " "RETURN id(_)" % (cypher_escape(primary_label), cypher_escape(primary_key), _label_string(labels, primary_label), set_rhs)) for record in tx.run(cypher, data=data, key=primary_index): yield record[0]
def _unwind_merge_query(data, merge_key, labels=None, keys=None, return_expr=None): pl = merge_key[0] pk = merge_key[1:] if keys: # data is list of lists indexes = [keys.index(key) for key in pk] fields = [Literal("r[%d]" % i) for i in range(len(keys))] set_rhs = cypher_repr(dict(zip(keys, fields))) else: # data is list of dicts indexes = list(pk) set_rhs = "r" merge_key_string = ", ".join("{}:r[$ix[{}]]".format(cypher_escape(key), i) for i, key in enumerate(pk)) merge_pattern = "(_:%s {%s})" % (cypher_escape(pl), merge_key_string) cypher = ("UNWIND $data AS r " "MERGE %s " "SET _%s " "SET _ = %s " % (merge_pattern, _label_string(pl, *(labels or ())), set_rhs)) if return_expr: cypher += " RETURN %s" % return_expr return cypher, {"data": data, "ix": indexes}
def match(self, start_node=None, rel_type=None, end_node=None, bidirectional=False, limit=None): """ Match and return all relationships with specific criteria. For example, to find all of Alice's friends:: for rel in graph.match(start_node=alice, rel_type="FRIEND"): print(rel.end_node()["name"]) :param start_node: start node of relationships to match (:const:`None` means any node) :param rel_type: type of relationships to match (:const:`None` means any type) :param end_node: end node of relationships to match (:const:`None` means any node) :param bidirectional: :const:`True` if reversed relationships should also be included :param limit: maximum number of relationships to match (:const:`None` means unlimited) """ clauses = [] returns = [] if start_node is None and end_node is None: clauses.append("MATCH (a)") parameters = {} returns.append("a") returns.append("b") elif end_node is None: clauses.append("MATCH (a) WHERE id(a) = {1}") start_node = cast_node(start_node) if not remote(start_node): raise TypeError("Nodes for relationship match end points must be bound") parameters = {"1": remote(start_node)._id} returns.append("b") elif start_node is None: clauses.append("MATCH (b) WHERE id(b) = {2}") end_node = cast_node(end_node) if not remote(end_node): raise TypeError("Nodes for relationship match end points must be bound") parameters = {"2": remote(end_node)._id} returns.append("a") else: clauses.append("MATCH (a) WHERE id(a) = {1} MATCH (b) WHERE id(b) = {2}") start_node = cast_node(start_node) end_node = cast_node(end_node) if not remote(start_node) or not remote(end_node): raise TypeError("Nodes for relationship match end points must be bound") parameters = {"1": remote(start_node)._id, "2": remote(end_node)._id} if rel_type is None: relationship_detail = "" elif is_collection(rel_type): relationship_detail = ":" + "|:".join(cypher_escape(t) for t in rel_type) else: relationship_detail = ":%s" % cypher_escape(rel_type) if bidirectional: clauses.append("MATCH (a)-[_" + relationship_detail + "]-(b)") else: clauses.append("MATCH (a)-[_" + relationship_detail + "]->(b)") returns.append("_") clauses.append("RETURN %s" % ", ".join(returns)) if limit is not None: clauses.append("LIMIT %d" % limit) cursor = self.run(" ".join(clauses), parameters) while cursor.forward(): record = cursor.current() yield record["_"]
def __repr__(self): from py2neo.cypher import cypher_escape if self.__selected: return "".join(":%s" % cypher_escape(e, **self.__kwargs) for e in self.__selected if e in self.__elements) else: return "".join(":%s" % cypher_escape(e, **self.__kwargs) for e in sorted(self.__elements))
def _merge_clause(value, merge_key, keys, prefix, suffix): pl, pk = _unpack_merge_key(merge_key) if keys is None: ix = list(pk) else: ix = [keys.index(key) for key in pk] merge_key_string = ", ".join("%s:%s[%s]" % (cypher_escape(key), value, cypher_repr(ix[i])) for i, key in enumerate(pk)) if merge_key_string: return "MERGE %s_:%s {%s}%s" % (prefix, cypher_escape(pl), merge_key_string, suffix) else: return "MERGE %s_:%s%s" % (prefix, cypher_escape(pl), suffix)
def create_uniqueness_constraint(self, label, property_key): """ Create a node uniqueness constraint for a given label and property key. While indexes support the use of composite keys, unique constraints may only be tied to a single property key. """ cypher = "CREATE CONSTRAINT ON (_:{}) ASSERT _.{} IS UNIQUE".format( cypher_escape(label), cypher_escape(property_key)) self.graph.run(cypher).close() while property_key not in self.get_uniqueness_constraints(label): sleep(0.1)
def _match_clause(value, name, node_key): if node_key is None: # ... add MATCH by id clause return "MATCH (%s) WHERE id(%s) = %s" % (name, name, value) else: # ... add MATCH by label/property clause pl, pk = _unpack_merge_key(node_key) n_pk = len(pk) if n_pk == 0: return "MATCH (%s:%s)" % (name, cypher_escape(pl)) elif n_pk == 1: return "MATCH (%s:%s {%s:%s})" % (name, cypher_escape(pl), cypher_escape(pk[0]), value) else: match_key_string = ", ".join("%s:%s[%s]" % (cypher_escape(key), value, j) for j, key in enumerate(pk)) return "MATCH (%s:%s {%s})" % (name, cypher_escape(pl), match_key_string)
def _query_and_parameters(self, count=False): """ A tuple of the Cypher query and parameters used to select the nodes that match the criteria for this selection. :return: Cypher query string """ clauses = [ "MATCH (_%s)" % "".join(":%s" % cypher_escape(label) for label in self._labels) ] parameters = {} if self._predicates: predicates = [] for predicate in self._predicates: if isinstance(predicate, tuple): predicate, param = predicate parameters.update(param) predicates.append(predicate) clauses.append("WHERE %s" % " AND ".join(predicates)) if count: clauses.append("RETURN count(_)") else: clauses.append("RETURN _") if self._order_by: clauses.append("ORDER BY %s" % (", ".join(self._order_by))) if self._skip: clauses.append("SKIP %d" % self._skip) if self._limit is not None: clauses.append("LIMIT %d" % self._limit) return " ".join(clauses), parameters
def query_entity(self, ntype, rel_conditions, attr_conditions, neg_attr): """ query entity names for given relation and attribute conditions Negation conditions only support attributes for now. """ statement = "MATCH (p:{})".format(ntype) for attr, value in attr_conditions: statement += "\nMATCH (p {{{}:'{}'}})".format(attr, cypher.cypher_escape(value)) for rel, entity in rel_conditions: statement += "\nMATCH (p)-[:{}]->({{name:'{}'}})".format(rel, cypher.cypher_escape(entity)) if neg_attr: statement += "\nWHERE p.{} <> '{}'".format(neg_attr[0][0], cypher.cypher_escape(neg_attr[0][1])) for attr, value in neg_attr[1:]: statement += "AND p.{} <> '{}'".format(attr, cypher.cypher_escape(value)) statement += "\nRETURN p.name as name" return self.graph.run(statement).data()
def _create_nodes(tx, labels, data): assert isinstance(labels, frozenset) label_string = "".join(":" + cypher_escape(label) for label in sorted(labels)) cypher = "UNWIND $x AS data CREATE (_%s) SET _ = data RETURN id(_)" % label_string for record in tx.run(cypher, x=data): yield record[0]
def drop_uniqueness_constraint(self, label, *property_keys): """ Remove the uniqueness constraint for a given property key. """ self.graph.run("DROP CONSTRAINT ON (a:%s) " "ASSERT a.%s IS UNIQUE" % (cypher_escape(label), ",".join( map(cypher_escape, property_keys)))).close()
def _merge_nodes(tx, p_label, p_key, labels, data): """ :param tx: :param p_label: :param p_key: :param labels: :param data: list of (p_value, properties) :return: """ assert isinstance(labels, frozenset) label_string = ":".join(cypher_escape(label) for label in sorted(labels)) cypher = "UNWIND $x AS data MERGE (_:%s {%s:data[0]}) SET _:%s SET _ = data[1] RETURN id(_)" % ( cypher_escape(p_label), cypher_escape(p_key), label_string) for record in tx.run(cypher, x=data): yield record[0]
def _query_and_parameters(self): """ A tuple of the Cypher query and parameters used to select the nodes that match the criteria for this selection. :return: Cypher query string """ clauses = [ "MATCH (_%s)" % "".join(":%s" % cypher_escape(label) for label in self._labels) ] parameters = {} if self._conditions: conditions = [] for condition in self._conditions: if isinstance(condition, tuple): condition, param = condition parameters.update(param) conditions.append(condition) clauses.append("WHERE %s" % " AND ".join(conditions)) clauses.append("RETURN _") if self._order_by: clauses.append("ORDER BY %s" % (", ".join(self._order_by))) if self._skip: clauses.append("SKIP %d" % self._skip) if self._limit is not None: clauses.append("LIMIT %d" % self._limit) return " ".join(clauses), parameters
def create_uniqueness_constraint(self, label, *property_keys): """ Create a uniqueness constraint for a label. """ self.graph.run("CREATE CONSTRAINT ON (a:%s) " "ASSERT a.%s IS UNIQUE" % (cypher_escape(label), ",".join(map(cypher_escape, property_keys)))).close() while property_keys not in self.get_uniqueness_constraints(label): sleep(0.1)
def create_index(self, label, *property_keys): """ Create a schema index for a label and property key combination. """ self.graph.run("CREATE INDEX ON :%s(%s)" % (cypher_escape(label), ",".join(map(cypher_escape, property_keys)))).close() while property_keys not in self.get_indexes(label): sleep(0.1)
def create_index(self, label, *property_keys): """ Create a schema index for a label and property key combination. """ cypher = "CREATE INDEX ON :{}({})".format( cypher_escape(label), ", ".join(map(cypher_escape, property_keys))) self.graph.update(cypher) while property_keys not in self.get_indexes(label): sleep(0.1)
def query_attribute(self, ntype, name, attrs): """ query attribute value for a given entity For all attributes to query, see http://123.57.246.134:7474/ """ statement = """MATCH (p:{} {{name:'{}'}}) RETURN p.{} as {}""".format(ntype, cypher.cypher_escape(name), attrs[0], attrs[0]) for attr in attrs[1:]: statement += ", p.{} as {}".format(attr, attr) return self.graph.run(statement).data()
def __db_create__(self, tx): from py2neo.cypher import cypher_escape nodes = list(self.nodes()) reads = [] writes = [] parameters = {} returns = {} for i, node in enumerate(nodes): node_id = "a%d" % i param_id = "x%d" % i remote_node = remote(node) if remote_node: reads.append("MATCH (%s) WHERE id(%s)={%s}" % (node_id, node_id, param_id)) parameters[param_id] = remote_node._id else: label_string = "".join(":" + cypher_escape(label) for label in sorted(node.labels())) writes.append("CREATE (%s%s {%s})" % (node_id, label_string, param_id)) parameters[param_id] = dict(node) node._set_remote_pending(tx) returns[node_id] = node for i, relationship in enumerate(self.relationships()): if not remote(relationship): rel_id = "r%d" % i start_node_id = "a%d" % nodes.index(relationship.start_node()) end_node_id = "a%d" % nodes.index(relationship.end_node()) type_string = cypher_escape(relationship.type()) param_id = "y%d" % i writes.append("CREATE UNIQUE (%s)-[%s:%s]->(%s) SET %s={%s}" % (start_node_id, rel_id, type_string, end_node_id, rel_id, param_id)) parameters[param_id] = dict(relationship) returns[rel_id] = relationship relationship._set_remote_pending(tx) statement = "\n".join(reads + writes + ["RETURN %s LIMIT 1" % ", ".join(returns)]) tx.entities.append(returns) list(tx.run(statement, parameters))
def _property_equality_conditions(properties, offset=1): for i, (key, value) in enumerate(properties.items(), start=offset): if key == "__id__": condition = "id(_)" else: condition = "_.%s" % cypher_escape(key) if isinstance(value, (tuple, set, frozenset)): condition += " IN {%d}" % i parameters = {"%d" % i: list(value)} else: condition += " = {%d}" % i parameters = {"%d" % i: value} yield condition, parameters
def _merge_relationships(tx, r_type, data): """ :param tx: :param r_type: :param data: list of (a_id, b_id, properties) :return: """ cypher = ("UNWIND $x AS data " "MATCH (a) WHERE id(a) = data[0] " "MATCH (b) WHERE id(b) = data[1] " "MERGE (a)-[_:%s]->(b) SET _ = data[2] RETURN id(_)" % cypher_escape(r_type)) for record in tx.run(cypher, x=data): yield record[0]
def _match_clause(name, node_key, value, prefix="(", suffix=")"): if node_key is None: # ... add MATCH by id clause return "MATCH %s%s%s WHERE id(%s) = %s" % (prefix, name, suffix, name, value) else: # ... add MATCH by label/property clause nk = NodeKey(node_key) n_pk = len(nk.keys()) if n_pk == 0: return "MATCH %s%s%s%s" % ( prefix, name, nk.label_string(), suffix) elif n_pk == 1: return "MATCH %s%s%s {%s:%s}%s" % ( prefix, name, nk.label_string(), cypher_escape(nk.keys()[0]), value, suffix) else: return "MATCH %s%s%s {%s}%s" % ( prefix, name, nk.label_string(), nk.key_value_string(value, list(range(n_pk))), suffix)
def unwind_create_relationships_query(data, rel_type, keys=None, start_node_key=None, end_node_key=None): """ Generate a parameterised ``UNWIND...CREATE`` query for bulk loading relationships into Neo4j. :param data: :param rel_type: :param keys: :param start_node_key: :param end_node_key: :return: (query, parameters) tuple """ return cypher_join("UNWIND $data AS r", _match_clause("r[0]", "a", start_node_key), _match_clause("r[2]", "b", end_node_key), "CREATE (a)-[_:%s]->(b)" % cypher_escape(rel_type), _set_properties_clause("r[1]", keys), data=list(data))
def __call__(self, *args, **kwargs): """ Call a procedure by name. For example: >>> from py2neo import Graph >>> g = Graph() >>> g.call("dbms.components") name | versions | edition --------------|-----------|----------- Neo4j Kernel | ['4.0.2'] | community :param procedure: fully qualified procedure name :param args: positional arguments to pass to the procedure :returns: :class:`.Cursor` object wrapping the result """ procedure_name = ".".join(cypher_escape(part) for part in self.name.split(".")) arg_list = [(str(i), arg) for i, arg in enumerate(args)] cypher = "CALL %s(%s)" % (procedure_name, ", ".join("$" + a[0] for a in arg_list)) keys = kwargs.get("keys") if keys: cypher += " YIELD %s" % ", ".join(keys) return self.graph.run(cypher, dict(arg_list))
def _property_conditions(properties, offset=1): for i, (key, value) in enumerate(properties.items(), start=offset): if key == "__id__": condition = "id(_)" else: condition = "_.%s" % cypher_escape(key) if value is None: condition += " IS NULL" parameters = {} elif isinstance(value, (tuple, set, frozenset)): condition += " IN {%d}" % i parameters = {"%d" % i: list(value)} elif re.match(_operators_search, key): parts = re.search(_operators_search, key) prop = parts.group(1) operator = parts.group(2) condition = "_.%s %s {%d}" % (prop, _operators[operator], i) parameters = {"%d" % i: value} else: condition += " = {%d}" % i parameters = {"%d" % i: value} yield condition, parameters
def check_credentials(cls, username, password): """ Trying to find user with provided username and password. Returns None if failing. Otherwise cls instance. :param username: str :param password: str :return: cls | None """ res = g.cypher.execute("MATCH (u:{class_name}) WHERE u.username={{username}} " "AND u.password={{password}} RETURN u".format(class_name=cypher_escape(cls.__name__)), username=username, password=password) if res.one: return res.one
def _query_and_parameters(self, count=False): """ A tuple of the Cypher query and parameters used to select the relationships that match the criteria for this selection. :return: Cypher query string """ def verify_node(n): if n.graph != self.graph: raise ValueError("Node %r does not belong to this graph" % n) if n.identity is None: raise ValueError("Node %r is not bound to a graph" % n) def r_type_name(r): try: return r.__name__ except AttributeError: return r clauses = [] parameters = {} if self._r_type is None: relationship_detail = "" elif is_collection(self._r_type): relationship_detail = ":" + "|:".join( cypher_escape(r_type_name(t)) for t in self._r_type) else: relationship_detail = ":%s" % cypher_escape( r_type_name(self._r_type)) if not self._nodes: clauses.append("MATCH (a)-[_" + relationship_detail + "]->(b)") elif isinstance(self._nodes, Sequence): if len(self._nodes) >= 1 and self._nodes[0] is not None: start_node = Node.cast(self._nodes[0]) verify_node(start_node) clauses.append("MATCH (a) WHERE id(a) = {x}") parameters["x"] = start_node.identity if len(self._nodes) >= 2 and self._nodes[1] is not None: end_node = Node.cast(self._nodes[1]) verify_node(end_node) clauses.append("MATCH (b) WHERE id(b) = {y}") parameters["y"] = end_node.identity if len(self._nodes) >= 3: raise ValueError("Node sequence cannot be longer than two") clauses.append("MATCH (a)-[_" + relationship_detail + "]->(b)") elif isinstance(self._nodes, Set): nodes = {node for node in self._nodes if node is not None} if len(nodes) >= 1: start_node = Node.cast(nodes.pop()) verify_node(start_node) clauses.append("MATCH (a) WHERE id(a) = {x}") parameters["x"] = start_node.identity if len(nodes) >= 1: end_node = Node.cast(nodes.pop()) verify_node(end_node) clauses.append("MATCH (b) WHERE id(b) = {y}") parameters["y"] = end_node.identity if len(nodes) >= 1: raise ValueError("Node set cannot be larger than two") clauses.append("MATCH (a)-[_" + relationship_detail + "]-(b)") else: raise ValueError("Nodes must be passed as a Sequence or a Set") if self._conditions: conditions = [] for condition in self._conditions: if isinstance(condition, tuple): condition, param = condition parameters.update(param) conditions.append(condition) clauses.append("WHERE %s" % " AND ".join(conditions)) if count: clauses.append("RETURN count(_)") else: clauses.append("RETURN _") if self._order_by: clauses.append("ORDER BY %s" % (", ".join(self._order_by))) if self._skip: clauses.append("SKIP %d" % self._skip) if self._limit is not None: clauses.append("LIMIT %d" % self._limit) return " ".join(clauses), parameters
def compile(self, key, i): return "_.%s < $%s" % (cypher_escape(key), i), {"%s" % i: self.value}
def compile(self, key, _): return "_.%s IS NOT NULL" % cypher_escape(key), {}
def compile(self, key, i): return "_.%s IN $%s" % (cypher_escape(key), i), { "%s" % i: list(self.value) }