def get_or_create_entity_history(entity, field, clock):
    """Gets an existing or creates a new entity history model
       for an entity temporal field.
    """
    new_value = getattr(entity, field)
    history_cls = entity._history[field]

    history_entity = session.query(history_cls).filter(
        history_cls.entity_id == entity.rid,
        history_cls.clock_id == clock.rid,
        history_cls.ticks.contains(clock.ticks),
    ).one_or_none()

    if not new_value:
        return history_entity, False
    if history_entity:
        if history_entity.value == new_value:
            return history_entity, False

        history_entity.ticks = pg_extras.NumericRange(
            history_entity.ticks.lower, clock.ticks, '[]')

    new_history_entity = history_cls()
    new_history_entity.entity = entity
    new_history_entity.clock = clock
    new_history_entity.ticks = pg_extras.NumericRange(clock.ticks + 1)
    new_history_entity.value = new_value
    session.add(new_history_entity)
    return new_history_entity, True
def int_range(lower=0, upper=None, nullable=False):
    """Postgres Int4Range column mapping.
    """
    return sa.Column(
        postgresql.INT4RANGE(),
        default=pg_extras.NumericRange(lower, upper),
        nullable=nullable)
示例#3
0
 def create_field_history(self, entity, field, clock, value):
     """creates a new entity field history.
     """
     history = entity._history[field]()
     history.entity = entity
     history.clock = clock
     history.ticks = pg_extras.NumericRange(clock.ticks + 1)
     history.value = value
     #  self.add(history)
     return history
 def create_field_history_bulk(self, entity, field, clock, value):
     """creates a new entity field history.
     """
     history = entity._history[field]()
     history.rid = uuid4()
     history.entity_id = entity.rid
     history.clock_id = clock.rid
     history.ticks = pg_extras.NumericRange(clock.ticks + 1)
     history.value = getattr(entity, field)
     return history, True
示例#5
0
    def make_clock(effective_lower: dt.datetime, vclock_lower: int,
                   **kwargs) -> _ClockSet:
        """construct a clock set tuple"""
        effective_upper = kwargs.get('effective_upper', None)
        vclock_upper = kwargs.get('vclock_upper', None)

        effective = psql_extras.DateTimeTZRange(effective_lower,
                                                effective_upper)
        vclock = psql_extras.NumericRange(vclock_lower, vclock_upper)

        return _ClockSet(effective, vclock)
示例#6
0
    def get_or_create_field_history(self, entity, field, clock):
        """gets an existing or creates a new entity history field
           for a temporal model.
        """
        new_value = getattr(entity, field)

        #if not new_value:
        #    return None, False

        if entity.rid:
            history = self.query(entity.__class__).field_history(
                entity, field, clock)

            if history:
                if history.value == new_value:
                    return history, False

                history.ticks = pg_extras.NumericRange(history.ticks.lower,
                                                       clock.ticks, '[]')

        new_history = self.create_field_history(entity, field, clock,
                                                new_value)

        return new_history, True
示例#7
0
    def _record_history(self, clocked: Clocked):
        """
        Record all history for a given clocked object

        Args:
            clocked (Clocked): instance of clocked object
        """

        #
        # Check for activity misuse
        #
        if clocked.temporal_options.activity_model is not None and clocked.activity is None:
            raise ValueError('An activity is required when saving a %s' %
                             type(clocked).__name__)
        if clocked.temporal_options.activity_model is None and clocked.activity is not None:
            raise ValueError(
                'There is no activity model for %s; you cannot supply an activity'
                % type(clocked).__name__)

        #
        # Determine which fields have changed
        #
        changed_fields = {}
        for field, history_model in self.history_models.items():
            new_val = clocked._meta.get_field(field).value_from_object(clocked)
            prev_val = clocked._state._django_temporal_previous[field]
            if new_val != prev_val or clocked._state._django_temporal_add:
                changed_fields[(field, history_model)] = new_val

        if not changed_fields:
            return

        #
        # Increment the clock and build the effective and vclock ranges for the next tick
        #
        timestamp = timezone.now()
        clocked.vclock += 1
        new_tick = clocked.vclock

        #
        # Create the EntityClock for this tick
        #
        clock_model = type(clocked).temporal_options.clock_model
        if clocked.temporal_options.activity_model is not None:
            clock = clock_model(entity=clocked,
                                activity=clocked.activity,
                                tick=new_tick)
        else:
            clock = clock_model(entity=clocked, tick=new_tick)
        clock.save()

        #
        # Create the field history for this tick
        #
        for (field, history_model), new_val in changed_fields.items():
            if new_tick > 1:
                # This is an update, not a create, so update the upper bounds of the previous tick.
                #
                # This cannot be done with F expressions because of the range updates, unless we write our
                # own implementations of int4range/tstzrange.
                #
                # Instead use .raw and force execution by wrapping with list(). This approach ensures that
                # the update happens in the correct connection/transaction.
                list(
                    history_model.objects.raw(
                        """ UPDATE {table_name}
                        SET vclock = int4range(lower(vclock), %s),
                            effective = tstzrange(lower(effective), %s)
                        WHERE entity_id = %s AND upper(vclock) IS NULL
                        RETURNING id;
                    """.format(table_name=connection.ops.quote_name(
                            history_model._meta.db_table)),
                        [new_tick, timestamp, clocked.pk]))

            hist = history_model(**{field: new_val},
                                 entity=clocked,
                                 vclock=psql_extras.NumericRange(
                                     new_tick, None),
                                 effective=psql_extras.DateTimeTZRange(
                                     timestamp, None))
            hist.save()

            # Update the stored state for this field to detect future changes
            clocked._state._django_temporal_previous[field] = new_val

        # Reset the activity so it can't be accidentally reused easily
        clocked.activity = None

        # Update the vclock value without triggering a recursive `record_history`
        type(clocked).objects \
            .filter(**{clocked._meta.pk.name: getattr(clocked, clocked._meta.pk.name)}) \
            .update(vclock=new_tick)