def make_declarations(cls, container_name, event_type, table_name, table_args, unique_parent):
"""
Declare columns and indexes.
An event assumes the following:
- Each event belongs to (and has a foreign key to) another "container" table.
Typically, the container table has immutable rows that define the context of an entity
and events track state changes to rows of this table.
- Each event has a well-defined type, derived from an enumeration.
- Each event has a well-defined state, consisting of one or more enumerated values.
- Each event has an integer version, starting from one.
Not every event uses versions; those that do will generally have a uniqueness contraint
on some event types per version of a container row.
- Each event has a nullable parent event id where a null value represents the first event
in a version and subsequent event have a unique parent id to ensure semantic ordering.
- Each event has a non-nullable serial clock to ensure total ordering.
"""
container_id = "{}.id".format(container_name)
container_id_name = "{}_id".format(container_name)
parent_id = "{}.id".format(table_name)
return {
# columns
container_id_name: Column(UUIDType, ForeignKey(container_id), nullable=False),
"event_type": Column(EnumType(event_type), nullable=False),
"clock": Column(Serial, server_default=FetchedValue(), nullable=False, unique=True),
"parent_id": Column(UUIDType, ForeignKey(parent_id), nullable=True, unique=unique_parent),
"state": Column(ARRAY(EnumType(event_type)), nullable=False, default=default_state),
"version": Column(Integer, default=1, nullable=False),
# shortcuts
"container_id": ColumnAlias(container_id_name),
"container_id_name": container_id_name,
# indexes and constraints
"__table_args__": table_args + cls.make_table_args(cls, table_name, container_id_name, event_type),
}