Rails, Stored Procedures, Migrations, Mysql 5+ and … Trouble!

This is a tale about Ruby On Rails, custom stored procedures for MySql 5 and how Rails 1.2.3 is not only opinionated against stored procedures but also actually incompatible with creating and sometimes calling (mysql) stored procedures. The tales does not end with a truly happy ending but some “hacks” are mentioned that I have found useful.

Background
The rails framework developers, being of the opinion that complexity is best located in the code and not in the database, does not advocate using stored procedures as a abstraction layer between the database and the application. Instead dynamic sql generated from RoR code is used. For typical application databases, this approach works very well indeed.

Examples where the traditional rails way of database thinking sometimes fails short are projects involving multiple (some non-rails) applications, projects involving legacy databases, projects where the lifetime of the data will exceed the life of the application code and finally specialized projects where a non-trivial amount of handcrafted sql code is needed (typically for reporting, statistical analysis, data mining, scheduled database maintenance tasks and other sql that are not related to the O-R mapping handled by ActiveRecord).

For the latter cases, some usage of stored procedures together with the rest of the traditional database arsenal (foreign key constrains (*), Triggers, views etc) can be useful at times… all depending on the specific project needs of cause.

(*) Actually, from experience, I highly recommend always defining foreign key constrains (even in “application” databases). This is sadly NOT the current way of thinking among rails developers and consequently rails support is lacking - but that is another story.

Defining stored procedures in MySql

Since version 5 of the MySql database you can define a simple no-arg stored procedure like this:

DELIMITER $$

CREATE PROCEDURE mydb.my_stored_procedure()
BEGIN
– INSERT CODE HERE
END $$

DELIMITER ;

Notice the importance of overriding the default “;” delimiter when defining your typical stored procedure! This is different from creating stored procedure in Sql Server for instance.

Calling stored procedures from Rails:

Begin by upgradíng your standard ruby mysql driver to the native “C” driver (gem install mysql) as the standard pure-ruby mysql driver doesn’t support all the needed MySql 5 features.

From rails, our MySQL procedure can f.x. be called using execute(”CALL my_stored_procedure()“) in a migration . Unfortunately, it might not always work. If your stored procedure return multiple result sets, calling the procedure will fail unless you pass the Mysql::CLIENT_MULTI_RESULTS flag when establishing the database connection. Unfortunately, active record does not allow specifying client flags for your database connection. For workaround instructions, that involve patching the MySql Adaptor, see the wiki entry here or the outdated bug report with patch here.

Creating your stored procedures in rails migrations:

The brave adventurer may try to create a Mysql 5 stored procedures like this in a rails migration:

class CreateDatabaseObjects < ActiveRecord::Migration

def self.up
sql_directory = File.join(File.dirname(__FILE__), “sql” )

begin
f=File.open(File.join(sql_directory, “my_stored_procedure.sql”), “r”)
sql = f.readlines.join
execute(sql)
ensure
f.close unless f==nil
end
end

def self.down
execute “DROP PROCEDURE my_stored_procedure”
end

end

Unfortunately, I have found that the obvious approach above does not work with the newest stable version of ruby 1.8.6 and rails 1.2.3 on my Windows installation (meaningless error). Hacking the MySql Adaptor to include the Mysql::CLIENT_MULTI_STATEMENTS client flag does not improve the situation (which is rather strange).

However, I did succeed in creating a workaround. Not a pretty hack or a truely happy resolution but it works:

class CreateDatabaseObjects < ActiveRecord::Migration

def self.up
sql_directory = File.join(File.dirname(__FILE__), “sql” )

# Hack: Invoke database cmd tool subprocess to create our mysql stored procedure.
conf = ActiveRecord::Base.configurations[RAILS_ENV]
sql_file = File.join(sql_directory, “my_stored_procedure.sql”)
cmd_line=”mysql -h “+conf[”host”]+” -D “+conf[”database”]+ ” -u “+conf[”username”]+” -p”+conf[”password”]+” <”+sql_file
if !system(cmd_line)
raise Exception, “Error executing “+cmd_line
end

end

def self.down
execute “DROP PROCEDURE my_stored_procedure”
end

end

- The end -

6 Responses to “Rails, Stored Procedures, Migrations, Mysql 5+ and … Trouble!”

  1. Rasmus Says:

    Hmmm it looks like you’ve been playing alot with RoR since you have the time to actually work with legacy databases and need to call stored procedures. Though I can think of scenarios where I need to do a stored procedure I try all I can to avoid it.

    One could speculate…. MySql only got SPs in the most recent edition and Oracle have only had them a couple of years… Couldn’t we do database driven applications before that?! Or are SPs just MS’s way of keeping you on their platform?!?!

    Hope to see you soon so we can go into some nerdie discussions about it… ;)

  2. Chris R Says:

    @Rasmus:

    Cough! Splutter! Oracle have only had stored procedures for a couple of years? I think you should check your history. PL/SQL was first released in *1992* as an optional extension to Oracle 6.

  3. Fung Says:

    Thank you so much!

    I have tried executing sql file and hacking the MySql adapter. And found your solution here by Google.

    However, I got into another situation that it failed for testing: the testing DB is missing the stored procedures.

    I tried to add them manually, but those SPs were dropped when I ran the tests ($rake test:units) again.

    Do you have a way out there?

    Thanks

  4. Luke Says:

    I’ve been working on the same problem for quite some time using Rails 116. I created a plugin with install instructions here: http://rubyscriptconsole.com/articles/2007/01/13/mysql-routine-migration-mrm-plugin/.

    I’m surprised by the lack of discussion and alternatives for using stored procs in Rails. My plugin is far from a perfect solution, but it has been a functioning tool for me for about 6 mos. now. As I mention several times on my blog, I’ve worked on exactly 0 development projects that didn’t require at a least few stored procs for some heavy-lifting chores. Integrating stored routine migrations into Rails was a must for me, so I had to create something.

    Anyway, look forward to getting your feedback.

  5. foo Says:

    i have the same problem when trying to create my stored procedures from within migrations (loading the mysql scripts from files) and
    i really wonder what the “meaningless error” was in your case?
    for me it seems as if the execute method does not take scripts with multiple sql commands but expects each sql command one by one.

  6. foo Says:

    the mysql c api has information on “Multiple Statement Execution”: http://dev.mysql.com/doc/refman/5.0/en/c-api-multiple-queries.html

Leave a Reply


Bad Behavior has blocked 478 access attempts in the last 7 days.