Skip to content
Snippets Groups Projects
Commit b7fbed22 authored by Simon Künzel's avatar Simon Künzel
Browse files

Add some database constraints

parent ab6eb66f
No related branches found
No related tags found
No related merge requests found
...@@ -6,7 +6,7 @@ from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method ...@@ -6,7 +6,7 @@ from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
from sqlalchemy.sql.base import ExecutableOption from sqlalchemy.sql.base import ExecutableOption
from sqlalchemy.orm import Session as SessionDb, DeclarativeBase, Mapped, mapped_column, relationship from sqlalchemy.orm import Session as SessionDb, DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy.types import Text, String, TIMESTAMP from sqlalchemy.types import Text, String, TIMESTAMP
from sqlalchemy import types, ForeignKey from sqlalchemy import types, ForeignKey, CheckConstraint, UniqueConstraint, ForeignKeyConstraint
from .base import ( from .base import (
Base, Base,
......
...@@ -48,15 +48,15 @@ def _to_string(value) -> str: ...@@ -48,15 +48,15 @@ def _to_string(value) -> str:
case PrimaryKeyConstraint() as constraint: case PrimaryKeyConstraint() as constraint:
assert isinstance(constraint, PrimaryKeyConstraint) assert isinstance(constraint, PrimaryKeyConstraint)
return f"PrimaryKeyConstraint({_to_string(constraint.columns)}, table.name={constraint.table.name})" return f"PrimaryKeyConstraint({_to_string(constraint.columns)}, table.name='{constraint.table.name}')"
case CheckConstraint() as constraint: case CheckConstraint() as constraint:
assert isinstance(constraint, CheckConstraint) assert isinstance(constraint, CheckConstraint)
return f"CheckConstraint({_to_string(constraint.columns)}, sqltext={constraint.sqltext} table.name={constraint.table.name})" return f"CheckConstraint({_to_string(constraint.columns)}, sqltext='{constraint.sqltext}', table.name='{constraint.table.name}')"
case UniqueConstraint() as constraint: case UniqueConstraint() as constraint:
assert isinstance(constraint, UniqueConstraint) assert isinstance(constraint, UniqueConstraint)
return f"UniqueConstraint({_to_string(constraint.columns)}, table.name={constraint.table.name})" return f"UniqueConstraint({_to_string(constraint.columns)}, table.name='{constraint.table.name}')"
case ForeignKeyConstraint() as constraint: case ForeignKeyConstraint() as constraint:
assert isinstance(constraint, ForeignKeyConstraint) assert isinstance(constraint, ForeignKeyConstraint)
...@@ -112,9 +112,14 @@ def _check_constraint_equal(actual_constraint: Constraint, schema_constraint: Co ...@@ -112,9 +112,14 @@ def _check_constraint_equal(actual_constraint: Constraint, schema_constraint: Co
if not isinstance(actual_constraint, CheckConstraint): if not isinstance(actual_constraint, CheckConstraint):
return False return False
if schema_constraint.sqltext != actual_constraint.sqltext: # TODO test if this works
return False # We try our best to check if they are equal
if schema_constraint.sqltext == actual_constraint.sqltext:
return True return True
# Sometimes the db returns it without correct brackets. Just strip them
if str(schema_constraint.sqltext).strip("()") == str(actual_constraint.sqltext).strip("()"):
return True
return False
case ForeignKeyConstraint(): case ForeignKeyConstraint():
assert isinstance(schema_constraint, ForeignKeyConstraint) # For pycharm assert isinstance(schema_constraint, ForeignKeyConstraint) # For pycharm
...@@ -342,14 +347,14 @@ def _check_table_equal(actual_table: Table, schema_table: Table) -> bool: ...@@ -342,14 +347,14 @@ def _check_table_equal(actual_table: Table, schema_table: Table) -> bool:
break break
else: else:
correct = False correct = False
print(f"Missing constraint\n {_to_string(schema_constraint)}\nin database. The following constraints do " print(f"Missing constraint\n {_to_string(schema_constraint)}\nin database for table {schema_table.name}. "
f"not match:") f"The following constraints do not match:")
print("\n".join(map(lambda c: f" {_to_string(c)}", actual_table.constraints))) print("\n".join(map(lambda c: f" {_to_string(c)}", actual_table.constraints)))
if len(unmatched_actual_constraints) > 0: if len(unmatched_actual_constraints) > 0:
correct = False correct = False
print(f"Got unexpected constraint\n {_to_string(next(iter(unmatched_actual_constraints)))}\nin database. The " print(f"Got unexpected constraint\n {_to_string(next(iter(unmatched_actual_constraints)))}\nin database for "
f"following schema constraints do not match:") f"table {schema_table.name}. The following schema constraints do not match:")
print("\n".join(map(lambda c: f" {_to_string(c)}", schema_table.constraints))) print("\n".join(map(lambda c: f" {_to_string(c)}", schema_table.constraints)))
return correct return correct
......
...@@ -22,6 +22,17 @@ _JOB_STATE_ENUM = create_enum_type(JobState) ...@@ -22,6 +22,17 @@ _JOB_STATE_ENUM = create_enum_type(JobState)
class Job(ApiObject, Base): class Job(ApiObject, Base):
__table_args__ = (
CheckConstraint(
"(on_end_event_type IS NULL) = (on_end_event_data IS NULL)",
name="check_event",
comment="Event type and data must be both set or both null"
),
CheckConstraint(
"cause_job_id IS NULL OR cause_user_id IS NULL",
name="check_only_one_cause"
)
)
status: Mapped[JobState] = api_mapped( status: Mapped[JobState] = api_mapped(
mapped_column(_JOB_STATE_ENUM, nullable=False, index=True, default=JobState.READY), mapped_column(_JOB_STATE_ENUM, nullable=False, index=True, default=JobState.READY),
...@@ -48,7 +59,6 @@ class Job(ApiObject, Base): ...@@ -48,7 +59,6 @@ class Job(ApiObject, Base):
) )
) )
# TODO check both null or not null
on_end_event_type: Mapped[str] = api_mapped( on_end_event_type: Mapped[str] = api_mapped(
mapped_column(String(collation=STRING_COLLATION), nullable=True), mapped_column(String(collation=STRING_COLLATION), nullable=True),
ApiStringField( ApiStringField(
...@@ -79,7 +89,6 @@ class Job(ApiObject, Base): ...@@ -79,7 +89,6 @@ class Job(ApiObject, Base):
include_in_data=True include_in_data=True
) )
) )
# TODO check only one
cause_job_id: Mapped[int] = mapped_column(ForeignKey("job.id"), nullable=True, index=True) cause_job_id: Mapped[int] = mapped_column(ForeignKey("job.id"), nullable=True, index=True)
cause_user_id: Mapped[int] = mapped_column(ForeignKey("user.id"), nullable=True, index=True) cause_user_id: Mapped[int] = mapped_column(ForeignKey("user.id"), nullable=True, index=True)
......
...@@ -6,12 +6,24 @@ from videoag_common.miscellaneous import * ...@@ -6,12 +6,24 @@ from videoag_common.miscellaneous import *
from videoag_common.api_object import * from videoag_common.api_object import *
from videoag_common.media_process import * from videoag_common.media_process import *
from .course import Lecture, Course from .course import Lecture
from .job import Job from .job import Job
# A file which was uploaded and needs to be assigned to a lecture, to be converted to a SourceFileTargetMedium # A file which was uploaded and needs to be assigned to a lecture, to be converted to a SourceFileTargetMedium
class SourceMedium(VisibilityApiObject, DeletableApiObject, Base): class SourceMedium(VisibilityApiObject, DeletableApiObject, Base):
__table_args__ = (
CheckConstraint(
"NOT tag IS NULL OR lecture_id IS NULL",
name="check_tag_is_set",
comment="Tag must be set if this has a lecture id"
),
CheckConstraint(
"NOT sha256 IS NULL OR lecture_id IS NULL",
name="check_sha256_is_set",
comment="sha256 must be set if this has a lecture id"
),
)
file_path: Mapped[str] = api_mapped( file_path: Mapped[str] = api_mapped(
mapped_column(String(collation=STRING_COLLATION), nullable=False, index=True), # Can't be unique because of deleted entries mapped_column(String(collation=STRING_COLLATION), nullable=False, index=True), # Can't be unique because of deleted entries
...@@ -41,6 +53,7 @@ class SourceMedium(VisibilityApiObject, DeletableApiObject, Base): ...@@ -41,6 +53,7 @@ class SourceMedium(VisibilityApiObject, DeletableApiObject, Base):
include_in_data=True include_in_data=True
) )
) )
lecture_id: Mapped[int] = mapped_column(ForeignKey("lecture.id"), nullable=True, index=True)
sha256: Mapped[str] = api_mapped( sha256: Mapped[str] = api_mapped(
mapped_column(String(length=64, collation=STRING_COLLATION), nullable=True), mapped_column(String(length=64, collation=STRING_COLLATION), nullable=True),
ApiStringField( ApiStringField(
...@@ -48,15 +61,8 @@ class SourceMedium(VisibilityApiObject, DeletableApiObject, Base): ...@@ -48,15 +61,8 @@ class SourceMedium(VisibilityApiObject, DeletableApiObject, Base):
data_notes="Only calculated once this is sorted" data_notes="Only calculated once this is sorted"
) )
) )
# TODO check course not null if lecture is not null
course_id: Mapped[int] = mapped_column(ForeignKey("course.id"), nullable=True, index=True)
lecture_id: Mapped[int] = mapped_column(ForeignKey("lecture.id"), nullable=True, index=True)
tag: Mapped[str] = mapped_column(String(collation=STRING_COLLATION), nullable=True) tag: Mapped[str] = mapped_column(String(collation=STRING_COLLATION), nullable=True)
course: Mapped[Course] = relationship(
primaryjoin=lambda: Course.id == SourceMedium.course_id,
lazy="raise_on_sql"
)
lecture: Mapped[Lecture] = relationship( lecture: Mapped[Lecture] = relationship(
back_populates="source_media", back_populates="source_media",
primaryjoin=lambda: Lecture.id == SourceMedium.lecture_id, primaryjoin=lambda: Lecture.id == SourceMedium.lecture_id,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment