Last active
December 12, 2025 14:46
-
-
Save rafaelurben/59c0b9887026891630e5859449a98306 to your computer and use it in GitHub Desktop.
MariaDB char(32) to native uuid migration generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- ============================================================================ | |
| -- UUID migration SQL GENERATOR for MariaDB 10.11.14 | |
| -- Produces statements to migrate UUID-like CHAR(32) columns to native UUID. | |
| -- Usage: run this script in the target database. It will OUTPUT SQL statements | |
| -- which you should REVIEW and run in the order indicated below. | |
| -- | |
| -- This can be particularly helpful when migrating Django apps, as Django used | |
| -- to use char(32) for UUIDFields but switched to uuid in django 5.0. | |
| -- | |
| -- NOTE: Pay attention if you have non-uuid char(32) columns! | |
| -- | |
| -- Created by rafaelurben and ChatGPT | |
| -- ============================================================================ | |
| -- 0) Parameters: change if you need a different database than DATABASE() | |
| SET @target_db = DATABASE(); | |
| -- ======================================================================== | |
| -- A) Identify candidate columns: CHAR(32) | |
| -- Result: list of table/column IS_NULLABLE and type info | |
| -- ======================================================================== | |
| SELECT TABLE_SCHEMA, | |
| TABLE_NAME, | |
| COLUMN_NAME, | |
| DATA_TYPE, | |
| COLUMN_TYPE, | |
| CHARACTER_MAXIMUM_LENGTH, | |
| IS_NULLABLE | |
| FROM INFORMATION_SCHEMA.COLUMNS | |
| WHERE TABLE_SCHEMA = @target_db | |
| AND (DATA_TYPE = 'char' AND CHARACTER_MAXIMUM_LENGTH = 32) | |
| ORDER BY TABLE_NAME, ORDINAL_POSITION; | |
| -- ======================================================================== | |
| -- B) Generate ADD COLUMN statements (adds <col>_tmp UUID NULL/NOT NULL) | |
| -- These statements are safe to run repeatedly; they check for existing tmp col. | |
| -- ======================================================================== | |
| SELECT CONCAT( | |
| 'ALTER TABLE `', TABLE_NAME, '` ADD COLUMN `', COLUMN_NAME, '_tmp` UUID ', | |
| (CASE WHEN IS_NULLABLE = 'NO' THEN 'NOT NULL' ELSE 'NULL' END), ';' | |
| ) AS add_tmp_sql | |
| FROM INFORMATION_SCHEMA.COLUMNS | |
| WHERE TABLE_SCHEMA = @target_db | |
| AND (DATA_TYPE = 'char' AND CHARACTER_MAXIMUM_LENGTH = 32) | |
| ORDER BY TABLE_NAME, COLUMN_NAME; | |
| -- ======================================================================== | |
| -- C) Generate UPDATE statements to populate tmp columns safely | |
| -- Handles char(32). Produces per-column UPDATE. | |
| -- ======================================================================== | |
| SELECT CONCAT( | |
| 'UPDATE `', TABLE_NAME, '`\n', | |
| 'SET `', COLUMN_NAME, '_tmp` = (\n', | |
| ' CASE\n', | |
| ' WHEN `', COLUMN_NAME, '` IS NULL OR `', COLUMN_NAME, '` = \'\' THEN NULL\n', | |
| ' WHEN LENGTH(`', COLUMN_NAME, '`) = 32 AND `', COLUMN_NAME, | |
| '` REGEXP ''^[0-9A-Fa-f]{32}$'' THEN (\n', | |
| ' LOWER(CONCAT(\n', | |
| ' SUBSTR(`', COLUMN_NAME, '`,1,8),''-'',\n', | |
| ' SUBSTR(`', COLUMN_NAME, '`,9,4),''-'',\n', | |
| ' SUBSTR(`', COLUMN_NAME, '`,13,4),''-'',\n', | |
| ' SUBSTR(`', COLUMN_NAME, '`,17,4),''-'',\n', | |
| ' SUBSTR(`', COLUMN_NAME, '`,21,12)\n', | |
| ' ))\n', | |
| ' )\n', | |
| ' ELSE NULL\n', | |
| ' END\n', | |
| ')\n', | |
| 'WHERE 1;' | |
| ) AS populate_tmp_sql | |
| FROM (SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH | |
| FROM INFORMATION_SCHEMA.COLUMNS | |
| WHERE TABLE_SCHEMA = @target_db | |
| AND (DATA_TYPE = 'char' AND CHARACTER_MAXIMUM_LENGTH = 32)) AS cols | |
| ORDER BY TABLE_NAME, COLUMN_NAME; | |
| -- ======================================================================== | |
| -- D) Generate verification check | |
| -- ======================================================================== | |
| SELECT CONCAT( | |
| GROUP_CONCAT( | |
| CONCAT( | |
| 'SELECT ''', TABLE_NAME, '.', COLUMN_NAME, ''' AS column_ident, ', | |
| 'COUNT(*) AS total_rows, ', | |
| 'SUM(CASE WHEN `', COLUMN_NAME, '` IS NOT NULL AND `', COLUMN_NAME, '` <> '''' AND `', | |
| COLUMN_NAME, '_tmp` IS NULL THEN 1 ELSE 0 END) AS conversion_failures, ', | |
| 'GROUP_CONCAT(CASE WHEN `', COLUMN_NAME, '` IS NOT NULL AND `', COLUMN_NAME, | |
| '` <> '''' AND `', COLUMN_NAME, '_tmp` IS NULL ', | |
| 'THEN CONCAT(''('', `', COLUMN_NAME, '`, '','', `', COLUMN_NAME, | |
| '_tmp`, '')'') END SEPARATOR ''; '') AS sample_failed_values ', | |
| 'FROM `', TABLE_NAME, '`' | |
| ) | |
| SEPARATOR ' UNION ALL ' | |
| ), | |
| ';' | |
| ) AS full_verification_sql | |
| FROM INFORMATION_SCHEMA.COLUMNS | |
| WHERE TABLE_SCHEMA = @target_db | |
| AND DATA_TYPE = 'char' | |
| AND CHARACTER_MAXIMUM_LENGTH = 32; | |
| -- ======================================================================== | |
| -- E) Generate statements to DROP keys and indices reference any of these columns. | |
| -- ======================================================================== | |
| -- E.1) Drop Foreign keys | |
| SELECT DISTINCT CONCAT( | |
| 'ALTER TABLE `', kcu.TABLE_SCHEMA, '`.`', kcu.TABLE_NAME, '` DROP FOREIGN KEY `', | |
| kcu.CONSTRAINT_NAME, '`;' | |
| ) AS drop_fk_sql | |
| FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu | |
| JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc | |
| ON kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA | |
| AND kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME | |
| WHERE kcu.REFERENCED_TABLE_SCHEMA = @target_db | |
| AND EXISTS ( | |
| -- ensure this constraint references a column that we plan to migrate | |
| SELECT 1 | |
| FROM INFORMATION_SCHEMA.COLUMNS c | |
| WHERE c.TABLE_SCHEMA = @target_db | |
| AND c.TABLE_NAME = kcu.REFERENCED_TABLE_NAME | |
| AND c.COLUMN_NAME = kcu.REFERENCED_COLUMN_NAME | |
| AND (DATA_TYPE = 'char' AND CHARACTER_MAXIMUM_LENGTH = 32)) | |
| ORDER BY drop_fk_sql; | |
| -- E.2) Drop Primary keys | |
| SELECT DISTINCT CONCAT('ALTER TABLE `', TABLE_SCHEMA, '`.`', TABLE_NAME, '` DROP PRIMARY KEY;') AS drop_pk_sql | |
| FROM INFORMATION_SCHEMA.COLUMNS | |
| WHERE TABLE_SCHEMA = DATABASE() | |
| AND DATA_TYPE = 'char' | |
| AND CHARACTER_MAXIMUM_LENGTH = 32 | |
| AND COLUMN_KEY = 'PRI'; | |
| -- E.3) Drop Unique indexes | |
| SELECT DISTINCT CONCAT( | |
| 'ALTER TABLE `', tc.TABLE_SCHEMA, '`.`', tc.TABLE_NAME, '` DROP INDEX `', tc.CONSTRAINT_NAME, | |
| '`;' | |
| ) AS drop_unique_sql | |
| FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc | |
| JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu | |
| ON tc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA | |
| AND tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME | |
| WHERE tc.CONSTRAINT_TYPE = 'UNIQUE' | |
| AND tc.CONSTRAINT_SCHEMA = DATABASE() | |
| AND EXISTS (SELECT 1 | |
| FROM INFORMATION_SCHEMA.COLUMNS c | |
| WHERE c.TABLE_SCHEMA = DATABASE() | |
| AND c.TABLE_NAME = kcu.TABLE_NAME | |
| AND c.COLUMN_NAME = kcu.COLUMN_NAME | |
| AND c.DATA_TYPE = 'char' | |
| AND c.CHARACTER_MAXIMUM_LENGTH = 32) | |
| ORDER BY tc.TABLE_NAME, tc.CONSTRAINT_NAME; | |
| -- ======================================================================== | |
| -- F) Generate final ALTER statements to drop old column and rename tmp -> original | |
| -- These are the statements you run AFTER verification and AFTER DROP FK statements. | |
| -- ======================================================================== | |
| SELECT CONCAT( | |
| 'ALTER TABLE `', TABLE_NAME, '` DROP COLUMN `', COLUMN_NAME, '`, CHANGE COLUMN `', COLUMN_NAME, | |
| '_tmp` `', COLUMN_NAME, '` UUID ', | |
| (CASE WHEN IS_NULLABLE = 'NO' THEN 'NOT NULL' ELSE 'NULL' END), | |
| ';' | |
| ) AS final_swap_sql | |
| FROM INFORMATION_SCHEMA.COLUMNS | |
| WHERE TABLE_SCHEMA = @target_db | |
| AND (DATA_TYPE = 'char' AND CHARACTER_MAXIMUM_LENGTH = 32) | |
| ORDER BY TABLE_NAME, COLUMN_NAME; | |
| -- ======================================================================== | |
| -- G) Generate statements to recreate keys and indices . | |
| -- ======================================================================== | |
| -- G.1) Generate statements to RECREATE UNIQUE constraints (grouping multi-column constraints) | |
| -- TODO: This might leave out columns that are not fks but are part of the unique constraint. Also, columns might be duplicated. | |
| SELECT CONCAT( | |
| 'ALTER TABLE `', tc.TABLE_SCHEMA, '`.`', tc.TABLE_NAME, '` ADD UNIQUE `', tc.CONSTRAINT_NAME, '` (', | |
| GROUP_CONCAT(CONCAT('`', COLUMN_NAME, '`') ORDER BY ORDINAL_POSITION SEPARATOR ', '), | |
| ');' | |
| ) AS create_unique_sql | |
| FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc | |
| JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu | |
| ON tc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA | |
| AND tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME | |
| WHERE tc.CONSTRAINT_TYPE = 'UNIQUE' | |
| AND tc.CONSTRAINT_SCHEMA = DATABASE() | |
| AND EXISTS (SELECT 1 | |
| FROM INFORMATION_SCHEMA.COLUMNS c | |
| WHERE c.TABLE_SCHEMA = DATABASE() | |
| AND c.TABLE_NAME = kcu.TABLE_NAME | |
| AND c.COLUMN_NAME = kcu.COLUMN_NAME | |
| AND c.DATA_TYPE = 'char' | |
| AND c.CHARACTER_MAXIMUM_LENGTH = 32) | |
| GROUP BY tc.CONSTRAINT_SCHEMA, tc.TABLE_NAME, tc.CONSTRAINT_NAME | |
| ORDER BY tc.TABLE_NAME, tc.CONSTRAINT_NAME; | |
| -- G.2) Generate statements to RECREATE primary keys (grouping multi-column PKs) | |
| SELECT DISTINCT CONCAT( | |
| 'ALTER TABLE `', TABLE_SCHEMA, '`.`', TABLE_NAME, '` ADD PRIMARY KEY (', | |
| GROUP_CONCAT(CONCAT('`', COLUMN_NAME, '`') ORDER BY ORDINAL_POSITION SEPARATOR ', '), | |
| ');' | |
| ) AS create_pk_sql | |
| FROM INFORMATION_SCHEMA.COLUMNS | |
| WHERE TABLE_SCHEMA = DATABASE() | |
| AND DATA_TYPE = 'char' | |
| AND CHARACTER_MAXIMUM_LENGTH = 32 | |
| AND COLUMN_KEY = 'PRI' | |
| GROUP BY TABLE_SCHEMA, TABLE_NAME; | |
| -- G.3) Now produce CREATE (recreate) FK statements (grouping multi-column constraints) | |
| SELECT CONCAT( | |
| 'ALTER TABLE `', kcu.TABLE_SCHEMA, '`.`', kcu.TABLE_NAME, '` ADD CONSTRAINT `', kcu.CONSTRAINT_NAME, | |
| '` FOREIGN KEY (', | |
| GROUP_CONCAT(CONCAT('`', kcu.COLUMN_NAME, '`') ORDER BY kcu.ORDINAL_POSITION SEPARATOR ', '), | |
| ') REFERENCES `', kcu.REFERENCED_TABLE_SCHEMA, '`.`', kcu.REFERENCED_TABLE_NAME, '` (', | |
| GROUP_CONCAT(CONCAT('`', kcu.REFERENCED_COLUMN_NAME, '`') ORDER BY kcu.ORDINAL_POSITION SEPARATOR ', '), | |
| ') ON UPDATE ', rc.UPDATE_RULE, ' ON DELETE ', rc.DELETE_RULE, ';' | |
| ) AS recreate_fk_sql | |
| FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu | |
| JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc | |
| ON kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA | |
| AND kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME | |
| WHERE kcu.REFERENCED_TABLE_SCHEMA = @target_db | |
| AND EXISTS (SELECT 1 | |
| FROM INFORMATION_SCHEMA.COLUMNS c | |
| WHERE c.TABLE_SCHEMA = @target_db | |
| AND c.TABLE_NAME = kcu.REFERENCED_TABLE_NAME | |
| AND c.COLUMN_NAME = kcu.REFERENCED_COLUMN_NAME | |
| AND (DATA_TYPE = 'char' AND CHARACTER_MAXIMUM_LENGTH = 32)) | |
| GROUP BY kcu.CONSTRAINT_NAME, kcu.TABLE_SCHEMA, kcu.TABLE_NAME, rc.UPDATE_RULE, rc.DELETE_RULE | |
| ORDER BY recreate_fk_sql; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment