aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb')
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb117
1 files changed, 88 insertions, 29 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 82ad0a3b8e..ae61d6ce94 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -29,7 +29,7 @@ module ActiveRecord
end
end
- # The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby
+ # 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:
@@ -40,16 +40,17 @@ module ActiveRecord
include Comparable
def initialize(version_string)
- @version = version_string.split('.').map(&:to_i)
+ @version = version_string.split('.').map { |v| v.to_i }
end
def <=>(version_string)
- @version <=> version_string.split('.').map(&:to_i)
+ @version <=> version_string.split('.').map { |v| v.to_i }
end
end
def initialize(connection, logger, config)
super(connection, logger)
+ @statements = {}
@config = config
end
@@ -61,6 +62,16 @@ module ActiveRecord
sqlite_version >= '2.0.0'
end
+ def supports_savepoints?
+ sqlite_version >= '3.6.8'
+ end
+
+ # Returns +true+ when the connection adapter supports prepared statement
+ # caching, otherwise returns +false+
+ def supports_statement_cache?
+ true
+ end
+
def supports_migrations? #:nodoc:
true
end
@@ -79,9 +90,14 @@ module ActiveRecord
def disconnect!
super
+ clear_cache!
@connection.close rescue nil
end
+ def clear_cache!
+ @statements.clear
+ end
+
def supports_count_distinct? #:nodoc:
sqlite_version >= '3.2.6'
end
@@ -121,7 +137,7 @@ module ActiveRecord
# Quote date/time values for use in SQL input. Includes microseconds
# if the value is a Time responding to usec.
def quoted_date(value) #:nodoc:
- if value.acts_like?(:time) && value.respond_to?(:usec)
+ if value.respond_to?(:usec)
"#{super}.#{sprintf("%06d", value.usec)}"
else
super
@@ -131,6 +147,32 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
+ def exec_query(sql, name = nil, binds = [])
+ log(sql, name, binds) do
+
+ # Don't cache statements without bind values
+ if binds.empty?
+ stmt = @connection.prepare(sql)
+ cols = stmt.columns
+ records = stmt.to_a
+ stmt.close
+ stmt = records
+ else
+ cache = @statements[sql] ||= {
+ :stmt => @connection.prepare(sql)
+ }
+ stmt = cache[:stmt]
+ cols = cache[:cols] ||= stmt.columns
+ stmt.reset!
+ stmt.bind_params binds.map { |col, val|
+ col ? col.type_cast(val) : val
+ }
+ end
+
+ ActiveRecord::Result.new(cols, stmt.to_a)
+ end
+ end
+
def execute(sql, name = nil) #:nodoc:
log(sql, name) { @connection.execute(sql) }
end
@@ -151,9 +193,19 @@ module ActiveRecord
alias :create :insert_sql
def select_rows(sql, name = nil)
- execute(sql, name).map do |row|
- (0...(row.size / 2)).map { |i| row[i] }
- end
+ exec_query(sql, name).rows
+ end
+
+ def create_savepoint
+ execute("SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def rollback_to_savepoint
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def release_savepoint
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
end
def begin_db_transaction #:nodoc:
@@ -177,24 +229,33 @@ module ActiveRecord
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
SQL
- execute(sql, name).map do |row|
+ exec_query(sql, name).map do |row|
row['name']
end
end
def columns(table_name, name = nil) #:nodoc:
table_structure(table_name).map do |field|
+ case field["dflt_value"]
+ when /^null$/i
+ field["dflt_value"] = nil
+ when /^'(.*)'$/
+ field["dflt_value"] = $1.gsub(/''/, "'")
+ when /^"(.*)"$/
+ field["dflt_value"] = $1.gsub(/""/, '"')
+ end
+
SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0)
end
end
def indexes(table_name, name = nil) #:nodoc:
- execute("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
+ exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
IndexDefinition.new(
table_name,
row['name'],
- row['unique'].to_i != 0,
- execute("PRAGMA index_info('#{row['name']}')").map { |col|
+ row['unique'] != 0,
+ exec_query("PRAGMA index_info('#{row['name']}')").map { |col|
col['name']
})
end
@@ -202,17 +263,17 @@ module ActiveRecord
def primary_key(table_name) #:nodoc:
column = table_structure(table_name).find { |field|
- field['pk'].to_i == 1
+ field['pk'] == 1
}
column && column['name']
end
def remove_index!(table_name, index_name) #:nodoc:
- execute "DROP INDEX #{quote_column_name(index_name)}"
+ exec_query "DROP INDEX #{quote_column_name(index_name)}"
end
def rename_table(name, new_name)
- execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
+ exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
end
# See: http://www.sqlite.org/lang_altertable.html
@@ -249,7 +310,7 @@ module ActiveRecord
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")
+ exec_query("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
@@ -275,23 +336,21 @@ module ActiveRecord
alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
end
+ def null_insert_value
+ Arel.sql 'NULL'
+ end
+
def empty_insert_statement_value
"VALUES(NULL)"
end
protected
- def select(sql, name = nil) #:nodoc:
- execute(sql, name).map do |row|
- record = {}
- row.each do |key, value|
- record[key.sub(/^"?\w+"?\./, '')] = value if key.is_a?(String)
- end
- record
- end
+ def select(sql, name = nil, binds = []) #:nodoc:
+ exec_query(sql, name, binds).to_a
end
def table_structure(table_name)
- structure = @connection.table_info(quote_table_name(table_name))
+ structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})").to_hash
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
structure
end
@@ -345,7 +404,7 @@ module ActiveRecord
name = name[5..-1]
end
- to_column_names = columns(to).map(&:name)
+ to_column_names = columns(to).map { |c| c.name }
columns = index.columns.map {|c| rename[c] || c }.select do |column|
to_column_names.include?(column)
end
@@ -360,18 +419,18 @@ module ActiveRecord
end
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}
+ column_mappings = Hash[columns.map {|name| [name, name]}]
+ rename.each { |a| column_mappings[a.last] = a.first }
from_columns = columns(from).collect {|col| col.name}
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
quoted_to = quote_table_name(to)
- @connection.execute "SELECT * FROM #{quote_table_name(from)}" do |row|
+ exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
sql << ')'
- @connection.execute sql
+ exec_query sql
end
end