fix: Convert UUIDField columns to uuid type for MariaDB (#37494)

The behavior of the MariaDB backend has changed behavior for UUIDField
from a `CharField(32)` to an actual `uuid` type. This is not converted
automatically, which results in all writes to the affected columns to
error with a message about the data being too long. This is because the
actual tring being written is a UUID with the `-` included, resulting in
a 36 character value which can't be inserted into a 32 character column.
This commit is contained in:
Tobias Macey
2025-10-20 10:32:44 -04:00
committed by GitHub
parent 264198f013
commit 0fdb6ed2fe
6 changed files with 514 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
# Generated migration for MariaDB UUID field conversion (Django 5.2)
"""
Migration to convert UUIDField from char(32) to uuid type for MariaDB compatibility.
This migration is necessary because Django 5 changed the behavior of UUIDField for MariaDB
databases from using CharField(32) to using a proper UUID type. This change isn't managed
automatically, so we need to generate migrations to safely convert the columns.
This migration only executes for MariaDB databases and is a no-op for other backends.
See: https://www.albertyw.com/note/django-5-mariadb-uuidfield
"""
from django.db import migrations
def apply_mariadb_migration(apps, schema_editor):
"""Apply the migration only for MariaDB databases."""
connection = schema_editor.connection
# Check if this is a MariaDB database
if connection.vendor != 'mysql':
return
# Additional check for MariaDB specifically (vs MySQL)
with connection.cursor() as cursor:
cursor.execute("SELECT VERSION()")
version = cursor.fetchone()[0]
if 'mariadb' not in version.lower():
return
# Apply the field changes for MariaDB
with connection.cursor() as cursor:
cursor.execute(
"ALTER TABLE entitlements_courseentitlement "
"MODIFY uuid uuid NOT NULL"
)
cursor.execute(
"ALTER TABLE entitlements_courseentitlement "
"MODIFY course_uuid uuid NOT NULL"
)
cursor.execute(
"ALTER TABLE entitlements_historicalcourseentitlement "
"MODIFY uuid uuid NOT NULL"
)
cursor.execute(
"ALTER TABLE entitlements_historicalcourseentitlement "
"MODIFY course_uuid uuid NOT NULL"
)
def reverse_mariadb_migration(apps, schema_editor):
"""Reverse the migration only for MariaDB databases."""
connection = schema_editor.connection
# Check if this is a MariaDB database
if connection.vendor != 'mysql':
return
# Additional check for MariaDB specifically (vs MySQL)
with connection.cursor() as cursor:
cursor.execute("SELECT VERSION()")
version = cursor.fetchone()[0]
if 'mariadb' not in version.lower():
return
# Reverse the field changes for MariaDB
with connection.cursor() as cursor:
cursor.execute(
"ALTER TABLE entitlements_courseentitlement "
"MODIFY uuid char(32) NOT NULL"
)
cursor.execute(
"ALTER TABLE entitlements_courseentitlement "
"MODIFY course_uuid char(32) NOT NULL"
)
cursor.execute(
"ALTER TABLE entitlements_historicalcourseentitlement "
"MODIFY uuid char(32) NOT NULL"
)
cursor.execute(
"ALTER TABLE entitlements_historicalcourseentitlement "
"MODIFY course_uuid char(32) NOT NULL"
)
class Migration(migrations.Migration):
dependencies = [
('entitlements', '0016_auto_20230808_0944'),
]
operations = [
migrations.RunPython(
code=apply_mariadb_migration,
reverse_code=reverse_mariadb_migration,
),
]

View File

@@ -0,0 +1,75 @@
# Generated migration for MariaDB UUID field conversion (Django 5.2)
"""
Migration to convert UUIDField from char(32) to uuid type for MariaDB compatibility.
This migration is necessary because Django 5 changed the behavior of UUIDField for MariaDB
databases from using CharField(32) to using a proper UUID type. This change isn't managed
automatically, so we need to generate migrations to safely convert the columns.
This migration only executes for MariaDB databases and is a no-op for other backends.
See: https://www.albertyw.com/note/django-5-mariadb-uuidfield
"""
from django.db import migrations
def apply_mariadb_migration(apps, schema_editor):
"""Apply the migration only for MariaDB databases."""
connection = schema_editor.connection
# Check if this is a MariaDB database
if connection.vendor != 'mysql':
return
# Additional check for MariaDB specifically (vs MySQL)
with connection.cursor() as cursor:
cursor.execute("SELECT VERSION()")
version = cursor.fetchone()[0]
if 'mariadb' not in version.lower():
return
# Apply the field changes for MariaDB
with connection.cursor() as cursor:
# The history_id is a primary key, so we need to be careful
cursor.execute(
"ALTER TABLE student_courseenrollment_history "
"MODIFY history_id uuid NOT NULL"
)
def reverse_mariadb_migration(apps, schema_editor):
"""Reverse the migration only for MariaDB databases."""
connection = schema_editor.connection
# Check if this is a MariaDB database
if connection.vendor != 'mysql':
return
# Additional check for MariaDB specifically (vs MySQL)
with connection.cursor() as cursor:
cursor.execute("SELECT VERSION()")
version = cursor.fetchone()[0]
if 'mariadb' not in version.lower():
return
# Reverse the field changes for MariaDB
with connection.cursor() as cursor:
cursor.execute(
"ALTER TABLE student_courseenrollment_history "
"MODIFY history_id char(32) NOT NULL"
)
class Migration(migrations.Migration):
dependencies = [
('student', '0047_courseaccessrolehistory'),
]
operations = [
migrations.RunPython(
code=apply_mariadb_migration,
reverse_code=reverse_mariadb_migration,
),
]

View File

@@ -0,0 +1,82 @@
# Generated migration for MariaDB UUID field conversion (Django 5.2)
"""
Migration to convert UUIDField from char(32) to uuid type for MariaDB compatibility.
This migration is necessary because Django 5 changed the behavior of UUIDField for MariaDB
databases from using CharField(32) to using a proper UUID type. This change isn't managed
automatically, so we need to generate migrations to safely convert the columns.
This migration only executes for MariaDB databases and is a no-op for other backends.
See: https://www.albertyw.com/note/django-5-mariadb-uuidfield
"""
from django.db import migrations
def apply_mariadb_migration(apps, schema_editor):
"""Apply the migration only for MariaDB databases."""
connection = schema_editor.connection
# Check if this is a MariaDB database
if connection.vendor != 'mysql':
return
# Additional check for MariaDB specifically (vs MySQL)
with connection.cursor() as cursor:
cursor.execute("SELECT VERSION()")
version = cursor.fetchone()[0]
if 'mariadb' not in version.lower():
return
# Apply the field changes for MariaDB
with connection.cursor() as cursor:
cursor.execute(
"ALTER TABLE course_goals_coursegoal "
"MODIFY unsubscribe_token uuid DEFAULT NULL"
)
cursor.execute(
"ALTER TABLE course_goals_historicalcoursegoal "
"MODIFY unsubscribe_token uuid DEFAULT NULL"
)
def reverse_mariadb_migration(apps, schema_editor):
"""Reverse the migration only for MariaDB databases."""
connection = schema_editor.connection
# Check if this is a MariaDB database
if connection.vendor != 'mysql':
return
# Additional check for MariaDB specifically (vs MySQL)
with connection.cursor() as cursor:
cursor.execute("SELECT VERSION()")
version = cursor.fetchone()[0]
if 'mariadb' not in version.lower():
return
# Reverse the field changes for MariaDB
with connection.cursor() as cursor:
cursor.execute(
"ALTER TABLE course_goals_coursegoal "
"MODIFY unsubscribe_token char(32) DEFAULT NULL"
)
cursor.execute(
"ALTER TABLE course_goals_historicalcoursegoal "
"MODIFY unsubscribe_token char(32) DEFAULT NULL"
)
class Migration(migrations.Migration):
dependencies = [
('course_goals', '0009_alter_historicalcoursegoal_options'),
]
operations = [
migrations.RunPython(
code=apply_mariadb_migration,
reverse_code=reverse_mariadb_migration,
),
]

View File

@@ -0,0 +1,98 @@
# Generated migration for MariaDB UUID field conversion (Django 5.2)
"""
Migration to convert UUIDField from char(32) to uuid type for MariaDB compatibility.
This migration is necessary because Django 5 changed the behavior of UUIDField for MariaDB
databases from using CharField(32) to using a proper UUID type. This change isn't managed
automatically, so we need to generate migrations to safely convert the columns.
This migration only executes for MariaDB databases and is a no-op for other backends.
See: https://www.albertyw.com/note/django-5-mariadb-uuidfield
"""
from django.db import migrations
def apply_mariadb_migration(apps, schema_editor):
"""Apply the migration only for MariaDB databases."""
connection = schema_editor.connection
# Check if this is a MariaDB database
if connection.vendor != 'mysql':
return
# Additional check for MariaDB specifically (vs MySQL)
with connection.cursor() as cursor:
cursor.execute("SELECT VERSION()")
version = cursor.fetchone()[0]
if 'mariadb' not in version.lower():
return
# Apply the field changes for MariaDB
with connection.cursor() as cursor:
cursor.execute(
"ALTER TABLE program_enrollments_programenrollment "
"MODIFY program_uuid uuid NOT NULL"
)
cursor.execute(
"ALTER TABLE program_enrollments_programenrollment "
"MODIFY curriculum_uuid uuid NOT NULL"
)
cursor.execute(
"ALTER TABLE program_enrollments_historicalprogramenrollment "
"MODIFY program_uuid uuid NOT NULL"
)
cursor.execute(
"ALTER TABLE program_enrollments_historicalprogramenrollment "
"MODIFY curriculum_uuid uuid NOT NULL"
)
def reverse_mariadb_migration(apps, schema_editor):
"""Reverse the migration only for MariaDB databases."""
connection = schema_editor.connection
# Check if this is a MariaDB database
if connection.vendor != 'mysql':
return
# Additional check for MariaDB specifically (vs MySQL)
with connection.cursor() as cursor:
cursor.execute("SELECT VERSION()")
version = cursor.fetchone()[0]
if 'mariadb' not in version.lower():
return
# Reverse the field changes for MariaDB
with connection.cursor() as cursor:
cursor.execute(
"ALTER TABLE program_enrollments_programenrollment "
"MODIFY program_uuid char(32) NOT NULL"
)
cursor.execute(
"ALTER TABLE program_enrollments_programenrollment "
"MODIFY curriculum_uuid char(32) NOT NULL"
)
cursor.execute(
"ALTER TABLE program_enrollments_historicalprogramenrollment "
"MODIFY program_uuid char(32) NOT NULL"
)
cursor.execute(
"ALTER TABLE program_enrollments_historicalprogramenrollment "
"MODIFY curriculum_uuid char(32) NOT NULL"
)
class Migration(migrations.Migration):
dependencies = [
('program_enrollments', '0011_auto_20230807_1905'),
]
operations = [
migrations.RunPython(
code=apply_mariadb_migration,
reverse_code=reverse_mariadb_migration,
),
]

View File

@@ -0,0 +1,86 @@
# Generated migration for MariaDB UUID field conversion (Django 5.2)
"""
Migration to convert UUIDField from char(32) to uuid type for MariaDB compatibility.
This migration is necessary because Django 5 changed the behavior of UUIDField for MariaDB
databases from using CharField(32) to using a proper UUID type. This change isn't managed
automatically, so we need to generate migrations to safely convert the columns.
This migration only executes for MariaDB databases and is a no-op for other backends.
See: https://www.albertyw.com/note/django-5-mariadb-uuidfield
"""
from django.db import migrations
def apply_mariadb_migration(apps, schema_editor):
"""Apply the migration only for MariaDB databases."""
connection = schema_editor.connection
# Check if this is a MariaDB database
if connection.vendor != 'mysql':
return
# Additional check for MariaDB specifically (vs MySQL)
with connection.cursor() as cursor:
cursor.execute("SELECT VERSION()")
version = cursor.fetchone()[0]
if 'mariadb' not in version.lower():
return
# Apply the field changes for MariaDB
with connection.cursor() as cursor:
# Convert external_user_id in externalid table
cursor.execute(
"ALTER TABLE external_user_ids_externalid "
"MODIFY external_user_id uuid NOT NULL"
)
# Convert external_user_id in historicalexternalid table
cursor.execute(
"ALTER TABLE external_user_ids_historicalexternalid "
"MODIFY external_user_id uuid NOT NULL"
)
def reverse_mariadb_migration(apps, schema_editor):
"""Reverse the migration only for MariaDB databases."""
connection = schema_editor.connection
# Check if this is a MariaDB database
if connection.vendor != 'mysql':
return
# Additional check for MariaDB specifically (vs MySQL)
with connection.cursor() as cursor:
cursor.execute("SELECT VERSION()")
version = cursor.fetchone()[0]
if 'mariadb' not in version.lower():
return
# Reverse the field changes for MariaDB
with connection.cursor() as cursor:
# Revert external_user_id in externalid table
cursor.execute(
"ALTER TABLE external_user_ids_externalid "
"MODIFY external_user_id char(32) NOT NULL"
)
# Revert external_user_id in historicalexternalid table
cursor.execute(
"ALTER TABLE external_user_ids_historicalexternalid "
"MODIFY external_user_id char(32) NOT NULL"
)
class Migration(migrations.Migration):
dependencies = [
('external_user_ids', '0008_remove_mbcoaching_extid_type'),
]
operations = [
migrations.RunPython(
code=apply_mariadb_migration,
reverse_code=reverse_mariadb_migration,
),
]

View File

@@ -0,0 +1,75 @@
# Generated migration for MariaDB UUID field conversion (Django 5.2)
"""
Migration to convert UUIDField from char(32) to uuid type for MariaDB compatibility.
This migration is necessary because Django 5 changed the behavior of UUIDField for MariaDB
databases from using CharField(32) to using a proper UUID type. This change isn't managed
automatically, so we need to generate migrations to safely convert the columns.
This migration only executes for MariaDB databases and is a no-op for other backends.
See: https://www.albertyw.com/note/django-5-mariadb-uuidfield
"""
from django.db import migrations
def apply_mariadb_migration(apps, schema_editor):
"""Apply the migration only for MariaDB databases."""
connection = schema_editor.connection
# Check if this is a MariaDB database
if connection.vendor != 'mysql':
return
# Additional check for MariaDB specifically (vs MySQL)
with connection.cursor() as cursor:
cursor.execute("SELECT VERSION()")
version = cursor.fetchone()[0]
if 'mariadb' not in version.lower():
return
# Apply the field changes for MariaDB
with connection.cursor() as cursor:
# The id field is a primary key
cursor.execute(
"ALTER TABLE survey_report_surveyreportanonymoussiteid "
"MODIFY id uuid NOT NULL"
)
def reverse_mariadb_migration(apps, schema_editor):
"""Reverse the migration only for MariaDB databases."""
connection = schema_editor.connection
# Check if this is a MariaDB database
if connection.vendor != 'mysql':
return
# Additional check for MariaDB specifically (vs MySQL)
with connection.cursor() as cursor:
cursor.execute("SELECT VERSION()")
version = cursor.fetchone()[0]
if 'mariadb' not in version.lower():
return
# Reverse the field changes for MariaDB
with connection.cursor() as cursor:
cursor.execute(
"ALTER TABLE survey_report_surveyreportanonymoussiteid "
"MODIFY id char(32) NOT NULL"
)
class Migration(migrations.Migration):
dependencies = [
('survey_report', '0005_surveyreportanonymoussiteid'),
]
operations = [
migrations.RunPython(
code=apply_mariadb_migration,
reverse_code=reverse_mariadb_migration,
),
]