def get(self, cls, **attr_filter): attr_filter = dict_to_db_values_dict(attr_filter) # Workaround that allows nodes and relationships with no # attrs to be saved. `save` will cause this method to be called # with an empty attr_filter, and when it receives None, will add # a new object. if not attr_filter: return None type_registry = self.type_registry unique_attrs = [key for _, key in type_registry.get_unique_attrs(cls)] query_params = { key: value for key, value in attr_filter.items() if key in unique_attrs and value is not None } if not query_params: raise ValueError( 'No relevant indexes found when calling get for class: {}' ' with filter {}'.format(cls, attr_filter) ) labels = type_registry.get_labels_for_type(cls) # since we found an index, we have at least one label node_declaration = 'n:' + ':'.join(labels) params = parameter_map(attr_filter, 'params') return self.query_single( "MATCH (%s %s) RETURN n" % (node_declaration, params), params=attr_filter)
def query(self, query, **params): """ Queries the store given a parameterized cypher query. Args: query: A parameterized cypher query. params: query: A parameterized cypher query. Returns: A generator with tuples containing stored objects or values. WARNING: If you use this method to modify the type hierarchy (i.e. types, their declared attributes or their relationships), ensure to call ``manager.invalidate_type_hierarchy()`` afterwards. Otherwise managers will continue to use cached versions. Instances can be modified without changing the type hierarchy. """ params = dict_to_db_values_dict(params) result = self._execute(query, **params) return (tuple(self._convert_row(row)) for row in result)
def test_attribute_types(): uid = Uuid().default dct = dict_to_db_values_dict({'foo': uid, 'bar': 123}) assert dct == {'foo': str(uid), 'bar': 123}
def get_match_clause(obj, name, type_registry): """Return node lookup by index for a match clause using unique attributes Args: obj: An object to create an index lookup. name: The name of the object in the query. Returns: A string with an index lookup for a cypher MATCH clause """ if isinstance(obj, PersistableType): value = get_type_id(obj) return '({name}:PersistableType {{id: {value}}})'.format( name=name, value=json.dumps(object_to_db_value(value)), ) if isinstance(obj, Relationship): if obj.start is None or obj.end is None: raise NoUniqueAttributeError( "{} is missing a start or end node".format(obj) ) neo4j_rel_name = get_neo4j_relationship_name(type(obj)) start_name = '{}__start'.format(name) end_name = '{}__end'.format(name) query = """ {start_clause}, {end_clause}, ({start_name})-[{name}:{neo4j_rel_name}]->({end_name}) """.format( name=name, start_clause=get_match_clause( obj.start, start_name, type_registry, ), end_clause=get_match_clause( obj.end, end_name, type_registry, ), start_name=start_name, end_name=end_name, neo4j_rel_name=neo4j_rel_name, ) return dedent(query) match_params = {} label_classes = set() for cls, attr_name in type_registry.get_unique_attrs(type(obj)): value = getattr(obj, attr_name) if value is not None: label_classes.add(cls) match_params[attr_name] = value if not match_params: raise NoUniqueAttributeError( "{} doesn't have any unique attributes".format(obj) ) match_params_string = inline_parameter_map( dict_to_db_values_dict(match_params) ) labels = ':'.join(get_type_id(cls) for cls in label_classes) return '({name}:{labels} {match_params_string})'.format( name=name, labels=labels, attr_name=attr_name, match_params_string=match_params_string, )
def get_type_hierarchy(self, start_type_id=None): """ Returns the entire type hierarchy defined in the database if start_type_id is None, else returns from that type. Returns: A generator yielding tuples of the form ``(type_id, bases, attrs)`` where - ``type_id`` identifies the type - ``bases`` lists the type_ids of the type's bases - ``attrs`` lists the attributes defined on the type """ if start_type_id: match = """ p = ( (ts:TypeSystem {id: "TypeSystem"})-[:DEFINES]->()<- [:ISA*]-(opt)<-[:ISA*0..]-(tpe) ) WHERE opt.id = {start_id} """ query_args = {'start_id': start_type_id} else: match = """ p=( (ts:TypeSystem {id: "TypeSystem"})-[:DEFINES]->()<- [:ISA*0..]-(tpe) ) """ query_args = {} query = join_lines( 'MATCH', match, """ WITH tpe, max(length(p)) AS level OPTIONAL MATCH tpe <-[:DECLAREDON*]- attr OPTIONAL MATCH tpe -[isa:ISA]-> base WITH tpe.id AS type_id, level, tpe AS class_attrs, filter( idx_base in collect(DISTINCT [isa.base_index, base.id]) WHERE not(LAST(idx_base) is NULL) ) AS bases, collect(DISTINCT attr) AS attrs ORDER BY level RETURN type_id, bases, class_attrs, attrs """) # we can't use self.query since we don't want to convert the # class_attrs dict params = dict_to_db_values_dict(query_args) for row in self._execute(query, **params): type_id, bases, class_attrs, instance_attrs = row # the bases are sorted using their index on the IsA relationship bases = tuple(base for (_, base) in sorted(bases)) class_attrs = class_attrs._properties for internal_attr in INTERNAL_CLASS_ATTRS: class_attrs.pop(internal_attr) instance_attrs = [self._convert_value(v) for v in instance_attrs] instance_attrs = {attr.name: attr for attr in instance_attrs} attrs = class_attrs attrs.update(instance_attrs) yield (type_id, bases, attrs)
def get(self, cls, **attr_filter): attr_filter = dict_to_db_values_dict(attr_filter) if not attr_filter: return None query_args = {} indexes = attr_filter.items() if issubclass(cls, (Relationship, PersistableType)): idx_name = get_index_name(cls) idx_key, idx_value = indexes[0] if issubclass(cls, Relationship): self._conn.get_or_create_index(neo4j.Relationship, idx_name) start_func = 'relationship' else: self._conn.get_or_create_index(neo4j.Node, idx_name) start_func = 'node' query = 'START nr = %s:%s(%s={idx_value}) RETURN nr' % ( start_func, idx_name, idx_key) query_args['idx_value'] = idx_value elif cls is TypeSystem: idx_name = get_index_name(TypeSystem) query = join_lines( 'START ts=node:%s(id={idx_value})' % idx_name, 'RETURN ts' ) query_args['idx_value'] = self.type_system.id else: idx_where = [] for key, value in indexes: idx_where.append('n.%s! = {%s}' % (key, key)) query_args[key] = value idx_where = ' or '.join(idx_where) idx_name = get_index_name(TypeSystem) query = join_lines( 'START root=node:%s(id={idx_value})' % idx_name, 'MATCH ', ' n -[:INSTANCEOF]-> ()', ' -[:ISA*0..]-> tpe -[:ISA*0..]-> () <-[:DEFINES]- root', 'WHERE %s' % idx_where, ' AND tpe.id = {tpe_id}', 'RETURN n', ) query_args['idx_value'] = self.type_system.id type_id = get_type_id(cls) query_args['tpe_id'] = type_id found = [node for (node,) in self._execute(query, **query_args)] if not found: return None # all the nodes returned should be the same first = found[0] for node in found: if node.id != first.id: raise UniqueConstraintError(( "Multiple nodes ({}) found for unique lookup for " "{}").format(found, cls)) obj = self._convert_value(first) return obj