From 86d2a02e259dd2b7c6a097cec07fc1761749e694 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 8 Jan 2021 23:16:10 +0000 Subject: [PATCH] First pass at tables for sharing logs with the teams This introduces the concept of a Session, which may or not map exactly to an actual session of matches, that a given collection of archives is used for. Running a Session then generates various Artefacts which we want to share with the teams (logs, animations, possibly other things). We assume that for some types of Artefact, teams should only able to view those which relate to their robot. With thanks to James for design discussion. Co-Authored-By: James Seden Smith --- code_submitter/tables.py | 94 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/code_submitter/tables.py b/code_submitter/tables.py index dfb7b6d..b489ecb 100644 --- a/code_submitter/tables.py +++ b/code_submitter/tables.py @@ -1,7 +1,11 @@ +import enum + import sqlalchemy metadata = sqlalchemy.MetaData() +# As a team member you upload your archives prior to their being used to +# simulate matches. Archive = sqlalchemy.Table( 'archive', metadata, @@ -19,6 +23,8 @@ ), ) +# As a team member you choose which of your uploaded archives is the one which +# should be used for simulating matches. ChoiceHistory = sqlalchemy.Table( 'choice_history', metadata, @@ -34,3 +40,91 @@ server_default=sqlalchemy.func.now(), ), ) + + +# At the point of downloading the archives in order to run matches, you create a +# Session. The act of doing that will also create the required ChoiceForSession +# rows to record which items will be contained in the download. +Session = sqlalchemy.Table( + 'session', + metadata, + sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True), + sqlalchemy.Column('description', sqlalchemy.String, nullable=False), + + sqlalchemy.Column('username', sqlalchemy.String, nullable=False), + + sqlalchemy.Column( + 'created', + sqlalchemy.DateTime(timezone=True), + nullable=False, + server_default=sqlalchemy.func.now(), + ), +) + +ChoiceForSession = sqlalchemy.Table( + 'choice_for_session', + metadata, + sqlalchemy.Column( + 'choice_id', + sqlalchemy.ForeignKey('choice_history.id'), + primary_key=True, + ), + sqlalchemy.Column( + 'session_id', + sqlalchemy.ForeignKey('session.id'), + primary_key=True, + ), +) + + +# After matches have been simulated, there are a number of artefacts which +# should be shared with the teams. + +class ArtefactType(enum.Enum): + Logs = 'logs' + Animations = 'animations' + + +Artefact = sqlalchemy.Table( + 'artefact', + metadata, + sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True), + sqlalchemy.Column('content', sqlalchemy.LargeBinary, nullable=False), + sqlalchemy.Column('type', sqlalchemy.Enum(ArtefactType), nullable=False), + + # Compound foreign key to the ChoiceForSession. This provides a link to the + # team this artefact is availalbe to. When `choice_id` is null, this + # indicates that the artefact is available to all teams. + sqlalchemy.Column( + 'choice_id', + sqlalchemy.ForeignKey('choice.id'), + nullable=True, + ), + sqlalchemy.Column( + 'session_id', + sqlalchemy.ForeignKey('session.id'), + nullable=False, + ), + sqlalchemy.ForeignKeyConstraint( + ('choice_id', 'session_id'), + ('choice_for_session.choice_id', 'choice_for_session.session_id'), + # This foreign key constraint only applies when both fields are populated + match='SIMPLE', + ), + + sqlalchemy.Column( + 'created', + sqlalchemy.DateTime(timezone=True), + nullable=False, + server_default=sqlalchemy.func.now(), + ), +) + +# Constrain session_id such that we only allow one globally-visible artefact +# per session. This is to prevent accidentally uploading everything as +# visible to everyone. +sqlalchemy.Index( + 'unique_globally_visible_artefact_per_session', + unique=True, + sqlite_where=Artefact.c.choice_id.is_(None), +)