Disclaimer: at the beginning it looks like this message should be on
rubyonrails-talk, but please, keep reading.
I have a Rails 3 project in which I'd like to migrate my databases with
a different database user than the one used for querying, inserting and
deleting records.
I have defined in my config/database.yml my new connections. The
situation is: test, development, test_migrations, development_migrations
are sqlite3 databases. production and production_migrations are mysql
databases.
Now, in my migrations I have the following:
def connection
ActiveRecord::Base.establish_connection("migrations_#{Rails.env}").connection
end
When running "rake db:migrate" I get using the sqlite3 adapter (test and
development) the following error:
ArgumentError: prepare called on a closed database: rollback transaction
with the following rake trace:
Is important to see that what actually is reporting is that it tries to
do a rollback and that is failing, but the rollback comes because a
previous operation failed.
As far as I've seen it looks like the problem is related with DDL
transactions. If I modify
activerecord-3.2.2/lib/active_record/migration.rb ddl_transaction method
with the next method:
def ddl_transaction(&block)
block.call
end
instead of
def ddl_transaction(&block)
if Base.connection.supports_ddl_transactions?
Base.transaction { block.call }
else
block.call
end
end
it seems to work fine. So the whole problem seems to be related with DDL
transactions. Any ideas ? I am willing to help if it's needed.
Best regards,
Rafael Fern�ndez L�pez.
AFAIK and from the current experience ActiveRecord isn't opening all connections. It does only on the current environment one.
> Have you considered that your workflow may be better served by over riding the db:migrate task in your own databases.rake and setting either some ENV variables or hooks to get what you need? Rake allows you to stack tasks, your would be last in, first run. You could even make your database.yml use ERB and use said ENV hooks to just make the simple "development" or "test" connection name go to the migration DBs. If indeed your issue is related to your implementation, the exploring other options could help.
In Rails3 connection() method in migrations was added in order to be able to run migrations in other databases. This also includes the case that I am working on: I prefer to have my database user for production that can only modify data, and have another user with all privileges that is only used on migrations. This is a key security concept. This way it is impossible to use a hypothetical sql injection for modifying database schema.
I prefer to do The Right Thing™ and not to patch it by overriding my db:migrate task. It would be a solution, but I would like to find the root cause of this problem.
I am going to dig a bit more on the code, and see if I find something interesting.
Thanks for your reasoning Ken.
Best regards,
Rafael Fernández López.
Ok, so here is the report. Studying the source code I got to the interesting part:
connection_specification.rb (in activerecord/connection_adapters/abstract)
def self.establish_connection(spec = ENV["DATABASE_URL"])
…
remove_connection
connection_handler.establish_connection name, spec
end
And here is where all the work comes… I tracked all this process, and the "problem" resides in that sqlite3 gem as well as the sqlite_adapter.rb (in active_record/connection_adapters) does not implement the "connect" method.
The sqlite gem only performs a sqlite3_open call in the initialize method. There is a close method, but there isn't an open method. So the dummy one is called that does nothing, and you end with only the new closed connection. And a nice point to remark is also that this only happens when we have DDL transactions. With DDL transactions disabled (as in my previous email) the connection is recreated instead of reopened, and for this reason this works out of the box.
Given that this is important for me, I am willing to implement those in the sqlite3 gem, as well as in the sqlite_adapter.rb. This way the connection can be opened.
Changes are not complex, and they'd give us the same behavior as if it were the mysql adapter.
What do you think ?