2009-08-04 17:16:03 +02:00
# encoding: binary
2007-01-22 14:43:50 +01:00
require 'active_record/connection_adapters/abstract_adapter'
module ActiveRecord
class Base
class << self
# Establishes a connection to the database that's used by all Active Record objects
def sqlite_connection ( config ) # :nodoc:
2007-12-21 08:48:59 +01:00
parse_sqlite_config! ( config )
2007-01-22 14:43:50 +01:00
unless self . class . const_defined? ( :SQLite )
require_library_or_gem ( config [ :adapter ] )
db = SQLite :: Database . new ( config [ :database ] , 0 )
db . show_datatypes = " ON " if ! defined? SQLite :: Version
db . results_as_hash = true if defined? SQLite :: Version
db . type_translation = false
2009-09-05 09:01:46 +02:00
message = " Support for SQLite2Adapter and DeprecatedSQLiteAdapter has been removed from Rails 3. "
message << " You should migrate to SQLite 3+ or use the plugin from git://github.com/rails/sqlite2_adapter.git with Rails 3. "
ActiveSupport :: Deprecation . warn ( message )
2007-01-22 14:43:50 +01:00
# "Downgrade" deprecated sqlite API
if SQLite . const_defined? ( :Version )
2009-03-16 15:55:30 +01:00
ConnectionAdapters :: SQLite2Adapter . new ( db , logger , config )
2007-01-22 14:43:50 +01:00
else
2009-03-16 15:55:30 +01:00
ConnectionAdapters :: DeprecatedSQLiteAdapter . new ( db , logger , config )
2007-01-22 14:43:50 +01:00
end
end
end
private
2007-12-21 08:48:59 +01:00
def parse_sqlite_config! ( config )
2009-09-05 09:01:46 +02:00
if config . include? ( :dbfile )
ActiveSupport :: Deprecation . warn " Please update config/database.yml to use 'database' instead of 'dbfile' "
end
2007-01-22 14:43:50 +01:00
config [ :database ] || = config [ :dbfile ]
# Require database.
unless config [ :database ]
raise ArgumentError , " No database file specified. Missing argument: database "
end
# Allow database path relative to RAILS_ROOT, but only if
# the database path is not the special path that tells
2007-12-21 08:48:59 +01:00
# Sqlite to build a database only in memory.
2007-01-22 14:43:50 +01:00
if Object . const_defined? ( :RAILS_ROOT ) && ':memory:' != config [ :database ]
config [ :database ] = File . expand_path ( config [ :database ] , RAILS_ROOT )
end
end
end
end
module ConnectionAdapters #:nodoc:
class SQLiteColumn < Column #:nodoc:
class << self
def string_to_binary ( value )
2009-08-04 17:16:03 +02:00
value = value . dup . force_encoding ( Encoding :: BINARY ) if value . respond_to? ( :force_encoding )
2007-10-15 19:16:54 +02:00
value . gsub ( / \ 0| \ % /n ) do | b |
2007-01-22 14:43:50 +01:00
case b
when " \0 " then " %00 "
when " % " then " %25 "
end
2007-12-21 08:48:59 +01:00
end
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
2007-01-22 14:43:50 +01:00
def binary_to_string ( value )
2009-08-04 17:16:03 +02:00
value = value . dup . force_encoding ( Encoding :: BINARY ) if value . respond_to? ( :force_encoding )
2007-10-15 19:16:54 +02:00
value . gsub ( / %00|%25 /n ) do | b |
2007-01-22 14:43:50 +01:00
case b
when " %00 " then " \0 "
when " %25 " then " % "
end
2007-12-21 08:48:59 +01:00
end
2007-01-22 14:43:50 +01:00
end
end
end
# The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby drivers (available both as gems and
# from http://rubyforge.org/projects/sqlite-ruby/).
#
# Options:
#
2008-05-18 06:22:34 +02:00
# * <tt>:database</tt> - Path to the database file.
2007-01-22 14:43:50 +01:00
class SQLiteAdapter < AbstractAdapter
2009-03-16 15:55:30 +01:00
class Version
include Comparable
def initialize ( version_string )
@version = version_string . split ( '.' ) . map ( & :to_i )
end
def <=> ( version_string )
@version < = > version_string . split ( '.' ) . map ( & :to_i )
end
end
def initialize ( connection , logger , config )
super ( connection , logger )
@config = config
end
2007-01-22 14:43:50 +01:00
def adapter_name #:nodoc:
'SQLite'
end
2009-03-16 15:55:30 +01:00
def supports_ddl_transactions?
sqlite_version > = '2.0.0'
end
2007-01-22 14:43:50 +01:00
def supports_migrations? #:nodoc:
true
end
2007-02-09 09:04:31 +01:00
2009-09-05 09:01:46 +02:00
def supports_primary_key? #:nodoc:
true
end
2007-02-09 09:04:31 +01:00
def requires_reloading?
true
end
2009-03-16 15:55:30 +01:00
def supports_add_column?
sqlite_version > = '3.1.6'
end
2007-12-21 08:48:59 +01:00
def disconnect!
super
@connection . close rescue nil
end
2007-01-22 14:43:50 +01:00
def supports_count_distinct? #:nodoc:
2007-12-21 08:48:59 +01:00
sqlite_version > = '3.2.6'
end
def supports_autoincrement? #:nodoc:
sqlite_version > = '3.1.0'
2007-01-22 14:43:50 +01:00
end
def native_database_types #:nodoc:
{
2007-12-21 08:48:59 +01:00
:primary_key = > default_primary_key_type ,
2007-01-22 14:43:50 +01:00
:string = > { :name = > " varchar " , :limit = > 255 } ,
:text = > { :name = > " text " } ,
:integer = > { :name = > " integer " } ,
:float = > { :name = > " float " } ,
2007-02-09 09:04:31 +01:00
:decimal = > { :name = > " decimal " } ,
2007-01-22 14:43:50 +01:00
:datetime = > { :name = > " datetime " } ,
:timestamp = > { :name = > " datetime " } ,
2008-05-18 06:22:34 +02:00
:time = > { :name = > " time " } ,
2007-01-22 14:43:50 +01:00
:date = > { :name = > " date " } ,
:binary = > { :name = > " blob " } ,
:boolean = > { :name = > " boolean " }
}
end
# QUOTING ==================================================
def quote_string ( s ) #:nodoc:
@connection . class . quote ( s )
end
def quote_column_name ( name ) #:nodoc:
%Q( " #{ name } " )
end
# DATABASE STATEMENTS ======================================
def execute ( sql , name = nil ) #:nodoc:
catch_schema_changes { log ( sql , name ) { @connection . execute ( sql ) } }
end
2007-12-21 08:48:59 +01:00
def update_sql ( sql , name = nil ) #:nodoc:
super
2007-01-22 14:43:50 +01:00
@connection . changes
end
2007-12-21 08:48:59 +01:00
def delete_sql ( sql , name = nil ) #:nodoc:
2007-01-22 14:43:50 +01:00
sql += " WHERE 1=1 " unless sql =~ / WHERE /i
2007-12-21 08:48:59 +01:00
super sql , name
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
def insert_sql ( sql , name = nil , pk = nil , id_value = nil , sequence_name = nil ) #:nodoc:
super || @connection . last_insert_row_id
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
def select_rows ( sql , name = nil )
2007-01-22 14:43:50 +01:00
execute ( sql , name ) . map do | row |
2007-12-21 08:48:59 +01:00
( 0 ... ( row . size / 2 ) ) . map { | i | row [ i ] }
2007-01-22 14:43:50 +01:00
end
end
def begin_db_transaction #:nodoc:
catch_schema_changes { @connection . transaction }
end
2007-12-21 08:48:59 +01:00
2007-01-22 14:43:50 +01:00
def commit_db_transaction #:nodoc:
catch_schema_changes { @connection . commit }
end
def rollback_db_transaction #:nodoc:
catch_schema_changes { @connection . rollback }
end
2007-02-09 09:04:31 +01:00
# SELECT ... FOR UPDATE is redundant since the table is locked.
def add_lock! ( sql , options ) #:nodoc:
sql
end
2007-01-22 14:43:50 +01:00
# SCHEMA STATEMENTS ========================================
def tables ( name = nil ) #:nodoc:
2007-12-21 08:48:59 +01:00
sql = <<-SQL
SELECT name
FROM sqlite_master
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
SQL
execute ( sql , name ) . map do | row |
2010-05-25 19:45:45 +02:00
row [ 'name' ]
2007-01-22 14:43:50 +01:00
end
end
def columns ( table_name , name = nil ) #:nodoc:
table_structure ( table_name ) . map do | field |
2010-05-25 19:45:45 +02:00
SQLiteColumn . new ( field [ 'name' ] , field [ 'dflt_value' ] , field [ 'type' ] , field [ 'notnull' ] . to_i == 0 )
2007-01-22 14:43:50 +01:00
end
end
def indexes ( table_name , name = nil ) #:nodoc:
2008-05-18 06:22:34 +02:00
execute ( " PRAGMA index_list( #{ quote_table_name ( table_name ) } ) " , name ) . map do | row |
2007-01-22 14:43:50 +01:00
index = IndexDefinition . new ( table_name , row [ 'name' ] )
2010-05-25 19:45:45 +02:00
index . unique = row [ 'unique' ] . to_i != 0
2007-01-22 14:43:50 +01:00
index . columns = execute ( " PRAGMA index_info(' #{ index . name } ') " ) . map { | col | col [ 'name' ] }
index
end
end
def primary_key ( table_name ) #:nodoc:
column = table_structure ( table_name ) . find { | field | field [ 'pk' ] . to_i == 1 }
column ? column [ 'name' ] : nil
end
2010-05-25 19:45:45 +02:00
def remove_index! ( table_name , index_name ) #:nodoc:
execute " DROP INDEX #{ quote_column_name ( index_name ) } "
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
2007-01-22 14:43:50 +01:00
def rename_table ( name , new_name )
2007-02-09 09:04:31 +01:00
execute " ALTER TABLE #{ name } RENAME TO #{ new_name } "
2007-01-22 14:43:50 +01:00
end
2009-03-16 15:55:30 +01:00
# See: http://www.sqlite.org/lang_altertable.html
# SQLite has an additional restriction on the ALTER TABLE statement
def valid_alter_table_options ( type , options )
type . to_sym != :primary_key
end
2007-01-22 14:43:50 +01:00
def add_column ( table_name , column_name , type , options = { } ) #:nodoc:
2009-03-16 15:55:30 +01:00
if supports_add_column? && valid_alter_table_options ( type , options )
super ( table_name , column_name , type , options )
else
alter_table ( table_name ) do | definition |
definition . column ( column_name , type , options )
end
2008-06-02 08:35:38 +02:00
end
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
2008-05-18 06:22:34 +02:00
def remove_column ( table_name , * column_names ) #:nodoc:
column_names . flatten . each do | column_name |
alter_table ( table_name ) do | definition |
definition . columns . delete ( definition [ column_name ] )
end
2007-01-22 14:43:50 +01:00
end
end
2008-05-18 06:22:34 +02:00
alias :remove_columns :remove_column
2007-12-21 08:48:59 +01:00
2007-01-22 14:43:50 +01:00
def change_column_default ( table_name , column_name , default ) #:nodoc:
alter_table ( table_name ) do | definition |
definition [ column_name ] . default = default
end
end
2008-09-07 07:54:05 +02:00
def change_column_null ( table_name , column_name , null , default = nil )
unless null || default . nil?
execute ( " UPDATE #{ quote_table_name ( table_name ) } SET #{ quote_column_name ( column_name ) } = #{ quote ( default ) } WHERE #{ quote_column_name ( column_name ) } IS NULL " )
end
alter_table ( table_name ) do | definition |
definition [ column_name ] . null = null
end
end
2007-01-22 14:43:50 +01:00
def change_column ( table_name , column_name , type , options = { } ) #:nodoc:
alter_table ( table_name ) do | definition |
2007-02-09 09:04:31 +01:00
include_default = options_include_default? ( options )
2007-01-22 14:43:50 +01:00
definition [ column_name ] . instance_eval do
self . type = type
2007-02-09 09:04:31 +01:00
self . limit = options [ :limit ] if options . include? ( :limit )
self . default = options [ :default ] if include_default
2007-12-21 08:48:59 +01:00
self . null = options [ :null ] if options . include? ( :null )
2007-01-22 14:43:50 +01:00
end
end
end
def rename_column ( table_name , column_name , new_column_name ) #:nodoc:
2008-09-07 07:54:05 +02:00
unless columns ( table_name ) . detect { | c | c . name == column_name . to_s }
raise ActiveRecord :: ActiveRecordError , " Missing column #{ table_name } . #{ column_name } "
end
2007-12-21 08:48:59 +01:00
alter_table ( table_name , :rename = > { column_name . to_s = > new_column_name . to_s } )
end
def empty_insert_statement ( table_name )
" INSERT INTO #{ table_name } VALUES(NULL) "
2007-01-22 14:43:50 +01:00
end
protected
2007-12-21 08:48:59 +01:00
def select ( sql , name = nil ) #:nodoc:
execute ( sql , name ) . map do | row |
record = { }
row . each_key do | key |
if key . is_a? ( String )
2008-05-18 06:22:34 +02:00
record [ key . sub ( / ^"? \ w+"? \ . / , '' ) ] = row [ key ]
2007-12-21 08:48:59 +01:00
end
end
record
end
end
2007-01-22 14:43:50 +01:00
def table_structure ( table_name )
2008-05-18 06:22:34 +02:00
returning structure = execute ( " PRAGMA table_info( #{ quote_table_name ( table_name ) } ) " ) do
2007-12-21 08:48:59 +01:00
raise ( ActiveRecord :: StatementInvalid , " Could not find table ' #{ table_name } ' " ) if structure . empty?
2007-01-22 14:43:50 +01:00
end
end
2007-12-21 08:48:59 +01:00
2007-01-22 14:43:50 +01:00
def alter_table ( table_name , options = { } ) #:nodoc:
altered_table_name = " altered_ #{ table_name } "
caller = lambda { | definition | yield definition if block_given? }
transaction do
2007-12-21 08:48:59 +01:00
move_table ( table_name , altered_table_name ,
2007-01-22 14:43:50 +01:00
options . merge ( :temporary = > true ) )
move_table ( altered_table_name , table_name , & caller )
end
end
2007-12-21 08:48:59 +01:00
2007-01-22 14:43:50 +01:00
def move_table ( from , to , options = { } , & block ) #:nodoc:
copy_table ( from , to , options , & block )
drop_table ( from )
end
2007-12-21 08:48:59 +01:00
2007-01-22 14:43:50 +01:00
def copy_table ( from , to , options = { } ) #:nodoc:
2009-02-04 21:26:08 +01:00
options = options . merge ( :id = > ( ! columns ( from ) . detect { | c | c . name == 'id' } . nil? && 'id' == primary_key ( from ) . to_s ) )
2007-12-21 08:48:59 +01:00
create_table ( to , options ) do | definition |
@definition = definition
2007-01-22 14:43:50 +01:00
columns ( from ) . each do | column |
column_name = options [ :rename ] ?
( options [ :rename ] [ column . name ] ||
options [ :rename ] [ column . name . to_sym ] ||
column . name ) : column . name
2007-12-21 08:48:59 +01:00
@definition . column ( column_name , column . type ,
2007-01-22 14:43:50 +01:00
:limit = > column . limit , :default = > column . default ,
:null = > column . null )
end
2007-12-21 08:48:59 +01:00
@definition . primary_key ( primary_key ( from ) ) if primary_key ( from )
2007-01-22 14:43:50 +01:00
yield @definition if block_given?
end
2007-12-21 08:48:59 +01:00
2008-05-18 06:22:34 +02:00
copy_table_indexes ( from , to , options [ :rename ] || { } )
2007-12-21 08:48:59 +01:00
copy_table_contents ( from , to ,
@definition . columns . map { | column | column . name } ,
2007-01-22 14:43:50 +01:00
options [ :rename ] || { } )
end
2007-12-21 08:48:59 +01:00
2008-05-18 06:22:34 +02:00
def copy_table_indexes ( from , to , rename = { } ) #:nodoc:
2007-01-22 14:43:50 +01:00
indexes ( from ) . each do | index |
name = index . name
if to == " altered_ #{ from } "
name = " temp_ #{ name } "
elsif from == " altered_ #{ to } "
name = name [ 5 .. - 1 ]
end
2007-12-21 08:48:59 +01:00
2008-05-18 06:22:34 +02:00
to_column_names = columns ( to ) . map ( & :name )
columns = index . columns . map { | c | rename [ c ] || c } . select do | column |
to_column_names . include? ( column )
end
unless columns . empty?
# index name can't be the same
opts = { :name = > name . gsub ( / _( #{ from } )_ / , " _ #{ to } _ " ) }
opts [ :unique ] = true if index . unique
add_index ( to , columns , opts )
end
2007-01-22 14:43:50 +01:00
end
end
2007-12-21 08:48:59 +01:00
2007-01-22 14:43:50 +01:00
def copy_table_contents ( from , to , columns , rename = { } ) #:nodoc:
column_mappings = Hash [ * columns . map { | name | [ name , name ] } . flatten ]
rename . inject ( column_mappings ) { | map , a | map [ a . last ] = a . first ; map }
2007-02-09 09:04:31 +01:00
from_columns = columns ( from ) . collect { | col | col . name }
columns = columns . find_all { | col | from_columns . include? ( column_mappings [ col ] ) }
2007-12-21 08:48:59 +01:00
quoted_columns = columns . map { | col | quote_column_name ( col ) } * ','
2008-05-18 06:22:34 +02:00
quoted_to = quote_table_name ( to )
@connection . execute " SELECT * FROM #{ quote_table_name ( from ) } " do | row |
sql = " INSERT INTO #{ quoted_to } ( #{ quoted_columns } ) VALUES ( "
2007-01-22 14:43:50 +01:00
sql << columns . map { | col | quote row [ column_mappings [ col ] ] } * ', '
sql << ')'
@connection . execute sql
end
end
2007-12-21 08:48:59 +01:00
2007-01-22 14:43:50 +01:00
def catch_schema_changes
return yield
rescue ActiveRecord :: StatementInvalid = > exception
if exception . message =~ / database schema has changed /
reconnect!
retry
else
raise
end
end
2007-12-21 08:48:59 +01:00
def sqlite_version
2009-03-16 15:55:30 +01:00
@sqlite_version || = SQLiteAdapter :: Version . new ( select_value ( 'select sqlite_version(*)' ) )
2007-12-21 08:48:59 +01:00
end
def default_primary_key_type
if supports_autoincrement?
'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL' . freeze
else
'INTEGER PRIMARY KEY NOT NULL' . freeze
end
2007-02-09 09:04:31 +01:00
end
end
2007-01-22 14:43:50 +01:00
class SQLite2Adapter < SQLiteAdapter # :nodoc:
2007-02-09 09:04:31 +01:00
def rename_table ( name , new_name )
move_table ( name , new_name )
end
2007-01-22 14:43:50 +01:00
end
class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc:
def insert ( sql , name = nil , pk = nil , id_value = nil )
execute ( sql , name = nil )
id_value || @connection . last_insert_rowid
end
end
end
end