skip to Main Content

I need to add a new UUID column to an existing SQLAlchemy / MySQL table where.

For doing so I added in my database model:

class MyTable(db.Model):
    uid = db.Column(db.BINARY(16), nullable=False, unique=True, default=uuid.uuid4)

Doing so generates the following alembic upgrade code which of course does not work as the default value if the new column is null:

op.add_column('my_table', sa.Column('uid', sa.BINARY(length=16), nullable=True))
op.create_unique_constraint(None, 'my_table', ['uid'])

I tried to extend the db.Column(db.BINARY(16), nullable=False, unique=True, default=uuid.uuid4) definition with an appropriate server_default=... parameter but wasn’t able to find a parameter that generates for each row a new random UUID.

How to add a new column and generate for all existing rows a new random and unique UUID value?

A solution that uses sa.String instead of sa.BINARY would also be acceptable.

2

Answers


  1. Chosen as BEST ANSWER

    In the end I manually created the necessary UPDATE statements in the alembic update file so existing rows are assigned a unique UID.

    For new entries default=uuid.uuid4 in the SQLAlchemy column definition is sufficient.

    Note that the MySQL UUID() function generates timestamp based UUID values for the existing records, and new records created via Python/SQLAlchemy use uuid.uuid4 which generates a v4 (random) UUID. So both are UUIDs but you will see which UUIDs were generated by UUID() as they only differ in the first block when generating them using the UPDATE statement.

    Using binary column type

    class MyTable(db.Model):
        uid = db.Column(db.BINARY(16), nullable=False, unique=True, default=uuid.uuid4)
    
    def upgrade():
        op.add_column('my_table', sa.Column('uid', sa.BINARY(length=16), nullable=False))
        op.execute("UPDATE my_table SET uid = (SELECT(UUID_TO_BIN(UUID())))")
        op.alter_column('my_table', 'uid', existing_type=sa.BINARY(length=16), nullable=False)
        op.create_unique_constraint(None, 'my_table', ['uid'])
    

    Using String/varchar column type

    class MyTable(db.Model):
        uid = db.Column(db.String(36), nullable=False, unique=True, default=uuid.uuid4)
    
    def upgrade():
        op.add_column('my_table', sa.Column('uid', sa.String(length=36), nullable=False))
        op.execute("UPDATE my_table SET uid = (SELECT(UUID()))")
        op.alter_column('my_table', 'uid', existing_type=sa.String(length=36), nullable=False)
        op.create_unique_constraint(None, 'my_table', ['uid'])
    

  2. As per mysqlalchemy’s documentation on server_default:

    A text() expression will be rendered as-is, without quotes:
    Column('y', DateTime, server_default = text('NOW()'))

    y DATETIME DEFAULT NOW()

    Based on this, your server_default definition should look like this:

    server_default=text('(UUID_TO_BIN(UUID())))')
    

    However, if your mysql version is earlier than v8.0.12, then you cannot use the server side default like this, you need to use either the default with setting uuid from python or you need a trigger as specified in the following SO question: MySQL set default id UUID

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search