Example #1
0
    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)
Example #2
0
    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)
Example #3
0
def test_attribute_types():
    uid = Uuid().default
    dct = dict_to_db_values_dict({'foo': uid, 'bar': 123})

    assert dct == {'foo': str(uid), 'bar': 123}
Example #4
0
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,
    )
Example #5
0
    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)
Example #6
0
    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