pythonmysqlsqlalchemyalembicsqlalchemy-migrate

Stored procedure with Alembic: MySQL Syntax Error


I am using Alembic to load a stored procedure into the MySQL database. Following the cookbook, I was able to create the required objects for creating and dropping the procedure. Now that I want to upgrade the version to actually load the procedure, I am getting SQL syntax near the DELIMITER $$ which I need for procedure definition. I have also tried to remove the DELIMITER and replace AS for starting the procedure, but that didn't work either. I even tried the simple example function in the cookbook, but that also gave me the same syntax error near $.

class ReplaceableObject:
    def __init__(self, name, sqltext):
        self.name = name
        self.sqltext = sqltext


class ReversibleOp(MigrateOperation):
    def __init__(self, target):
        self.target = target

    @classmethod
    def invoke_for_target(cls, operations, target):
        op = cls(target)
        return operations.invoke(op)

    def reverse(self):
        raise NotImplementedError()

    @classmethod
    def _get_object_from_version(cls, operations, ident):
        version, objname = ident.split(".")

        module = operations.get_context().script.get_revision(version).module
        obj = getattr(module, objname)
        return obj

    @classmethod
    def replace(cls, operations, target, replaces=None, replace_with=None):

        if replaces:
            old_obj = cls._get_object_from_version(operations, replaces)
            drop_old = cls(old_obj).reverse()
            create_new = cls(target)
        elif replace_with:
            old_obj = cls._get_object_from_version(operations, replace_with)
            drop_old = cls(target).reverse()
            create_new = cls(old_obj)
        else:
            raise TypeError("replaces or replace_with is required")

        operations.invoke(drop_old)
        operations.invoke(create_new)


@Operations.register_operation("create_sp", "invoke_for_target")
@Operations.register_operation("replace_sp", "replace")
class CreateSPOp(ReversibleOp):
    def reverse(self):
        return DropSPOp(self.target)


@Operations.register_operation("drop_sp", "invoke_for_target")
class DropSPOp(ReversibleOp):
    def reverse(self):
        return CreateSPOp(self.target)


@Operations.implementation_for(CreateSPOp)
def create_sp(operations, operation):
    operations.execute(
        """
        DELIMITER $$
        CREATE PROCEDURE %s %s
        DELIMITER ;
        """ % (
            operation.target.name, operation.target.sqltext
        )
    )


@Operations.implementation_for(DropSPOp)
def drop_sp(operations, operation):
    operations.execute("DROP PROCEDURE IF EXISTS %s" % operation.target.name)

And this is how I'm trying to create it:

my_procedure = ReplaceableObject(
    "my_procedure(IN arg1 TEXT, IN arg2 TEXT, OUT res TEXT)",
    """
    BEGIN
        -- procedure implementation
    END $$   
    """
)

def upgrade():
    op.create_sp(my_procedure)

def downgrade():
    op.drop_sp(my_procedure)

And this is the error (I'm using pymysql as client):

sqlalchemy.exc.ProgrammingError: (pymysql.err.ProgrammingError) (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DELIMITER $$\n        CREATE PROCEDURE my_procedure(IN arg1 TEXT, IN arg2 TEXT, OU' at line 1")

Solution

  • You don't need DELIMITER except in an environment that supports a series of semicolon-separated statements — the MySQL client.

    In fact, the DELIMITER command is a mysql client builtin, and it's not recognized by the MySQL Server.

    You must not use DELIMITER when submitting statements to the MySQL Server through an API, even if it's CREATE PROCEDURE.