feat: migrate database driver from MariaDB to PostgreSQL

Switch from pymysql to psycopg2-binary, update connection string to
postgresql+psycopg2, replace MySQL-specific JSON imports with generic
SQLAlchemy JSON, convert MySQL prefix index to PostgreSQL left() function,
and remove FetchedValue() (unnecessary with PostgreSQL).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-03-02 08:25:05 +00:00
parent bbcb0c489a
commit e9eb98119f
8 changed files with 11 additions and 15 deletions

View file

@ -1,6 +1,6 @@
# Database # Database
DB_HOST= DB_HOST=
DB_PORT=3306 DB_PORT=5432
DB_NAME=dak_c2s DB_NAME=dak_c2s
DB_USER=dak_c2s_admin DB_USER=dak_c2s_admin
DB_PASSWORD= DB_PASSWORD=

View file

@ -8,7 +8,7 @@ from functools import lru_cache
class Settings(BaseSettings): class Settings(BaseSettings):
# Database # Database
DB_HOST: str = "localhost" DB_HOST: str = "localhost"
DB_PORT: int = 3306 DB_PORT: int = 5432
DB_NAME: str = "dak_c2s" DB_NAME: str = "dak_c2s"
DB_USER: str = "dak_c2s_admin" DB_USER: str = "dak_c2s_admin"
DB_PASSWORD: str = "" DB_PASSWORD: str = ""
@ -37,8 +37,8 @@ class Settings(BaseSettings):
def database_url(self) -> str: def database_url(self) -> str:
password = quote_plus(self.DB_PASSWORD) password = quote_plus(self.DB_PASSWORD)
return ( return (
f"mysql+pymysql://{self.DB_USER}:{password}" f"postgresql+psycopg2://{self.DB_USER}:{password}"
f"@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}?charset=utf8mb4" f"@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
) )
class Config: class Config:

View file

@ -12,11 +12,11 @@ from sqlalchemy import (
ForeignKey, ForeignKey,
Index, Index,
Integer, Integer,
JSON,
String, String,
Text, Text,
func, func,
) )
from sqlalchemy.dialects.mysql import JSON
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base from app.database import Base

View file

@ -21,7 +21,6 @@ from sqlalchemy import (
text, text,
) )
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.schema import FetchedValue
from app.database import Base from app.database import Base
@ -115,7 +114,6 @@ class Case(Base):
nullable=False, nullable=False,
server_default=func.now(), server_default=func.now(),
onupdate=func.now(), onupdate=func.now(),
server_onupdate=FetchedValue(),
) )
updated_by: Mapped[Optional[int]] = mapped_column( updated_by: Mapped[Optional[int]] = mapped_column(
Integer, ForeignKey("users.id"), nullable=True Integer, ForeignKey("users.id"), nullable=True
@ -154,9 +152,9 @@ class Case(Base):
Index("idx_fallgruppe", "fallgruppe"), Index("idx_fallgruppe", "fallgruppe"),
Index("idx_datum", "datum"), Index("idx_datum", "datum"),
Index("idx_nachname_vorname", "nachname", "vorname"), Index("idx_nachname_vorname", "nachname", "vorname"),
# Note: idx_pending_icd uses a prefix length icd(20) which SQLAlchemy # Note: idx_pending_icd uses a functional index on left(icd, 20).
# does not support natively. We use text() for the prefix column. # We use text() for the expression column.
Index("idx_pending_icd", "jahr", "kw", "fallgruppe", text("icd(20)")), Index("idx_pending_icd", "jahr", "kw", "fallgruppe", text("left(icd, 20)")),
Index("idx_pending_coding", "gutachten", "gutachten_typ"), Index("idx_pending_coding", "gutachten", "gutachten_typ"),
CheckConstraint( CheckConstraint(
"fallgruppe IN ('onko','kardio','intensiv','galle','sd')", "fallgruppe IN ('onko','kardio','intensiv','galle','sd')",

View file

@ -11,12 +11,12 @@ from sqlalchemy import (
ForeignKey, ForeignKey,
Index, Index,
Integer, Integer,
JSON,
SmallInteger, SmallInteger,
String, String,
UniqueConstraint, UniqueConstraint,
func, func,
) )
from sqlalchemy.dialects.mysql import JSON
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base from app.database import Base

View file

@ -17,7 +17,6 @@ from sqlalchemy import (
func, func,
) )
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.schema import FetchedValue
from app.database import Base from app.database import Base
@ -59,7 +58,6 @@ class User(Base):
nullable=False, nullable=False,
server_default=func.now(), server_default=func.now(),
onupdate=func.now(), onupdate=func.now(),
server_onupdate=FetchedValue(),
) )
# Relationships # Relationships

View file

@ -3,7 +3,7 @@ uvicorn[standard]==0.34.0
gunicorn==23.0.0 gunicorn==23.0.0
sqlalchemy==2.0.36 sqlalchemy==2.0.36
alembic==1.14.1 alembic==1.14.1
pymysql==1.1.1 psycopg2-binary==2.9.10
cryptography==44.0.0 cryptography==44.0.0
python-jose[cryptography]==3.3.0 python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4 passlib[bcrypt]==1.7.4

View file

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=DAK Zweitmeinungs-Portal Backend (FastAPI/Uvicorn) Description=DAK Zweitmeinungs-Portal Backend (FastAPI/Uvicorn)
After=network.target mariadb.service After=network.target postgresql.service
[Service] [Service]
Type=simple Type=simple