From 0515e64af644dd06445ed18d29edd78cd5cfa613 Mon Sep 17 00:00:00 2001 From: Tobias Macey Date: Thu, 16 Oct 2025 16:46:34 -0400 Subject: [PATCH] fix: Convert UUIDField columns to uuid type for MariaDB 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. --- .../0017_mariadb_uuid_conversion.py | 98 +++++++++++++++++++ .../0048_mariadb_uuid_conversion.py | 75 ++++++++++++++ .../0010_mariadb_uuid_conversion.py | 82 ++++++++++++++++ .../0012_mariadb_uuid_conversion.py | 98 +++++++++++++++++++ .../0009_mariadb_uuid_conversion.py | 86 ++++++++++++++++ .../0006_mariadb_uuid_conversion.py | 75 ++++++++++++++ 6 files changed, 514 insertions(+) create mode 100644 common/djangoapps/entitlements/migrations/0017_mariadb_uuid_conversion.py create mode 100644 common/djangoapps/student/migrations/0048_mariadb_uuid_conversion.py create mode 100644 lms/djangoapps/course_goals/migrations/0010_mariadb_uuid_conversion.py create mode 100644 lms/djangoapps/program_enrollments/migrations/0012_mariadb_uuid_conversion.py create mode 100644 openedx/core/djangoapps/external_user_ids/migrations/0009_mariadb_uuid_conversion.py create mode 100644 openedx/features/survey_report/migrations/0006_mariadb_uuid_conversion.py diff --git a/common/djangoapps/entitlements/migrations/0017_mariadb_uuid_conversion.py b/common/djangoapps/entitlements/migrations/0017_mariadb_uuid_conversion.py new file mode 100644 index 000000000000..4932db508aad --- /dev/null +++ b/common/djangoapps/entitlements/migrations/0017_mariadb_uuid_conversion.py @@ -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, + ), + ] diff --git a/common/djangoapps/student/migrations/0048_mariadb_uuid_conversion.py b/common/djangoapps/student/migrations/0048_mariadb_uuid_conversion.py new file mode 100644 index 000000000000..14d93ec19263 --- /dev/null +++ b/common/djangoapps/student/migrations/0048_mariadb_uuid_conversion.py @@ -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, + ), + ] diff --git a/lms/djangoapps/course_goals/migrations/0010_mariadb_uuid_conversion.py b/lms/djangoapps/course_goals/migrations/0010_mariadb_uuid_conversion.py new file mode 100644 index 000000000000..8fea22984269 --- /dev/null +++ b/lms/djangoapps/course_goals/migrations/0010_mariadb_uuid_conversion.py @@ -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, + ), + ] diff --git a/lms/djangoapps/program_enrollments/migrations/0012_mariadb_uuid_conversion.py b/lms/djangoapps/program_enrollments/migrations/0012_mariadb_uuid_conversion.py new file mode 100644 index 000000000000..f5260a7a5307 --- /dev/null +++ b/lms/djangoapps/program_enrollments/migrations/0012_mariadb_uuid_conversion.py @@ -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, + ), + ] diff --git a/openedx/core/djangoapps/external_user_ids/migrations/0009_mariadb_uuid_conversion.py b/openedx/core/djangoapps/external_user_ids/migrations/0009_mariadb_uuid_conversion.py new file mode 100644 index 000000000000..eddb1608856e --- /dev/null +++ b/openedx/core/djangoapps/external_user_ids/migrations/0009_mariadb_uuid_conversion.py @@ -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, + ), + ] diff --git a/openedx/features/survey_report/migrations/0006_mariadb_uuid_conversion.py b/openedx/features/survey_report/migrations/0006_mariadb_uuid_conversion.py new file mode 100644 index 000000000000..13f1d284d792 --- /dev/null +++ b/openedx/features/survey_report/migrations/0006_mariadb_uuid_conversion.py @@ -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, + ), + ]