mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Added SQLite3 compatibility through the sqlite3-ruby adapter by Jamis Buck #381 [bitsweat]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@374 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
903dcefbaf
commit
a133f3e64f
7 changed files with 157 additions and 90 deletions
|
@ -1,5 +1,7 @@
|
|||
*SVN*
|
||||
|
||||
* Added SQLite3 compatibility through the sqlite3-ruby adapter by Jamis Buck #381 [bitsweat]
|
||||
|
||||
* Added support for the new protocol spoken by MySQL 4.1.1+ servers for the Ruby/MySQL adapter that ships with Rails #440 [Matt Mower]
|
||||
|
||||
* Added that Observers can use the observes class method instead of overwriting self.observed_class().
|
||||
|
|
|
@ -19,7 +19,7 @@ PKG_FILES = FileList[
|
|||
|
||||
|
||||
desc "Default Task"
|
||||
task :default => [ :test_ruby_mysql, :test_mysql_ruby, :test_sqlite, :test_postgresql ]
|
||||
task :default => [ :test_ruby_mysql, :test_mysql_ruby, :test_sqlite, :test_sqlite3, :test_postgresql ]
|
||||
|
||||
# Run the unit tests
|
||||
|
||||
|
@ -47,6 +47,12 @@ Rake::TestTask.new("test_sqlite") { |t|
|
|||
t.verbose = true
|
||||
}
|
||||
|
||||
Rake::TestTask.new("test_sqlite3") { |t|
|
||||
t.libs << "test" << "test/connections/native_sqlite3"
|
||||
t.pattern = 'test/*_test.rb'
|
||||
t.verbose = true
|
||||
}
|
||||
|
||||
Rake::TestTask.new("test_sqlserver") { |t|
|
||||
t.libs << "test" << "test/connections/native_sqlserver"
|
||||
t.pattern = 'test/*_test.rb'
|
||||
|
@ -101,6 +107,9 @@ spec = Gem::Specification.new do |s|
|
|||
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
||||
end
|
||||
s.files.delete "test/fixtures/fixture_database.sqlite"
|
||||
s.files.delete "test/fixtures/fixture_database_2.sqlite"
|
||||
s.files.delete "test/fixtures/fixture_database.sqlite3"
|
||||
s.files.delete "test/fixtures/fixture_database_2.sqlite3"
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'active_record'
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
$:.unshift(File.dirname(__FILE__))
|
||||
|
||||
require 'action_controller/support/core_ext'
|
||||
require 'active_record/support/core_ext'
|
||||
require 'active_record/support/clean_logger'
|
||||
require 'active_record/support/misc'
|
||||
require 'active_record/support/dependencies'
|
||||
|
|
|
@ -89,7 +89,7 @@ module ActiveRecord
|
|||
raise AdapterNotSpecified, "#{spec} database is not configured"
|
||||
end
|
||||
else
|
||||
spec = symbolize_strings_in_hash(spec)
|
||||
spec = spec.symbolize_keys
|
||||
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
|
||||
adapter_method = "#{spec[:adapter]}_connection"
|
||||
unless respond_to?(adapter_method) then raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" end
|
||||
|
@ -152,10 +152,7 @@ module ActiveRecord
|
|||
|
||||
# Converts all strings in a hash to symbols.
|
||||
def self.symbolize_strings_in_hash(hash)
|
||||
hash.inject({}) do |hash_with_symbolized_strings, pair|
|
||||
hash_with_symbolized_strings[pair.first.to_sym] = pair.last
|
||||
hash_with_symbolized_strings
|
||||
end
|
||||
hash.symbolize_keys
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -356,7 +353,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def quote_column_name(name)
|
||||
return name
|
||||
name
|
||||
end
|
||||
|
||||
# Returns a string of the CREATE TABLE SQL statements for recreating the entire structure of the database.
|
||||
|
@ -367,16 +364,20 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
protected
|
||||
def log(sql, name, connection, &action)
|
||||
def log(sql, name, connection = nil)
|
||||
connection ||= @connection
|
||||
begin
|
||||
if @logger.nil? || @logger.level > Logger::INFO
|
||||
action.call(connection)
|
||||
else
|
||||
yield connection
|
||||
elsif block_given?
|
||||
result = nil
|
||||
bm = measure { result = action.call(connection) }
|
||||
bm = measure { result = yield connection }
|
||||
@runtime += bm.real
|
||||
log_info(sql, name, bm.real)
|
||||
result
|
||||
else
|
||||
log_info(sql, name, 0)
|
||||
nil
|
||||
end
|
||||
rescue => e
|
||||
log_info("#{e.message}: #{sql}", name, 0)
|
||||
|
|
|
@ -1,33 +1,67 @@
|
|||
# sqlite_adapter.rb
|
||||
# author: Luke Holden <lholden@cablelan.net>
|
||||
# updated for SQLite3: Jamis Buck <jamis_buck@byu.edu>
|
||||
|
||||
require 'active_record/connection_adapters/abstract_adapter'
|
||||
|
||||
module ActiveRecord
|
||||
class Base
|
||||
# Establishes a connection to the database that's used by all Active Record objects
|
||||
def self.sqlite_connection(config) # :nodoc:
|
||||
require_library_or_gem('sqlite') unless self.class.const_defined?(:SQLite)
|
||||
symbolize_strings_in_hash(config)
|
||||
unless config.has_key?(:dbfile)
|
||||
raise ArgumentError, "No database file specified. Missing argument: dbfile"
|
||||
class << self
|
||||
# sqlite3 adapter reuses sqlite_connection.
|
||||
def sqlite3_connection(config) # :nodoc:
|
||||
parse_config!(config)
|
||||
|
||||
unless self.class.const_defined?(:SQLite3)
|
||||
require_library_or_gem(config[:adapter])
|
||||
end
|
||||
|
||||
config[:dbfile] = File.expand_path(config[:dbfile], RAILS_ROOT) if Object.const_defined?(:RAILS_ROOT)
|
||||
db = SQLite::Database.new(config[:dbfile], 0)
|
||||
db = SQLite3::Database.new(
|
||||
config[:dbfile],
|
||||
:results_as_hash => true,
|
||||
:type_translation => false
|
||||
)
|
||||
ConnectionAdapters::SQLiteAdapter.new(db, logger)
|
||||
end
|
||||
|
||||
# Establishes a connection to the database that's used by all Active Record objects
|
||||
def sqlite_connection(config) # :nodoc:
|
||||
parse_config!(config)
|
||||
|
||||
unless self.class.const_defined?(:SQLite)
|
||||
require_library_or_gem(config[:adapter])
|
||||
|
||||
db = SQLite::Database.new(config[:dbfile], 0)
|
||||
db.show_datatypes = "ON" if !defined? SQLite::Version
|
||||
db.results_as_hash = true if defined? SQLite::Version
|
||||
db.type_translation = false
|
||||
|
||||
# "Downgrade" deprecated sqlite API
|
||||
if SQLite.const_defined?(:Version)
|
||||
ConnectionAdapters::SQLiteAdapter.new(db, logger)
|
||||
else
|
||||
ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def parse_config!(config)
|
||||
# Require dbfile.
|
||||
unless config.has_key?(:dbfile)
|
||||
raise ArgumentError, "No database file specified. Missing argument: dbfile"
|
||||
end
|
||||
|
||||
# Allow database path relative to RAILS_ROOT.
|
||||
if Object.const_defined?(:RAILS_ROOT)
|
||||
config[:dbfile] = File.expand_path(config[:dbfile], RAILS_ROOT)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ConnectionAdapters
|
||||
|
||||
class SQLiteColumn < Column
|
||||
|
||||
def string_to_binary(value)
|
||||
value.gsub(/(\0|\%)/) do
|
||||
case $1
|
||||
|
@ -45,43 +79,11 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class SQLiteAdapter < AbstractAdapter # :nodoc:
|
||||
def select_all(sql, name = nil)
|
||||
select(sql, name)
|
||||
end
|
||||
|
||||
def select_one(sql, name = nil)
|
||||
result = select(sql, name)
|
||||
result.nil? ? nil : result.first
|
||||
end
|
||||
|
||||
def columns(table_name, name = nil)
|
||||
table_structure(table_name).inject([]) do |columns, field|
|
||||
columns << SQLiteColumn.new(field['name'], field['dflt_value'], field['type'])
|
||||
columns
|
||||
end
|
||||
end
|
||||
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil)
|
||||
execute(sql, name = nil)
|
||||
id_value || @connection.send( defined?( SQLite::Version ) ? :last_insert_row_id : :last_insert_rowid )
|
||||
end
|
||||
|
||||
def execute(sql, name = nil)
|
||||
log(sql, name, @connection) do |connection|
|
||||
if defined?( SQLite::Version )
|
||||
case sql
|
||||
when "BEGIN" then connection.transaction
|
||||
when "COMMIT" then connection.commit
|
||||
when "ROLLBACK" then connection.rollback
|
||||
else connection.execute(sql)
|
||||
end
|
||||
else
|
||||
connection.execute( sql )
|
||||
end
|
||||
end
|
||||
log(sql, name) { @connection.execute(sql) }
|
||||
end
|
||||
|
||||
def update(sql, name = nil)
|
||||
|
@ -95,41 +97,60 @@ module ActiveRecord
|
|||
@connection.changes
|
||||
end
|
||||
|
||||
def begin_db_transaction() execute "BEGIN" end
|
||||
def commit_db_transaction() execute "COMMIT" end
|
||||
def rollback_db_transaction() execute "ROLLBACK" end
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil)
|
||||
execute(sql, name = nil)
|
||||
id_value || @connection.last_insert_row_id
|
||||
end
|
||||
|
||||
def select_all(sql, name = nil)
|
||||
execute(sql, name).map do |row|
|
||||
record = {}
|
||||
row.each_key do |key|
|
||||
record[key.sub(/\w+\./, '')] = row[key] unless key.is_a?(Fixnum)
|
||||
end
|
||||
record
|
||||
end
|
||||
end
|
||||
|
||||
def select_one(sql, name = nil)
|
||||
result = select_all(sql, name)
|
||||
result.nil? ? nil : result.first
|
||||
end
|
||||
|
||||
|
||||
def begin_db_transaction() @connection.transaction end
|
||||
def commit_db_transaction() @connection.commit end
|
||||
def rollback_db_transaction() @connection.rollback end
|
||||
|
||||
|
||||
def tables
|
||||
execute('.table').map { |table| Table.new(table) }
|
||||
end
|
||||
|
||||
def columns(table_name, name = nil)
|
||||
table_structure(table_name).map { |field|
|
||||
SQLiteColumn.new(field['name'], field['dflt_value'], field['type'])
|
||||
}
|
||||
end
|
||||
|
||||
def quote_string(s)
|
||||
SQLite::Database.quote(s)
|
||||
@connection.class.quote(s)
|
||||
end
|
||||
|
||||
def quote_column_name(name)
|
||||
return "'#{name}'"
|
||||
end
|
||||
|
||||
private
|
||||
def select(sql, name = nil)
|
||||
results = nil
|
||||
log(sql, name, @connection) { |connection| results = connection.execute(sql) }
|
||||
|
||||
rows = []
|
||||
|
||||
results.each do |row|
|
||||
hash_only_row = {}
|
||||
row.each_key do |key|
|
||||
hash_only_row[key.sub(/\w+\./, "")] = row[key] unless key.class == Fixnum
|
||||
end
|
||||
rows << hash_only_row
|
||||
end
|
||||
|
||||
return rows
|
||||
end
|
||||
|
||||
protected
|
||||
def table_structure(table_name)
|
||||
sql = "PRAGMA table_info(#{table_name});"
|
||||
results = nil
|
||||
log(sql, nil, @connection) { |connection| results = connection.execute(sql) }
|
||||
return results
|
||||
execute "PRAGMA table_info(#{table_name})"
|
||||
end
|
||||
end
|
||||
|
||||
class DeprecatedSQLiteAdapter < SQLiteAdapter # :nodoc:
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil)
|
||||
execute(sql, name = nil)
|
||||
id_value || @connection.last_insert_rowid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
34
activerecord/test/connections/native_sqlite3/connection.rb
Normal file
34
activerecord/test/connections/native_sqlite3/connection.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
print "Using native SQLite3\n"
|
||||
require 'fixtures/course'
|
||||
require 'logger'
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
ActiveRecord::Base.logger.level = Logger::DEBUG
|
||||
|
||||
BASE_DIR = File.expand_path(File.dirname(__FILE__) + '/../../fixtures')
|
||||
sqlite_test_db = "#{BASE_DIR}/fixture_database.sqlite3"
|
||||
sqlite_test_db2 = "#{BASE_DIR}/fixture_database_2.sqlite3"
|
||||
|
||||
def make_connection(clazz, db_file, db_definitions_file)
|
||||
unless File.exist?(db_file)
|
||||
puts "SQLite3 database not found at #{db_file}. Rebuilding it."
|
||||
sqlite_command = "sqlite3 #{db_file} 'create table a (a integer); drop table a;'"
|
||||
puts "Executing '#{sqlite_command}'"
|
||||
`#{sqlite_command}`
|
||||
clazz.establish_connection(
|
||||
:adapter => "sqlite3",
|
||||
:dbfile => db_file)
|
||||
script = File.read("#{BASE_DIR}/db_definitions/#{db_definitions_file}")
|
||||
# SQLite-Ruby has problems with semi-colon separated commands, so split and execute one at a time
|
||||
script.split(';').each do
|
||||
|command|
|
||||
clazz.connection.execute(command) unless command.strip.empty?
|
||||
end
|
||||
else
|
||||
clazz.establish_connection(
|
||||
:adapter => "sqlite3",
|
||||
:dbfile => db_file)
|
||||
end
|
||||
end
|
||||
|
||||
make_connection(ActiveRecord::Base, sqlite_test_db, 'sqlite.sql')
|
||||
make_connection(Course, sqlite_test_db2, 'sqlite2.sql')
|
Loading…
Reference in a new issue