sqlmysqlprimary-key

SQL function to convert ulid stored as VARCHAR(255) to binary(16)


I have a mysql database where everywhere id (Primary Key) is ulid. The problem is that it is stored as VARCHAR(255). I want to correct it (change to binary) the following way:

  1. Add id_bin column of type BINARY(16) to each table.
  2. Copy id values to this new id_bin column while transforming them to binary.
  3. Drop the main Primary Key and add a new one to id_bin column.
  4. Rename the id_bin to id.
  5. Make the same changes to all the foreign keys.

The problem is transforming the ulid stored as VARCHAR(255) in utf8mb3 charset to binary. I have tried the following function:

DELIMITER //

-- The input is of type CHAR(26) for simplicity as converting varchar(255) to char(26) does not seem to be a problem, the problem is converting it further to binary
CREATE FUNCTION ConvertULIDToBinary(ulid CHAR(26) CHARACTER SET utf8mb3) 
RETURNS BINARY(16)
DETERMINISTIC
BEGIN
    DECLARE result BINARY(16);
    SET result = UNHEX(CONCAT(
        LPAD(CONV(SUBSTRING(ulid, 1, 6), 36, 16), 10, '0'),
        LPAD(CONV(SUBSTRING(ulid, 7, 6), 36, 16), 10, '0'),
        LPAD(CONV(SUBSTRING(ulid, 13, 6), 36, 16), 10, '0'),
        LPAD(CONV(SUBSTRING(ulid, 19, 8), 36, 16), 16, '0')
    ));
    RETURN result;
END //

DELIMITER ;

But if I run it, I get the following error:

SQL Error (1406) in statement #3: Data too long for column 'result' at row 1

Solution

  • Try the following function:

    delimiter //
    DROP FUNCTION IF EXISTS ULID_DECODE//
    CREATE FUNCTION ULID_DECODE (s CHAR(26)) RETURNS BINARY(16) DETERMINISTIC
    BEGIN
    DECLARE s_base32 CHAR(26);
    SET s_base32 = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(UPPER(s), 'J', 'I'), 'K', 'J'), 'M', 'K'), 'N', 'L'), 'P', 'M'), 'Q', 'N'), 'R', 'O'), 'S', 'P'), 'T', 'Q'), 'V', 'R'), 'W', 'S'), 'X', 'T'), 'Y', 'U'), 'Z', 'V');
    RETURN UNHEX(CONCAT(LPAD(CONV(SUBSTRING(s_base32, 1, 2), 32, 16), 2, '0'), LPAD(CONV(SUBSTRING(s_base32, 3, 12), 32, 16), 15, '0'), LPAD(CONV(SUBSTRING(s_base32, 15, 12), 32, 16), 15, '0')));
    END//
    delimiter ;