aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters')
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb108
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb74
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb47
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb243
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb485
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb64
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb19
14 files changed, 819 insertions, 330 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index d69f02d504..561e48d52e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -128,10 +128,11 @@ module ActiveRecord
@reserved_connections[current_connection_id] ||= checkout
end
- # Check to see if there is an active connection in this connection
- # pool.
+ # Is there an open connection that is being used for the current thread?
def active_connection?
- active_connections.any?
+ @reserved_connections.fetch(current_connection_id) {
+ return false
+ }.in_use?
end
# Signal that the thread is finished with the current connection.
@@ -184,19 +185,10 @@ module ActiveRecord
end
end
- # Verify active connections and remove and disconnect connections
- # associated with stale threads.
- def verify_active_connections! #:nodoc:
- synchronize do
- @connections.each do |connection|
- connection.verify!
- end
- end
- end
-
def clear_stale_cached_connections! # :nodoc:
+ reap
end
- deprecate :clear_stale_cached_connections!
+ deprecate :clear_stale_cached_connections! => "Please use #reap instead"
# Check-out a database connection from the pool, indicating that you want
# to use it. You should call #checkin when you no longer need this.
@@ -243,6 +235,8 @@ module ActiveRecord
conn.run_callbacks :checkin do
conn.expire
end
+
+ release conn
end
end
@@ -254,10 +248,7 @@ module ActiveRecord
# FIXME: we might want to store the key on the connection so that removing
# from the reserved hash will be a little easier.
- thread_id = @reserved_connections.keys.find { |k|
- @reserved_connections[k] == conn
- }
- @reserved_connections.delete thread_id if thread_id
+ release conn
end
end
@@ -275,6 +266,20 @@ module ActiveRecord
private
+ def release(conn)
+ thread_id = nil
+
+ if @reserved_connections[current_connection_id] == conn
+ thread_id = current_connection_id
+ else
+ thread_id = @reserved_connections.keys.find { |k|
+ @reserved_connections[k] == conn
+ }
+ end
+
+ @reserved_connections.delete thread_id if thread_id
+ end
+
def new_connection
ActiveRecord::Base.send(spec.adapter_method, spec.config)
end
@@ -298,10 +303,6 @@ module ActiveRecord
end
c
end
-
- def active_connections
- @connections.find_all { |c| c.in_use? }
- end
end
# ConnectionHandler is a collection of ConnectionPool objects. It is used
@@ -328,16 +329,18 @@ module ActiveRecord
# ActiveRecord::Base.connection_handler. Active Record models use this to
# determine that connection pool that they should use.
class ConnectionHandler
- attr_reader :connection_pools
-
- def initialize(pools = {})
+ def initialize(pools = Hash.new { |h,k| h[k] = {} })
@connection_pools = pools
- @class_to_pool = {}
+ @class_to_pool = Hash.new { |h,k| h[k] = {} }
+ end
+
+ def connection_pools
+ @connection_pools[Process.pid]
end
def establish_connection(name, spec)
- @connection_pools[spec] ||= ConnectionAdapters::ConnectionPool.new(spec)
- @class_to_pool[name] = @connection_pools[spec]
+ set_pool_for_spec spec, ConnectionAdapters::ConnectionPool.new(spec)
+ set_class_to_pool name, connection_pools[spec]
end
# Returns true if there are any active connections among the connection
@@ -350,21 +353,16 @@ module ActiveRecord
# and also returns connections to the pool cached by threads that are no
# longer alive.
def clear_active_connections!
- @connection_pools.each_value {|pool| pool.release_connection }
+ connection_pools.each_value {|pool| pool.release_connection }
end
# Clears the cache which maps classes.
def clear_reloadable_connections!
- @connection_pools.each_value {|pool| pool.clear_reloadable_connections! }
+ connection_pools.each_value {|pool| pool.clear_reloadable_connections! }
end
def clear_all_connections!
- @connection_pools.each_value {|pool| pool.disconnect! }
- end
-
- # Verify active connections.
- def verify_active_connections! #:nodoc:
- @connection_pools.each_value {|pool| pool.verify_active_connections! }
+ connection_pools.each_value {|pool| pool.disconnect! }
end
# Locate the connection of the nearest super class. This can be an
@@ -388,21 +386,53 @@ module ActiveRecord
# can be used as an argument for establish_connection, for easily
# re-establishing the connection.
def remove_connection(klass)
- pool = @class_to_pool.delete(klass.name)
+ pool = class_to_pool.delete(klass.name)
return nil unless pool
- @connection_pools.delete pool.spec
+ connection_pools.delete pool.spec
pool.automatic_reconnect = false
pool.disconnect!
pool.spec.config
end
def retrieve_connection_pool(klass)
- pool = @class_to_pool[klass.name]
+ pool = get_pool_for_class klass.name
return pool if pool
return nil if ActiveRecord::Model == klass
retrieve_connection_pool klass.active_record_super
end
+
+ private
+
+ def class_to_pool
+ @class_to_pool[Process.pid]
+ end
+
+ def set_pool_for_spec(spec, pool)
+ @connection_pools[Process.pid][spec] = pool
+ end
+
+ def set_class_to_pool(name, pool)
+ @class_to_pool[Process.pid][name] = pool
+ pool
+ end
+
+ def get_pool_for_class(klass)
+ @class_to_pool[Process.pid].fetch(klass) {
+ c_to_p = @class_to_pool.values.find { |class_to_pool|
+ class_to_pool[klass]
+ }
+
+ if c_to_p
+ pool = c_to_p[klass]
+ pool = ConnectionAdapters::ConnectionPool.new pool.spec
+ set_pool_for_spec pool.spec, pool
+ set_class_to_pool klass, pool
+ else
+ set_class_to_pool klass, nil
+ end
+ }
+ end
end
class ConnectionManagement
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index eb8cff9610..174450eb00 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -2,9 +2,11 @@ module ActiveRecord
module ConnectionAdapters # :nodoc:
module DatabaseStatements
# Converts an arel AST to SQL
- def to_sql(arel)
+ def to_sql(arel, binds = [])
if arel.respond_to?(:ast)
- visitor.accept(arel.ast)
+ visitor.accept(arel.ast) do
+ quote(*binds.shift.reverse)
+ end
else
arel
end
@@ -13,19 +15,19 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select_all(arel, name = nil, binds = [])
- select(to_sql(arel), name, binds)
+ select(to_sql(arel, binds), name, binds)
end
# Returns a record hash with the column names as keys and column values
# as values.
- def select_one(arel, name = nil)
- result = select_all(arel, name)
+ def select_one(arel, name = nil, binds = [])
+ result = select_all(arel, name, binds)
result.first if result
end
# Returns a single value from a record
- def select_value(arel, name = nil)
- if result = select_one(arel, name)
+ def select_value(arel, name = nil, binds = [])
+ if result = select_one(arel, name, binds)
result.values.first
end
end
@@ -33,7 +35,7 @@ module ActiveRecord
# Returns an array of the values of the first column in a select:
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
def select_values(arel, name = nil)
- result = select_rows(to_sql(arel), name)
+ result = select_rows(to_sql(arel, []), name)
result.map { |v| v[0] }
end
@@ -84,19 +86,19 @@ module ActiveRecord
# If the next id was calculated in advance (as in Oracle), it should be
# passed in as +id_value+.
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
- sql, binds = sql_for_insert(to_sql(arel), pk, id_value, sequence_name, binds)
+ sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
value = exec_insert(sql, name, binds)
id_value || last_inserted_id(value)
end
# Executes the update statement and returns the number of rows affected.
def update(arel, name = nil, binds = [])
- exec_update(to_sql(arel), name, binds)
+ exec_update(to_sql(arel, binds), name, binds)
end
# Executes the delete statement and returns the number of rows affected.
def delete(arel, name = nil, binds = [])
- exec_delete(to_sql(arel), name, binds)
+ exec_delete(to_sql(arel, binds), name, binds)
end
# Checks whether there is currently no transaction active. This is done
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 6ba64bb88f..17377bad96 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -57,7 +57,7 @@ module ActiveRecord
def select_all(arel, name = nil, binds = [])
if @query_cache_enabled
- sql = to_sql(arel)
+ sql = to_sql(arel, binds)
cache_sql(sql, binds) { super(sql, name, binds) }
else
super
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 132ca10f79..7ee8f40631 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -6,7 +6,10 @@ require 'bigdecimal/util'
module ActiveRecord
module ConnectionAdapters #:nodoc:
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders) #:nodoc:
+ # Abstract representation of an index definition on a table. Instances of
+ # this type are typically created and returned by methods in database
+ # adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where) #:nodoc:
end
# Abstract representation of a column definition. Instances of this type
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 84c340770a..8b9e830040 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -56,7 +56,7 @@ module ActiveRecord
# Returns an array of Column objects for the table specified by +table_name+.
# See the concrete implementation for details on the expected parameter values.
- def columns(table_name, name = nil) end
+ def columns(table_name) end
# Checks to see if a column exists in a given table.
#
@@ -163,7 +163,7 @@ module ActiveRecord
yield td if block_given?
if options[:force] && table_exists?(table_name)
- drop_table(table_name)
+ drop_table(table_name, options)
end
create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
@@ -174,7 +174,7 @@ module ActiveRecord
end
# Creates a new join table with the name created using the lexical order of the first two
- # arguments. These arguments can be be a String or a Symbol.
+ # arguments. These arguments can be a String or a Symbol.
#
# # Creates a table called 'assemblies_parts' with no id.
# create_join_table(:assemblies, :parts)
@@ -294,7 +294,7 @@ module ActiveRecord
end
# Drops a table from the database.
- def drop_table(table_name)
+ def drop_table(table_name, options = {})
execute "DROP TABLE #{quote_table_name(table_name)}"
end
@@ -381,9 +381,16 @@ module ActiveRecord
#
# Note: mysql doesn't yet support index order (it accepts the syntax but ignores it)
#
+ # ====== Creating a partial index
+ # add_index(:accounts, [:branch_id, :party_id], :unique => true, :where => "active")
+ # generates
+ # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
+ #
+ # Note: only supported by PostgreSQL
+ #
def add_index(table_name, column_name, options = {})
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})"
+ index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
end
# Remove the given index from the table.
@@ -532,8 +539,8 @@ module ActiveRecord
# ===== Examples
# add_timestamps(:suppliers)
def add_timestamps(table_name)
- add_column table_name, :created_at, :datetime, :null => false
- add_column table_name, :updated_at, :datetime, :null => false
+ add_column table_name, :created_at, :datetime
+ add_column table_name, :updated_at, :datetime
end
# Removes the timestamp columns (created_at and updated_at) from the table definition.
@@ -581,6 +588,9 @@ module ActiveRecord
if Hash === options # legacy support, since this param was a string
index_type = options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
+ if supports_partial_index?
+ index_options = options[:where] ? " WHERE #{options[:where]}" : ""
+ end
else
index_type = options
end
@@ -593,7 +603,7 @@ module ActiveRecord
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
- [index_name, index_type, index_columns]
+ [index_name, index_type, index_columns, index_options]
end
def index_name_for_remove(table_name, options = {})
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index edea414db7..1d713e472b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -13,21 +13,24 @@ module ActiveRecord
autoload :Column
autoload :ConnectionSpecification
- autoload_under 'abstract' do
- autoload :IndexDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
- autoload :ColumnDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
- autoload :TableDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
- autoload :Table, 'active_record/connection_adapters/abstract/schema_definitions'
+ autoload_at 'active_record/connection_adapters/abstract/schema_definitions' do
+ autoload :IndexDefinition
+ autoload :ColumnDefinition
+ autoload :TableDefinition
+ autoload :Table
+ end
+ autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
+ autoload :ConnectionHandler
+ autoload :ConnectionManagement
+ end
+
+ autoload_under 'abstract' do
autoload :SchemaStatements
autoload :DatabaseStatements
autoload :DatabaseLimits
autoload :Quoting
-
autoload :ConnectionPool
- autoload :ConnectionHandler, 'active_record/connection_adapters/abstract/connection_pool'
- autoload :ConnectionManagement, 'active_record/connection_adapters/abstract/connection_pool'
-
autoload :QueryCache
end
@@ -54,7 +57,7 @@ module ActiveRecord
define_callbacks :checkout, :checkin
attr_accessor :visitor, :pool
- attr_reader :schema_cache, :last_use, :in_use
+ attr_reader :schema_cache, :last_use, :in_use, :logger
alias :in_use? :in_use
def initialize(connection, logger = nil, pool = nil) #:nodoc:
@@ -83,6 +86,11 @@ module ActiveRecord
end
end
+ def schema_cache=(cache)
+ cache.connection = self
+ @schema_cache = cache
+ end
+
def expire
@in_use = false
end
@@ -142,6 +150,11 @@ module ActiveRecord
false
end
+ # Does this adapter support partial indices?
+ def supports_partial_index?
+ false
+ end
+
# Does this adapter support explain? As of this writing sqlite3,
# mysql2, and postgresql are the only ones that do.
def supports_explain?
@@ -158,7 +171,7 @@ module ActiveRecord
# Returns a bind substitution value given a +column+ and list of current
# +binds+
def substitute_at(column, index)
- Arel.sql '?'
+ Arel::Nodes::BindParam.new '?'
end
# REFERENTIAL INTEGRITY ====================================
@@ -271,26 +284,25 @@ module ActiveRecord
protected
- def log(sql, name = "SQL", binds = [])
- @instrumenter.instrument(
- "sql.active_record",
- :sql => sql,
- :name => name,
- :connection_id => object_id,
- :binds => binds) { yield }
- rescue Exception => e
- message = "#{e.class.name}: #{e.message}: #{sql}"
- @logger.debug message if @logger
- exception = translate_exception(e, message)
- exception.set_backtrace e.backtrace
- raise exception
- end
-
- def translate_exception(e, message)
- # override in derived class
- ActiveRecord::StatementInvalid.new(message)
- end
-
+ def log(sql, name = "SQL", binds = [])
+ @instrumenter.instrument(
+ "sql.active_record",
+ :sql => sql,
+ :name => name,
+ :connection_id => object_id,
+ :binds => binds) { yield }
+ rescue Exception => e
+ message = "#{e.class.name}: #{e.message}: #{sql}"
+ @logger.error message if @logger
+ exception = translate_exception(e, message)
+ exception.set_backtrace e.backtrace
+ raise exception
+ end
+
+ def translate_exception(e, message)
+ # override in derived class
+ ActiveRecord::StatementInvalid.new(message)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 761e929fde..64f922b7ad 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/object/blank'
+require 'arel/visitors/bind_visitor'
module ActiveRecord
module ConnectionAdapters
@@ -122,12 +123,21 @@ module ActiveRecord
:boolean => { :name => "tinyint", :limit => 1 }
}
+ class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
+ include Arel::Visitors::BindVisitor
+ end
+
# FIXME: Make the first parameter more similar for the two adapters
def initialize(connection, logger, connection_options, config)
super(connection, logger)
@connection_options, @config = connection_options, config
@quoted_column_names, @quoted_table_names = {}, {}
- @visitor = Arel::Visitors::MySQL.new self
+
+ if config.fetch(:prepared_statements) { true }
+ @visitor = Arel::Visitors::MySQL.new self
+ else
+ @visitor = BindSubstitution.new self
+ end
end
def adapter_name #:nodoc:
@@ -365,7 +375,7 @@ module ActiveRecord
def tables(name = nil, database = nil, like = nil) #:nodoc:
sql = "SHOW TABLES "
- sql << "IN #{database} " if database
+ sql << "IN #{quote_table_name(database)} " if database
sql << "LIKE #{quote(like)}" if like
execute_and_free(sql, 'SCHEMA') do |result|
@@ -409,7 +419,7 @@ module ActiveRecord
end
# Returns an array of +Column+ objects for the table specified by +table_name+.
- def columns(table_name, name = nil)#:nodoc:
+ def columns(table_name)#:nodoc:
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
execute_and_free(sql, 'SCHEMA') do |result|
each_hash(result).map do |field|
@@ -427,7 +437,7 @@ module ActiveRecord
table, arguments = args.shift, args
method = :"#{command}_sql"
- if respond_to?(method)
+ if respond_to?(method, true)
send(method, table, *arguments)
else
raise "Unknown method called : #{method}(#{arguments.inspect})"
@@ -474,15 +484,26 @@ module ActiveRecord
# Maps logical Rails types to MySQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
- return super unless type.to_s == 'integer'
-
- case limit
- when 1; 'tinyint'
- when 2; 'smallint'
- when 3; 'mediumint'
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
- when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
+ case type.to_s
+ when 'integer'
+ case limit
+ when 1; 'tinyint'
+ when 2; 'smallint'
+ when 3; 'mediumint'
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
+ when 5..8; 'bigint'
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
+ end
+ when 'text'
+ case limit
+ when 0..0xff; 'tinytext'
+ when nil, 0x100..0xffff; 'text'
+ when 0x10000..0xffffff; 'mediumtext'
+ when 0x1000000..0xffffffff; 'longtext'
+ else raise(ActiveRecordError, "No text type has character length #{limit}")
+ end
+ else
+ super
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 2ecb198edb..78e54c4c9b 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -66,6 +66,25 @@ module ActiveRecord
end
end
+ def binary?
+ type == :binary
+ end
+
+ # Casts a Ruby value to something appropriate for writing to the database.
+ def type_cast_for_write(value)
+ return value unless number?
+
+ if value == false
+ 0
+ elsif value == true
+ 1
+ elsif value.is_a?(String) && value.blank?
+ nil
+ else
+ value
+ end
+ end
+
# Casts value (which is a String) to an appropriate instance.
def type_cast(value)
return nil if value.nil?
@@ -83,7 +102,6 @@ module ActiveRecord
when :date then klass.value_to_date(value)
when :binary then klass.binary_to_string(value)
when :boolean then klass.value_to_boolean(value)
- when :hstore then klass.cast_hstore(value)
else value
end
end
@@ -101,7 +119,7 @@ module ActiveRecord
when :date then "#{klass}.value_to_date(#{var_name})"
when :binary then "#{klass}.binary_to_string(#{var_name})"
when :boolean then "#{klass}.value_to_boolean(#{var_name})"
- when :hstore then "#{klass}.cast_hstore(#{var_name})"
+ when :hstore then "#{klass}.string_to_hstore(#{var_name})"
else var_name
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index c1332fde1a..3f45f23de8 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -32,6 +32,7 @@ module ActiveRecord
def initialize(connection, logger, connection_options, config)
super
+ @visitor = BindSubstitution.new self
configure_connection
end
@@ -65,10 +66,6 @@ module ActiveRecord
@connection.escape(string)
end
- def substitute_at(column, index)
- Arel.sql "\0"
- end
-
# CONNECTION MANAGEMENT ====================================
def active?
@@ -80,6 +77,7 @@ module ActiveRecord
disconnect!
connect
end
+ alias :reset! :reconnect!
# Disconnects from the database if already connected.
# Otherwise, this method does nothing.
@@ -90,15 +88,10 @@ module ActiveRecord
end
end
- def reset!
- disconnect!
- connect
- end
-
# DATABASE STATEMENTS ======================================
def explain(arel, binds = [])
- sql = "EXPLAIN #{to_sql(arel)}"
+ sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
start = Time.now
result = exec_query(sql, 'EXPLAIN', binds)
elapsed = Time.now - start
@@ -224,8 +217,7 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select(sql, name = nil, binds = [])
- binds = binds.dup
- exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name)
+ exec_query(sql, name)
end
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
@@ -235,17 +227,11 @@ module ActiveRecord
alias :create :insert_sql
def exec_insert(sql, name, binds)
- binds = binds.dup
-
- # Pretend to support bind parameters
- execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
+ execute to_sql(sql, binds), name
end
def exec_delete(sql, name, binds)
- binds = binds.dup
-
- # Pretend to support bind parameters
- execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
+ execute to_sql(sql, binds), name
@connection.affected_rows
end
alias :exec_update :exec_delete
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 5905242747..724dbff1f0 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -119,7 +119,7 @@ module ActiveRecord
private
def cache
- @cache[$$]
+ @cache[Process.pid]
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
new file mode 100644
index 0000000000..c82afc232c
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -0,0 +1,243 @@
+require 'active_record/connection_adapters/abstract_adapter'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class PostgreSQLAdapter < AbstractAdapter
+ module OID
+ class Type
+ def type; end
+
+ def type_cast_for_write(value)
+ value
+ end
+ end
+
+ class Identity < Type
+ def type_cast(value)
+ value
+ end
+ end
+
+ class Bytea < Type
+ def type_cast(value)
+ PGconn.unescape_bytea value
+ end
+ end
+
+ class Money < Type
+ def type_cast(value)
+ return if value.nil?
+
+ # Because money output is formatted according to the locale, there are two
+ # cases to consider (note the decimal separators):
+ # (1) $12,345,678.12
+ # (2) $12.345.678,12
+
+ case value
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
+ value.gsub!(/[^-\d.]/, '')
+ when /^-?\D+[\d.]+,\d{2}$/ # (2)
+ value.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
+ end
+
+ ConnectionAdapters::Column.value_to_decimal value
+ end
+ end
+
+ class Vector < Type
+ attr_reader :delim, :subtype
+
+ # +delim+ corresponds to the `typdelim` column in the pg_types
+ # table. +subtype+ is derived from the `typelem` column in the
+ # pg_types table.
+ def initialize(delim, subtype)
+ @delim = delim
+ @subtype = subtype
+ end
+
+ # FIXME: this should probably split on +delim+ and use +subtype+
+ # to cast the values. Unfortunately, the current Rails behavior
+ # is to just return the string.
+ def type_cast(value)
+ value
+ end
+ end
+
+ class Integer < Type
+ def type_cast(value)
+ return if value.nil?
+
+ value.to_i rescue value ? 1 : 0
+ end
+ end
+
+ class Boolean < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::Column.value_to_boolean value
+ end
+ end
+
+ class Timestamp < Type
+ def type; :timestamp; end
+
+ def type_cast(value)
+ return if value.nil?
+
+ # FIXME: probably we can improve this since we know it is PG
+ # specific
+ ConnectionAdapters::PostgreSQLColumn.string_to_time value
+ end
+ end
+
+ class Date < Type
+ def type; :datetime; end
+
+ def type_cast(value)
+ return if value.nil?
+
+ # FIXME: probably we can improve this since we know it is PG
+ # specific
+ ConnectionAdapters::Column.value_to_date value
+ end
+ end
+
+ class Time < Type
+ def type_cast(value)
+ return if value.nil?
+
+ # FIXME: probably we can improve this since we know it is PG
+ # specific
+ ConnectionAdapters::Column.string_to_dummy_time value
+ end
+ end
+
+ class Float < Type
+ def type_cast(value)
+ return if value.nil?
+
+ value.to_f
+ end
+ end
+
+ class Decimal < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::Column.value_to_decimal value
+ end
+ end
+
+ class Hstore < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::PostgreSQLColumn.string_to_hstore value
+ end
+ end
+
+ class TypeMap
+ def initialize
+ @mapping = {}
+ end
+
+ def []=(oid, type)
+ @mapping[oid] = type
+ end
+
+ def [](oid)
+ @mapping[oid]
+ end
+
+ def key?(oid)
+ @mapping.key? oid
+ end
+
+ def fetch(ftype, fmod)
+ # The type for the numeric depends on the width of the field,
+ # so we'll do something special here.
+ #
+ # When dealing with decimal columns:
+ #
+ # places after decimal = fmod - 4 & 0xffff
+ # places before decimal = (fmod - 4) >> 16 & 0xffff
+ if ftype == 1700 && (fmod - 4 & 0xffff).zero?
+ ftype = 23
+ end
+
+ @mapping.fetch(ftype) { |oid| yield oid, fmod }
+ end
+ end
+
+ TYPE_MAP = TypeMap.new # :nodoc:
+
+ # When the PG adapter connects, the pg_type table is queried. The
+ # key of this hash maps to the `typname` column from the table.
+ # TYPE_MAP is then dynamically built with oids as the key and type
+ # objects as values.
+ NAMES = Hash.new { |h,k| # :nodoc:
+ h[k] = OID::Identity.new
+ }
+
+ # Register an OID type named +name+ with a typcasting object in
+ # +type+. +name+ should correspond to the `typname` column in
+ # the `pg_type` table.
+ def self.register_type(name, type)
+ NAMES[name] = type
+ end
+
+ # Alias the +old+ type to the +new+ type.
+ def self.alias_type(new, old)
+ NAMES[new] = NAMES[old]
+ end
+
+ # Is +name+ a registered type?
+ def self.registered_type?(name)
+ NAMES.key? name
+ end
+
+ register_type 'int2', OID::Integer.new
+ alias_type 'int4', 'int2'
+ alias_type 'int8', 'int2'
+ alias_type 'oid', 'int2'
+
+ register_type 'numeric', OID::Decimal.new
+ register_type 'text', OID::Identity.new
+ alias_type 'varchar', 'text'
+ alias_type 'char', 'text'
+ alias_type 'bpchar', 'text'
+ alias_type 'xml', 'text'
+
+ # FIXME: why are we keeping these types as strings?
+ alias_type 'tsvector', 'text'
+ alias_type 'interval', 'text'
+ alias_type 'cidr', 'text'
+ alias_type 'inet', 'text'
+ alias_type 'macaddr', 'text'
+ alias_type 'bit', 'text'
+ alias_type 'varbit', 'text'
+
+ # FIXME: I don't think this is correct. We should probably be returning a parsed date,
+ # but the tests pass with a string returned.
+ register_type 'timestamptz', OID::Identity.new
+
+ register_type 'money', OID::Money.new
+ register_type 'bytea', OID::Bytea.new
+ register_type 'bool', OID::Boolean.new
+
+ register_type 'float4', OID::Float.new
+ alias_type 'float8', 'float4'
+
+ register_type 'timestamp', OID::Timestamp.new
+ register_type 'date', OID::Date.new
+ register_type 'time', OID::Time.new
+
+ register_type 'path', OID::Identity.new
+ register_type 'polygon', OID::Identity.new
+ register_type 'circle', OID::Identity.new
+ register_type 'hstore', OID::Hstore.new
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 1d8e5d813a..10a178e369 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1,6 +1,8 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_support/core_ext/object/blank'
require 'active_record/connection_adapters/statement_pool'
+require 'active_record/connection_adapters/postgresql/oid'
+require 'arel/visitors/bind_visitor'
# Make sure we're using pg high enough for PGResult#values
gem 'pg', '~> 0.11'
@@ -14,7 +16,7 @@ module ActiveRecord
# Forward any unused config params to PGconn.connect.
[:statement_limit, :encoding, :min_messages, :schema_search_path,
- :schema_order, :adapter, :pool, :wait_timeout,
+ :schema_order, :adapter, :pool, :wait_timeout, :template,
:reaping_frequency].each do |key|
conn_params.delete key
end
@@ -34,7 +36,8 @@ module ActiveRecord
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
# Instantiates a new PostgreSQL column definition in a table.
- def initialize(name, default, sql_type = nil, null = true)
+ def initialize(name, default, oid_type, sql_type = nil, null = true)
+ @oid_type = oid_type
super(name, self.class.extract_value_from_default(default), sql_type, null)
end
@@ -52,180 +55,192 @@ module ActiveRecord
end
end
- def cast_hstore(object)
+ def hstore_to_string(object)
if Hash === object
object.map { |k,v|
"#{escape_hstore(k)}=>#{escape_hstore(v)}"
- }.join ', '
+ }.join ','
else
- kvs = object.scan(/(?<!\\)".*?(?<!\\)"/).map { |o|
- unescape_hstore(o[1...-1])
- }
- Hash[kvs.each_slice(2).to_a]
+ object
end
end
- private
- HSTORE_ESCAPE = {
- ' ' => '\\ ',
- '\\' => '\\\\',
- '"' => '\\"',
- '=' => '\\=',
- }
- HSTORE_ESCAPE_RE = Regexp.union(HSTORE_ESCAPE.keys)
- HSTORE_UNESCAPE = HSTORE_ESCAPE.invert
- HSTORE_UNESCAPE_RE = Regexp.union(HSTORE_UNESCAPE.keys)
-
- def unescape_hstore(value)
- value.gsub(HSTORE_UNESCAPE_RE) do |match|
- HSTORE_UNESCAPE[match]
+ def string_to_hstore(string)
+ if string.nil?
+ nil
+ elsif String === string
+ Hash[string.scan(HstorePair).map { |k,v|
+ v = v.upcase == 'NULL' ? nil : v.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1')
+ k = k.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1')
+ [k,v]
+ }]
+ else
+ string
end
end
+ private
+ HstorePair = begin
+ quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
+ unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
+ /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/
+ end
+
def escape_hstore(value)
- value.gsub(HSTORE_ESCAPE_RE) do |match|
- HSTORE_ESCAPE[match]
- end
+ value.nil? ? 'NULL'
+ : value =~ /[=\s,>]/ ? '"%s"' % value.gsub(/(["\\])/, '\\\\\1')
+ : value == "" ? '""'
+ : value.to_s.gsub(/(["\\])/, '\\\\\1')
end
end
# :startdoc:
- private
- def extract_limit(sql_type)
- case sql_type
- when /^bigint/i; 8
- when /^smallint/i; 2
- else super
- end
- end
-
- # Extracts the scale from PostgreSQL-specific data types.
- def extract_scale(sql_type)
- # Money type has a fixed scale of 2.
- sql_type =~ /^money/ ? 2 : super
- end
-
- # Extracts the precision from PostgreSQL-specific data types.
- def extract_precision(sql_type)
- if sql_type == 'money'
- self.class.money_precision
- else
- super
- end
- end
-
- # Maps PostgreSQL-specific data types to logical Rails types.
- def simplified_type(field_type)
- case field_type
- # Numeric and monetary types
- when /^(?:real|double precision)$/
- :float
- # Monetary types
- when 'money'
- :decimal
- when 'hstore'
- :hstore
+ # Extracts the value from a PostgreSQL column default definition.
+ def self.extract_value_from_default(default)
+ # This is a performance optimization for Ruby 1.9.2 in development.
+ # If the value is nil, we return nil straight away without checking
+ # the regular expressions. If we check each regular expression,
+ # Regexp#=== will call NilClass#to_str, which will trigger
+ # method_missing (defined by whiny nil in ActiveSupport) which
+ # makes this method very very slow.
+ return default unless default
+
+ case default
+ # Numeric types
+ when /\A\(?(-?\d+(\.\d*)?\)?)\z/
+ $1
# Character types
- when /^(?:character varying|bpchar)(?:\(\d+\))?$/
- :string
+ when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
+ $1
+ # Character types (8.1 formatting)
+ when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
+ $1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
# Binary data types
- when 'bytea'
- :binary
+ when /\A'(.*)'::bytea\z/m
+ $1
# Date/time types
- when /^timestamp with(?:out)? time zone$/
- :datetime
- when 'interval'
- :string
+ when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
+ $1
+ when /\A'(.*)'::interval\z/
+ $1
+ # Boolean type
+ when 'true'
+ true
+ when 'false'
+ false
# Geometric types
- when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
- :string
+ when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
+ $1
# Network address types
- when /^(?:cidr|inet|macaddr)$/
- :string
- # Bit strings
- when /^bit(?: varying)?(?:\(\d+\))?$/
- :string
+ when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
+ $1
+ # Bit string types
+ when /\AB'(.*)'::"?bit(?: varying)?"?\z/
+ $1
# XML type
- when 'xml'
- :xml
- # tsvector type
- when 'tsvector'
- :tsvector
+ when /\A'(.*)'::xml\z/m
+ $1
# Arrays
- when /^\D+\[\]$/
- :string
+ when /\A'(.*)'::"?\D+"?\[\]\z/
+ $1
+ # Hstore
+ when /\A'(.*)'::hstore\z/
+ $1
# Object identifier types
- when 'oid'
- :integer
- # UUID type
- when 'uuid'
- :string
- # Small and big integer types
- when /^(?:small|big)int$/
- :integer
- # Pass through all types that are not specific to PostgreSQL.
+ when /\A-?\d+\z/
+ $1
else
- super
- end
+ # Anything else is blank, some user type, or some function
+ # and we can't know the value of that, so return nil.
+ nil
end
+ end
- # Extracts the value from a PostgreSQL column default definition.
- def self.extract_value_from_default(default)
- # This is a performance optimization for Ruby 1.9.2 in development.
- # If the value is nil, we return nil straight away without checking
- # the regular expressions. If we check each regular expression,
- # Regexp#=== will call NilClass#to_str, which will trigger
- # method_missing (defined by whiny nil in ActiveSupport) which
- # makes this method very very slow.
- return default unless default
-
- case default
- # Numeric types
- when /\A\(?(-?\d+(\.\d*)?\)?)\z/
- $1
- # Character types
- when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
- $1
- # Character types (8.1 formatting)
- when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
- $1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
- # Binary data types
- when /\A'(.*)'::bytea\z/m
- $1
- # Date/time types
- when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
- $1
- when /\A'(.*)'::interval\z/
- $1
- # Boolean type
- when 'true'
- true
- when 'false'
- false
- # Geometric types
- when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
- $1
- # Network address types
- when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
- $1
- # Bit string types
- when /\AB'(.*)'::"?bit(?: varying)?"?\z/
- $1
- # XML type
- when /\A'(.*)'::xml\z/m
- $1
- # Arrays
- when /\A'(.*)'::"?\D+"?\[\]\z/
- $1
- # Object identifier types
- when /\A-?\d+\z/
- $1
- else
- # Anything else is blank, some user type, or some function
- # and we can't know the value of that, so return nil.
- nil
- end
+ def type_cast(value)
+ return if value.nil?
+ return super if encoded?
+
+ @oid_type.type_cast value
+ end
+
+ private
+ def extract_limit(sql_type)
+ case sql_type
+ when /^bigint/i; 8
+ when /^smallint/i; 2
+ else super
+ end
+ end
+
+ # Extracts the scale from PostgreSQL-specific data types.
+ def extract_scale(sql_type)
+ # Money type has a fixed scale of 2.
+ sql_type =~ /^money/ ? 2 : super
+ end
+
+ # Extracts the precision from PostgreSQL-specific data types.
+ def extract_precision(sql_type)
+ if sql_type == 'money'
+ self.class.money_precision
+ else
+ super
+ end
+ end
+
+ # Maps PostgreSQL-specific data types to logical Rails types.
+ def simplified_type(field_type)
+ case field_type
+ # Numeric and monetary types
+ when /^(?:real|double precision)$/
+ :float
+ # Monetary types
+ when 'money'
+ :decimal
+ when 'hstore'
+ :hstore
+ # Character types
+ when /^(?:character varying|bpchar)(?:\(\d+\))?$/
+ :string
+ # Binary data types
+ when 'bytea'
+ :binary
+ # Date/time types
+ when /^timestamp with(?:out)? time zone$/
+ :datetime
+ when 'interval'
+ :string
+ # Geometric types
+ when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
+ :string
+ # Network address types
+ when /^(?:cidr|inet|macaddr)$/
+ :string
+ # Bit strings
+ when /^bit(?: varying)?(?:\(\d+\))?$/
+ :string
+ # XML type
+ when 'xml'
+ :xml
+ # tsvector type
+ when 'tsvector'
+ :tsvector
+ # Arrays
+ when /^\D+\[\]$/
+ :string
+ # Object identifier types
+ when 'oid'
+ :integer
+ # UUID type
+ when 'uuid'
+ :string
+ # Small and big integer types
+ when /^(?:small|big)int$/
+ :integer
+ # Pass through all types that are not specific to PostgreSQL.
+ else
+ super
end
+ end
end
# The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
@@ -284,7 +299,8 @@ module ActiveRecord
:binary => { :name => "bytea" },
:boolean => { :name => "boolean" },
:xml => { :name => "xml" },
- :tsvector => { :name => "tsvector" }
+ :tsvector => { :name => "tsvector" },
+ :hstore => { :name => "hstore" }
}
# Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -302,6 +318,10 @@ module ActiveRecord
true
end
+ def supports_partial_index?
+ true
+ end
+
class StatementPool < ConnectionAdapters::StatementPool
def initialize(connection, max)
super
@@ -340,7 +360,7 @@ module ActiveRecord
private
def cache
- @cache[$$]
+ @cache[Process.pid]
end
def dealloc(key)
@@ -354,11 +374,23 @@ module ActiveRecord
end
end
+ class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
+ include Arel::Visitors::BindVisitor
+ end
+
# Initializes and connects a PostgreSQL adapter.
def initialize(connection, logger, connection_parameters, config)
super(connection, logger)
+
+ if config.fetch(:prepared_statements) { true }
+ @visitor = Arel::Visitors::PostgreSQL.new self
+ else
+ @visitor = BindSubstitution.new self
+ end
+
+ connection_parameters.delete :prepared_statements
+
@connection_parameters, @config = connection_parameters, config
- @visitor = Arel::Visitors::PostgreSQL.new self
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
@local_tz = nil
@@ -372,6 +404,7 @@ module ActiveRecord
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
end
+ initialize_type_map
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
end
@@ -455,14 +488,14 @@ module ActiveRecord
# Escapes binary strings for bytea input to the database.
def escape_bytea(value)
- @connection.escape_bytea(value) if value
+ PGconn.escape_bytea(value) if value
end
# Unescapes bytea output from a database to the binary string it represents.
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
# on escaped binary output from database drive.
def unescape_bytea(value)
- @connection.unescape_bytea(value) if value
+ PGconn.unescape_bytea(value) if value
end
# Quotes PostgreSQL-specific data types for SQL input.
@@ -470,6 +503,11 @@ module ActiveRecord
return super unless column
case value
+ when Hash
+ case column.sql_type
+ when 'hstore' then super(PostgreSQLColumn.hstore_to_string(value), column)
+ else super
+ end
when Float
return super unless value.infinite? && column.type == :datetime
"'#{value.to_s.downcase}'"
@@ -501,6 +539,9 @@ module ActiveRecord
when String
return super unless 'bytea' == column.sql_type
{ :value => value, :format => 1 }
+ when Hash
+ return super unless 'hstore' == column.sql_type
+ PostgreSQLColumn.hstore_to_string(value)
else
super
end
@@ -571,7 +612,7 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
def explain(arel, binds = [])
- sql = "EXPLAIN #{to_sql(arel)}"
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
end
@@ -693,7 +734,14 @@ module ActiveRecord
end
def substitute_at(column, index)
- Arel.sql("$#{index + 1}")
+ Arel::Nodes::BindParam.new "$#{index + 1}"
+ end
+
+ class Result < ActiveRecord::Result
+ def initialize(columns, rows, column_types)
+ super(columns, rows)
+ @column_types = column_types
+ end
end
def exec_query(sql, name = 'SQL', binds = [])
@@ -701,7 +749,17 @@ module ActiveRecord
result = binds.empty? ? exec_no_cache(sql, binds) :
exec_cache(sql, binds)
- ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
+ types = {}
+ result.fields.each_with_index do |fname, i|
+ ftype = result.ftype i
+ fmod = result.fmod i
+ types[fname] = OID::TYPE_MAP.fetch(ftype, fmod) { |oid, mod|
+ warn "unknown OID: #{fname}(#{oid}) (#{sql})"
+ OID::Identity.new
+ }
+ end
+
+ ret = Result.new(result.fields, result.values, types)
result.clear
return ret
end
@@ -885,16 +943,20 @@ module ActiveRecord
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
+ where = inddef.scan(/WHERE (.+)$/).flatten[0]
- column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where)
end.compact
end
# Returns the list of all column definitions for a table.
- def columns(table_name, name = nil)
+ def columns(table_name)
# Limit, precision, and scale are all handled by the superclass.
- column_definitions(table_name).collect do |column_name, type, default, notnull|
- PostgreSQLColumn.new(column_name, default, type, notnull == 'f')
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
+ oid = OID::TYPE_MAP.fetch(oid.to_i, fmod.to_i) {
+ OID::Identity.new
+ }
+ PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
end
end
@@ -916,6 +978,27 @@ module ActiveRecord
end_sql
end
+ # Returns an array of schema names.
+ def schema_names
+ query(<<-SQL).flatten
+ SELECT nspname
+ FROM pg_namespace
+ WHERE nspname !~ '^pg_.*'
+ AND nspname NOT IN ('information_schema')
+ ORDER by nspname;
+ SQL
+ end
+
+ # Creates a schema for the given schema name.
+ def create_schema schema_name
+ execute "CREATE SCHEMA #{schema_name}"
+ end
+
+ # Drops the schema for the given schema name.
+ def drop_schema schema_name
+ execute "DROP SCHEMA #{schema_name} CASCADE"
+ end
+
# Sets the schema search path to a string of comma-separated schema names.
# Names beginning with $ have to be quoted (e.g. $user => '$user').
# See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
@@ -923,7 +1006,7 @@ module ActiveRecord
# This should be not be called manually but set in database.yml.
def schema_search_path=(schema_csv)
if schema_csv
- execute "SET search_path TO #{schema_csv}"
+ execute("SET search_path TO #{schema_csv}", 'SCHEMA')
@schema_search_path = schema_csv
end
end
@@ -983,26 +1066,46 @@ module ActiveRecord
def pk_and_sequence_for(table) #:nodoc:
# First try looking for a sequence with a dependency on the
# given table's primary key.
- result = exec_query(<<-end_sql, 'SCHEMA').rows.first
- SELECT attr.attname, ns.nspname, seq.relname
- FROM pg_class seq
- INNER JOIN pg_depend dep ON seq.oid = dep.objid
- INNER JOIN pg_attribute attr ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
- INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
- INNER JOIN pg_namespace ns ON seq.relnamespace = ns.oid
- WHERE seq.relkind = 'S'
- AND cons.contype = 'p'
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
+ result = query(<<-end_sql, 'PK and serial sequence')[0]
+ SELECT attr.attname, seq.relname
+ FROM pg_class seq,
+ pg_attribute attr,
+ pg_depend dep,
+ pg_namespace name,
+ pg_constraint cons
+ WHERE seq.oid = dep.objid
+ AND seq.relkind = 'S'
+ AND attr.attrelid = dep.refobjid
+ AND attr.attnum = dep.refobjsubid
+ AND attr.attrelid = cons.conrelid
+ AND attr.attnum = cons.conkey[1]
+ AND cons.contype = 'p'
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
end_sql
- # [primary_key, sequence]
- if result.second == 'public' then
- sequence = result.last
- else
- sequence = result.second+'.'+result.last
+ if result.nil? or result.empty?
+ # If that fails, try parsing the primary key's default value.
+ # Support the 7.x and 8.0 nextval('foo'::text) as well as
+ # the 8.1+ nextval('foo'::regclass).
+ result = query(<<-end_sql, 'PK and custom sequence')[0]
+ SELECT attr.attname,
+ CASE
+ WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
+ substr(split_part(def.adsrc, '''', 2),
+ strpos(split_part(def.adsrc, '''', 2), '.')+1)
+ ELSE split_part(def.adsrc, '''', 2)
+ END
+ FROM pg_class t
+ JOIN pg_attribute attr ON (t.oid = attrelid)
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
+ WHERE t.oid = '#{quote_table_name(table)}'::regclass
+ AND cons.contype = 'p'
+ AND def.adsrc ~* 'nextval'
+ end_sql
end
- [result.first, sequence]
+ [result.first, result.last]
rescue
nil
end
@@ -1107,7 +1210,7 @@ module ActiveRecord
# Construct a clean list of column names from the ORDER BY clause, removing
# any ASC/DESC modifiers
- order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*/i, '') }
+ order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '') }
order_columns.delete_if { |c| c.blank? }
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
@@ -1151,6 +1254,22 @@ module ActiveRecord
end
private
+ def initialize_type_map
+ result = execute('SELECT oid, typname, typelem, typdelim FROM pg_type', 'SCHEMA')
+ leaves, nodes = result.partition { |row| row['typelem'] == '0' }
+
+ # populate the leaf nodes
+ leaves.find_all { |row| OID.registered_type? row['typname'] }.each do |row|
+ OID::TYPE_MAP[row['oid'].to_i] = OID::NAMES[row['typname']]
+ end
+
+ # populate composite types
+ nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
+ vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
+ OID::TYPE_MAP[row['oid'].to_i] = vector
+ end
+ end
+
FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
def exec_no_cache(sql, binds)
@@ -1173,7 +1292,11 @@ module ActiveRecord
# prepared statements whose return value may have changed is
# FEATURE_NOT_SUPPORTED. Check here for more details:
# http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
- code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
+ begin
+ code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
+ rescue
+ raise e
+ end
if FEATURE_NOT_SUPPORTED == code
@statements.delete sql_key(sql)
retry
@@ -1280,7 +1403,7 @@ module ActiveRecord
# - ::regclass is a function that gives the id for a table name
def column_definitions(table_name) #:nodoc:
exec_query(<<-end_sql, 'SCHEMA').rows
- SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull, a.atttypid, a.atttypmod
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index 4e8932a695..aad1f9a7ef 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -1,26 +1,17 @@
module ActiveRecord
module ConnectionAdapters
class SchemaCache
- attr_reader :columns, :columns_hash, :primary_keys, :tables
- attr_reader :connection
+ attr_reader :columns, :columns_hash, :primary_keys, :tables, :version
+ attr_accessor :connection
def initialize(conn)
@connection = conn
- @tables = {}
- @columns = Hash.new do |h, table_name|
- h[table_name] = conn.columns(table_name, "#{table_name} Columns")
- end
-
- @columns_hash = Hash.new do |h, table_name|
- h[table_name] = Hash[columns[table_name].map { |col|
- [col.name, col]
- }]
- end
-
- @primary_keys = Hash.new do |h, table_name|
- h[table_name] = table_exists?(table_name) ? conn.primary_key(table_name) : nil
- end
+ @columns = {}
+ @columns_hash = {}
+ @primary_keys = {}
+ @tables = {}
+ prepare_default_proc
end
# A cached lookup for table existence.
@@ -30,12 +21,22 @@ module ActiveRecord
@tables[name] = connection.table_exists?(name)
end
+ # Add internal cache for table with +table_name+.
+ def add(table_name)
+ if table_exists?(table_name)
+ @primary_keys[table_name]
+ @columns[table_name]
+ @columns_hash[table_name]
+ end
+ end
+
# Clears out internal caches
def clear!
@columns.clear
@columns_hash.clear
@primary_keys.clear
@tables.clear
+ @version = nil
end
# Clear out internal caches for table with +table_name+.
@@ -45,6 +46,37 @@ module ActiveRecord
@primary_keys.delete table_name
@tables.delete table_name
end
+
+ def marshal_dump
+ # if we get current version during initialization, it happens stack over flow.
+ @version = ActiveRecord::Migrator.current_version
+ [@version] + [:@columns, :@columns_hash, :@primary_keys, :@tables].map do |val|
+ self.instance_variable_get(val).inject({}) { |h, v| h[v[0]] = v[1]; h }
+ end
+ end
+
+ def marshal_load(array)
+ @version, @columns, @columns_hash, @primary_keys, @tables = array
+ prepare_default_proc
+ end
+
+ private
+
+ def prepare_default_proc
+ @columns.default_proc = Proc.new do |h, table_name|
+ h[table_name] = connection.columns(table_name)
+ end
+
+ @columns_hash.default_proc = Proc.new do |h, table_name|
+ h[table_name] = Hash[columns[table_name].map { |col|
+ [col.name, col]
+ }]
+ end
+
+ @primary_keys.default_proc = Proc.new do |h, table_name|
+ h[table_name] = table_exists?(table_name) ? connection.primary_key(table_name) : nil
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 6391ea3800..3d8dfab05c 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -1,6 +1,6 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/connection_adapters/statement_pool'
-require 'active_support/core_ext/string/encoding'
+require 'arel/visitors/bind_visitor'
module ActiveRecord
module ConnectionAdapters #:nodoc:
@@ -69,12 +69,21 @@ module ActiveRecord
end
end
+ class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
+ include Arel::Visitors::BindVisitor
+ end
+
def initialize(connection, logger, config)
super(connection, logger)
@statements = StatementPool.new(@connection,
config.fetch(:statement_limit) { 1000 })
@config = config
- @visitor = Arel::Visitors::SQLite.new self
+
+ if config.fetch(:prepared_statements) { true }
+ @visitor = Arel::Visitors::SQLite.new self
+ else
+ @visitor = BindSubstitution.new self
+ end
end
def adapter_name #:nodoc:
@@ -193,7 +202,7 @@ module ActiveRecord
value = super
if column.type == :string && value.encoding == Encoding::ASCII_8BIT
- @logger.error "Binary data inserted for `string` type on column `#{column.name}`"
+ logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger
value.encode! 'utf-8'
end
value
@@ -202,7 +211,7 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
def explain(arel, binds = [])
- sql = "EXPLAIN QUERY PLAN #{to_sql(arel)}"
+ sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
end
@@ -324,7 +333,7 @@ module ActiveRecord
end
# Returns an array of +SQLiteColumn+ objects for the table specified by +table_name+.
- def columns(table_name, name = nil) #:nodoc:
+ def columns(table_name) #:nodoc:
table_structure(table_name).map do |field|
case field["dflt_value"]
when /^null$/i