Beispiel #1
0
    def start_path(self):
        """Create matching path of query."""
        # Find path to model
        datapath = registry.path(self.label) or []

        self.matches = []
        self.rels = []
        self.state_matches = []

        for i, pathtuple in enumerate(datapath):
            label, relname = pathtuple
            self.matches.append(
                (label.lower, label, '({}:{})'.format(label.lower(), label)))
            self.rels.append(
                ('r{}'.format(i), relname, '-[r{}:{}]->'.format(i, relname)))

            if registry.state_properties(label):
                self.state_matches.append(label)

            self.addreturn(label)

        self.matches.append((self.label.lower(), self.label,
                             '({}:{})'.format(self.label.lower(), self.label)))
        if registry.state_properties(self.label):
            self.state_matches.append(self.label)

        self.return_labels.append(self.label)
        return self
Beispiel #2
0
    def test_init(self, m_state, m_side):

        m_state.fetch_all.return_value = []
        m_side.fetch_all.return_value = []

        model = 'Environment'
        identity = 'someid'
        d = Diff(model, identity, 0, 1)

        self.assertEqual(d.model, model)
        self.assertEqual(d.identity, identity)
        self.assertEqual(d.t1, 0)
        self.assertEqual(d.t2, 1)

        # Assert that queries are called on increasing length paths only.
        l_path = 0
        for call in m_side.call_args_list:
            path = call[0][0]
            self.assertTrue(len(path) >= l_path)
            l_path = max(l_path, len(path))

        # Assert that state queries are ignored for stateless models.
        stateless = set()
        for m in registry.models:
            if not registry.state_properties(m):
                stateless.add(m)
        for call in m_state.call_args_list:
            path = call[0][0]
            self.assertFalse(path[-1] in stateless)
Beispiel #3
0
    def orderby(self, prop, direction, label=None):
        """Add to orderby clause

        :param prop: Property to order by
        :type prop: str
        :param direction: Order direction (ASC or DESC)
        :type direction: str
        :param label: Label with the property. Defaults to target label.
            Can be target label or label in parent path
        :type label: str
        """
        # Default label to target label
        if label is None:
            label = self.label

        # Make sure label is valid
        model = registry.models.get(label)
        if model is None:
            raise InvalidLabelError(label)

        # Make sure property is valid
        if prop not in registry.properties(label):
            raise InvalidPropertyError(label, prop)

        # Check if property is part of the state of the model
        if prop in registry.state_properties(label):
            varname = '{}_state'.format(label.lower())
        else:
            varname = label.lower()

        self._orderby.append((varname, prop, direction))
Beispiel #4
0
    def filter(self, prop, operator, value, label=None):
        """Add a filter.

        :param prop: Name of the property
        :type prop: str
        :param operator: Operator to use
        :type operator: str
        :param value: Value to filter
        :type value: str
        :param model: Optional label that has prop. Defaults to self.label
        :type model: str
        """
        if label is None:
            label = self.label

        valid_properties = registry.properties(label)
        if not valid_properties:
            raise InvalidLabelError(label)

        if prop not in registry.properties(label):
            raise InvalidPropertyError(prop, label)

        if prop in registry.state_properties(label):
            label = '{}_state'.format(label)

        condition = '{}.{} {} {}'.format(
            label.lower(), prop, operator,
            '$filterval{}'.format(self.filter_count))
        self.filter_wheres.append(condition)

        self.params['filterval{}'.format(self.filter_count)] = value
        self.filter_count += 1
        return self
Beispiel #5
0
    def add_column(self, model, prop, name=None):
        """Add a column to return.

        Verify model is in path and prop belongs to model.
        Add a column tuple. These will be used in the return clause.

        :param model: Name of the model
        :type model: str
        :param prop: Name of the property belonging to the model.
        :type prop: str
        :param name: Optional name of the column
            instead of model.prop, the column name can be custom.
            RETURN model.prop vs RETURN model.prop AS custom
        :type name: str
        """
        datapath = [p[0] for p in (registry.path(self.label) or [])]
        datapath += [self.label]

        if model not in datapath and model != self.label:
            raise InvalidLabelError(model)

        if prop not in registry.properties(model):
            raise InvalidPropertyError(prop, model)

        key = '{}.{}'.format(model, prop)

        if prop in registry.state_properties(model):
            model = _model_state(model)

        if name is not None:
            key = name

        self._columns[key] = '{}.{}'.format(model.lower(), prop)
        return self
    def __init__(self, model, identity, t1, t2):
        """Init the diff

        :param model: Type|Label of the root node
        :type model: str
        :param identity: Identity of the root node
        :type identity: str
        :param t1: First timestamp in milliseconds
        :type t1: int
        :param t2: Second timestamp in milliseconds
        :type t2: int
        """
        self.model = model
        self.identity = identity
        self.t1 = t1
        self.t2 = t2

        self.children = {}

        # Get list of paths
        paths = registry.forest.paths_from(self.model)
        for p in paths:
            q = DiffSideQuery(p, identity, (t1, t2))
            for row in q.fetch_all():
                self.feed(row, 't1')

            q = DiffSideQuery(p, identity, (t2, t1))
            for row in q.fetch_all():
                self.feed(row, 't2')

            if registry.state_properties(p[-1]):
                q = DiffStateQuery(p, identity, (t1, t2))
                for row in q.fetch_all():
                    self.feed(row, ['t1', 't2'])
Beispiel #7
0
 def fetch(self):
     resp = self._fetch(str(self))
     rows = []
     for record in resp:
         row = {}
         for label in self.return_labels:
             obj = {}
             for key, value in record[label.lower()].items():
                 obj[key] = value
             if registry.state_properties(label):
                 state_key = '{}_state'.format(label.lower())
                 for key, value in record[state_key].items():
                     obj[key] = value
             row[label] = obj
         rows.append(row)
     return rows
def prune(session, env, path, stats):
    """Prune the leaves at the end of the path.

    First prune state leaves
    Then prune leaves
    Only if the type of leaf is not shared.

    :param session: Neo4j driver session
    :type session: neo4j.v1.session.BoltSession
    :param env: Environment to remove
    :type env: EnvironmentEntity
    :param path: List of labels indicating path.
    :type path: list
    :param stats: Dictionary of deleted node counts.
    :type stats: dict
    :returns: Updated deleted node counts.
        (This is changed by reference but also returned.)
    :rtype: dict
    """
    leaf = path[-1]

    # Do nothing if the leaf is shared.
    if registry.is_shared(leaf):
        stats[leaf] = None
        return stats

    params = {'uuid': env.uuid}

    # First detach and delete state nodes.
    if registry.state_properties(leaf):
        match = ('MATCH (e:Environment)-[*]->(:{})-[:HAS_STATE]->(n:{}State)'
                 'WHERE e.uuid = $uuid').format(leaf, leaf)
        deleted = delete_until_zero(session, match, params=params, limit=5000)
        stats['{}State'.format(leaf)] = deleted

    # Then detach and delete identity nodes
    if leaf != 'Environment':
        match = ('MATCH (e:Environment)-[*]->(n:{})'
                 'WHERE e.uuid = $uuid').format(leaf)
    else:
        # Environment is a special case.
        # There are no paths from environment to self
        match = ('MATCH (n:Environment)' 'WHERE n.uuid = $uuid')

    deleted = delete_until_zero(session, match, params=params, limit=5000)
    stats[leaf] = deleted
    return stats
    def node_at_time(self, t):
        """Get a node from the database at time t.

        :param t: A time in milliseconds
        :type t: int
        :returns: A dictionary of properties
        :rtype: dict
        """
        var_models = []
        rels = []
        returns = ['n']
        for model, reltype in self.full_path:
            var_models.append((model.lower(), model))
            rels.append(reltype)
        var_models.append(('n', self.model))

        if registry.state_properties(self.model):
            var_models.append(('ns', registry.models[self.model].state_label))
            rels.append('HAS_STATE')
            returns.append('ns')

        cipher = 'MATCH p = ({}:{})'.format(var_models[0][0], var_models[0][1])

        for var_model, rel in zip(var_models[1:], rels):
            var, model = var_model
            cipher += '-[:{}]->({}:{})'.format(rel, var, model)

        cipher += (
            ' WHERE ALL (r IN RELATIONSHIPS(p) WHERE r.from <= $t < r.to) AND'
        )
        cipher += (
            ' n.{} = $identity'.format(registry.identity_property(self.model))
        )
        cipher += ' RETURN ' + ','.join(returns) + ' LIMIT 1'

        self.params['t'] = t

        record = self._fetch(cipher).single()
        if record is None:
            node = {}
        else:
            node = {k: v for k, v in record['n'].items()}
            if 'ns' in record:
                for k, v in record['ns'].items():
                    node[k] = v
        return node
Beispiel #10
0
def node_count():
    """Count the number of nodes with each label.

    Use this before migration and after migration to verify no
    unwanted nodes have been created.

    :returns: Mapping of label to counts.
    :rtype: dict
    """
    counts = {}
    labels = []
    driver = get_neo4j()
    for model_name, klass in registry.models.items():
        labels.append(klass.label)
        if registry.state_properties(model_name):
            labels.append(klass.state_label)

    for label in labels:
        cipher = 'MATCH (n:{}) RETURN COUNT(*) as `total`'.format(label)
        with driver.session() as session:
            with session.begin_transaction() as tx:
                res = tx.run(cipher).single()
                counts[label] = res['total']
    return counts