rails icon indicating copy to clipboard operation
rails copied to clipboard

`ActiveRecord::ConnectionAdapters::SQLite3Adapter#initialize` does not correctly create missing parent directories

Open postmodern opened this issue 1 year ago • 6 comments

ActiveRecord::ConnectionAdapters::SQLite3Adapter#initialize will check if a sqlite3 database file path exists, and if not it will attempt to create the directory using Dir.mkdir. However, Dir.mkdir cannot create all parent directories like FileUtils.mkdir_p. Thus an No such file or directory @ dir_s_mkdir - does/not/exist/yet (Errno::ENOENT) will be raised if one of the parent-parent directories of the sqlite3 database file do not exist yet.

Steps to reproduce

  1. ActiveRecord::Base.establish_connect({adapter: 'sqlite3', database: 'does/not/exist/yet/database.sqlite3'})
  2. migrations = ActiveRecord::MigrationContext.new(['path/to/db/migrate'])

Your reproduction script goes here

require 'active_record'

ActiveRecord::Base.establish_connect({adapter: 'sqlite3', database: 'does/not/exist/yet/database.sqlite3'})
migrations = ActiveRecord::MigrationContext.new(['path/to/db/migrate'])

Expected behavior

Use FileUtils.mkdir_p to create all parent directories for the missing sqlite3 database file.

Actual behavior

/home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/sqlite3_adapter.rb:119:in `rescue in initialize': Database not found (ActiveRecord::NoDatabaseError)
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/sqlite3_adapter.rb:115:in `initialize'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/sqlite3_adapter.rb:24:in `new'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/sqlite3_adapter.rb:24:in `sqlite3_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:676:in `public_send'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:676:in `new_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:723:in `checkout_new_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:702:in `try_to_checkout_new_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:654:in `acquire_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:353:in `checkout'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:182:in `connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_handler.rb:246:in `retrieve_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_handling.rb:287:in `retrieve_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_handling.rb:254:in `connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/tasks/database_tasks.rb:510:in `migration_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/migration.rb:1367:in `connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/migration.rb:1229:in `initialize'
	... 5 levels...
/home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/sqlite3_adapter.rb:116:in `mkdir': No such file or directory @ dir_s_mkdir - does/not/exist/yet (Errno::ENOENT)
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/sqlite3_adapter.rb:116:in `initialize'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/sqlite3_adapter.rb:24:in `new'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/sqlite3_adapter.rb:24:in `sqlite3_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:676:in `public_send'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:676:in `new_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:723:in `checkout_new_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:702:in `try_to_checkout_new_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:654:in `acquire_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:353:in `checkout'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:182:in `connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/connection_handler.rb:246:in `retrieve_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_handling.rb:287:in `retrieve_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/connection_handling.rb:254:in `connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/tasks/database_tasks.rb:510:in `migration_connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/migration.rb:1367:in `connection'
	from /home/postmodern/.gem/ruby/gems/activerecord-7.1.3.2/lib/active_record/migration.rb:1229:in `initialize'
	... 5 levels...

System configuration

Rails version: 7.1.3.2

Ruby version: ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-linux]

postmodern avatar Apr 21 '24 14:04 postmodern

@postmodern Please check this one. Seems like the actual cause of the issue. https://github.com/rails/rails/issues/50391

maniSHarma7575 avatar Apr 22 '24 04:04 maniSHarma7575

@maniSHarma7575 as indicated in my issue, I'm not using rails db:drop. I am directly calling ActiveRecord::Base.establish_connection and directly invoking the migrations using ActiveRecord::MigrationContext.new(['path/to/db/migrate']). I discovered this limitation when trying to run my own database tool that uses ActiveRecord to create a database in the ~/.local/share/ronin-db/ directory, but when I ran it in a completely new user environment that did not have a ~/.local directory, ActiveRecord failed.

postmodern avatar Apr 22 '24 04:04 postmodern

@maniSHarma7575 as indicated in my issue, I'm not using rails db:drop. I am directly calling ActiveRecord::Base.establish_connection and directly invoking the migrations using ActiveRecord::MigrationContext.new(['path/to/db/migrate']). I discovered this limitation when trying to run my own database tool that uses ActiveRecord to create a database in the ~/.local/share/ronin-db/ directory, but when I ran it in a completely new user environment that did not have a ~/.local directory, ActiveRecord failed.

@postmodern I think it's intentional that it should raise an error when parent directory does not exists. Rails have a test case for that in source code as well.

https://github.com/rails/rails/blob/82054e8eb76a72ded0abee83019e60c19f552a8b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb#L26-L32

maniSHarma7575 avatar Apr 22 '24 05:04 maniSHarma7575

@eileencodes Should we fixed this? For Sqlite3 Rails should allow to create the database even if the database path with parent directories doesn't exists?

maniSHarma7575 avatar Apr 22 '24 05:04 maniSHarma7575

@maniSHarma7575 why? It does not have to raise an error, it can simply create the parent directories using FileUtils.mkdir_p. I don't see any benefit for having such a limitation. This only prevents ActiveRecord from creating databases in nested directories that do not exist yet.

postmodern avatar Apr 22 '24 05:04 postmodern

@maniSHarma7575 why? It does not have to raise an error, it can simply create the parent directories using FileUtils.mkdir_p. I don't see any benefit for having such a limitation. This only prevents ActiveRecord from creating databases in nested directories that do not exist yet.

@postmodern I also agree with you and have created a pull request for it. However, I'm unsure about the opinion of the Rails core team. Let's wait for their input and see what their take on it is.

maniSHarma7575 avatar Apr 22 '24 08:04 maniSHarma7575