diff options
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/abstract_adapter.rb')
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/abstract_adapter.rb | 203 |
1 files changed, 154 insertions, 49 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 3c94bad208..e0a4af5359 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -1,11 +1,14 @@ require 'date' require 'bigdecimal' require 'bigdecimal/util' +require 'active_record/type' require 'active_support/core_ext/benchmark' require 'active_record/connection_adapters/schema_cache' require 'active_record/connection_adapters/abstract/schema_dumper' require 'active_record/connection_adapters/abstract/schema_creation' require 'monitor' +require 'arel/collectors/bind' +require 'arel/collectors/sql_string' module ActiveRecord module ConnectionAdapters # :nodoc: @@ -21,6 +24,7 @@ module ActiveRecord autoload :TableDefinition autoload :Table autoload :AlterTable + autoload :TimestampDefaultDeprecation end autoload_at 'active_record/connection_adapters/abstract/connection_pool' do @@ -39,7 +43,8 @@ module ActiveRecord end autoload_at 'active_record/connection_adapters/abstract/transaction' do - autoload :ClosedTransaction + autoload :TransactionManager + autoload :NullTransaction autoload :RealTransaction autoload :SavepointTransaction autoload :TransactionState @@ -59,6 +64,7 @@ module ActiveRecord # Most of the methods in the adapter are useful during migrations. Most # notably, the instance methods provided by SchemaStatement are very useful. class AbstractAdapter + ADAPTER_NAME = 'Abstract'.freeze include Quoting, DatabaseStatements, SchemaStatements include DatabaseLimits include QueryCache @@ -71,8 +77,8 @@ module ActiveRecord define_callbacks :checkout, :checkin attr_accessor :visitor, :pool - attr_reader :schema_cache, :last_use, :in_use, :logger - alias :in_use? :in_use + attr_reader :schema_cache, :owner, :logger + alias :in_use? :owner def self.type_cast_config_to_integer(config) if config =~ SIMPLE_INT @@ -90,13 +96,14 @@ module ActiveRecord end end + attr_reader :prepared_statements + def initialize(connection, logger = nil, pool = nil) #:nodoc: super() @connection = connection - @in_use = false + @owner = nil @instrumenter = ActiveSupport::Notifications.instrumenter - @last_use = false @logger = logger @pool = pool @schema_cache = SchemaCache.new self @@ -104,6 +111,26 @@ module ActiveRecord @prepared_statements = false end + class BindCollector < Arel::Collectors::Bind + def compile(bvs, conn) + super(bvs.map { |bv| conn.quote(*bv.reverse) }) + end + end + + class SQLString < Arel::Collectors::SQLString + def compile(bvs, conn) + super(bvs) + end + end + + def collector + if prepared_statements + SQLString.new + else + BindCollector.new + end + end + def valid_type?(type) true end @@ -114,9 +141,8 @@ module ActiveRecord def lease synchronize do - unless in_use - @in_use = true - @last_use = Time.now + unless in_use? + @owner = Thread.current end end end @@ -127,49 +153,35 @@ module ActiveRecord end def expire - @in_use = false - end - - def unprepared_visitor - self.class::BindSubstitution.new self + @owner = nil end def unprepared_statement old_prepared_statements, @prepared_statements = @prepared_statements, false - old_visitor, @visitor = @visitor, unprepared_visitor yield ensure - @visitor, @prepared_statements = old_visitor, old_prepared_statements + @prepared_statements = old_prepared_statements end # Returns the human-readable name of the adapter. Use mixed case - one # can always use downcase if needed. def adapter_name - 'Abstract' + self.class::ADAPTER_NAME end - # Does this adapter support migrations? Backend specific, as the - # abstract adapter always returns +false+. + # Does this adapter support migrations? def supports_migrations? false end # Can this adapter determine the primary key for tables not attached - # to an Active Record class, such as join tables? Backend specific, as - # the abstract adapter always returns +false+. + # to an Active Record class, such as join tables? def supports_primary_key? false end - # Does this adapter support using DISTINCT within COUNT? This is +true+ - # for all adapters except sqlite. - def supports_count_distinct? - true - end - # Does this adapter support DDL rollbacks in transactions? That is, would - # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL, - # SQL Server, and others support this. MySQL and others do not. + # CREATE TABLE or ALTER TABLE get rolled back by a transaction? def supports_ddl_transactions? false end @@ -178,8 +190,7 @@ module ActiveRecord false end - # Does this adapter support savepoints? PostgreSQL and MySQL do, - # SQLite < 3.6.8 does not. + # Does this adapter support savepoints? def supports_savepoints? false end @@ -187,7 +198,6 @@ module ActiveRecord # Should primary key values be selected from their corresponding # sequence before the insert statement? If true, next_sequence_value # is called before each insert to set the record's primary key. - # This is false for all adapters but Firebird. def prefetch_primary_key?(table_name = nil) false end @@ -202,8 +212,7 @@ module ActiveRecord false end - # Does this adapter support explain? As of this writing sqlite3, - # mysql2, and postgresql are the only ones that do. + # Does this adapter support explain? def supports_explain? false end @@ -213,12 +222,27 @@ module ActiveRecord false end - # Does this adapter support database extensions? As of this writing only - # postgresql does. + # Does this adapter support database extensions? def supports_extensions? false end + # Does this adapter support creating indexes in the same statement as + # creating the table? + def supports_indexes_in_create? + false + end + + # Does this adapter support creating foreign key constraints? + def supports_foreign_keys? + false + end + + # Does this adapter support views? + def supports_views? + false + end + # This is meant to be implemented by the adapters that support extensions def disable_extension(name) end @@ -227,24 +251,22 @@ module ActiveRecord def enable_extension(name) end - # A list of extensions, to be filled in by adapters that support them. At - # the moment only postgresql does. + # A list of extensions, to be filled in by adapters that support them. def extensions [] end # A list of index algorithms, to be filled by adapters that support them. - # MySQL and PostgreSQL have support for them right now. def index_algorithms {} end # QUOTING ================================================== - # Returns a bind substitution value given a bind +index+ and +column+ + # Returns a bind substitution value given a bind +column+ # NOTE: The column param is currently being used by the sqlserver-adapter - def substitute_at(column, index) - Arel::Nodes::BindParam.new '?' + def substitute_at(column, _unused = 0) + Arel::Nodes::BindParam.new end # REFERENTIAL INTEGRITY ==================================== @@ -295,7 +317,6 @@ module ActiveRecord end # Returns true if its required to reload the connection between requests for development mode. - # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite. def requires_reloading? false end @@ -317,10 +338,6 @@ module ActiveRecord @connection end - def open_transactions - @transaction.number - end - def create_savepoint(name = nil) end @@ -330,16 +347,22 @@ module ActiveRecord def release_savepoint(name = nil) end - def case_sensitive_modifier(node) + def case_sensitive_modifier(node, table_attribute) node end + def case_sensitive_comparison(table, attribute, column, value) + table_attr = table[attribute] + value = case_sensitive_modifier(value, table_attr) unless value.nil? + table_attr.eq(value) + end + def case_insensitive_comparison(table, attribute, column, value) table[attribute].lower.eq(table.lower(value)) end def current_savepoint_name - "active_record_#{open_transactions}" + current_transaction.savepoint_name end # Check the connection back in to the connection pool @@ -347,8 +370,84 @@ module ActiveRecord pool.checkin self end + def type_map # :nodoc: + @type_map ||= Type::TypeMap.new.tap do |mapping| + initialize_type_map(mapping) + end + end + + def new_column(name, default, cast_type, sql_type = nil, null = true) + Column.new(name, default, cast_type, sql_type, null) + end + + def lookup_cast_type(sql_type) # :nodoc: + type_map.lookup(sql_type) + end + + def column_name_for_operation(operation, node) # :nodoc: + node.to_sql + end + protected + def initialize_type_map(m) # :nodoc: + register_class_with_limit m, %r(boolean)i, Type::Boolean + register_class_with_limit m, %r(char)i, Type::String + register_class_with_limit m, %r(binary)i, Type::Binary + register_class_with_limit m, %r(text)i, Type::Text + register_class_with_limit m, %r(date)i, Type::Date + register_class_with_limit m, %r(time)i, Type::Time + register_class_with_limit m, %r(datetime)i, Type::DateTime + register_class_with_limit m, %r(float)i, Type::Float + register_class_with_limit m, %r(int)i, Type::Integer + + m.alias_type %r(blob)i, 'binary' + m.alias_type %r(clob)i, 'text' + m.alias_type %r(timestamp)i, 'datetime' + m.alias_type %r(numeric)i, 'decimal' + m.alias_type %r(number)i, 'decimal' + m.alias_type %r(double)i, 'float' + + m.register_type(%r(decimal)i) do |sql_type| + scale = extract_scale(sql_type) + precision = extract_precision(sql_type) + + if scale == 0 + # FIXME: Remove this class as well + Type::DecimalWithoutScale.new(precision: precision) + else + Type::Decimal.new(precision: precision, scale: scale) + end + end + end + + def reload_type_map # :nodoc: + type_map.clear + initialize_type_map(type_map) + end + + def register_class_with_limit(mapping, key, klass) # :nodoc: + mapping.register_type(key) do |*args| + limit = extract_limit(args.last) + klass.new(limit: limit) + end + end + + def extract_scale(sql_type) # :nodoc: + case sql_type + when /\((\d+)\)/ then 0 + when /\((\d+)(,(\d+))\)/ then $3.to_i + end + end + + def extract_precision(sql_type) # :nodoc: + $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/ + end + + def extract_limit(sql_type) # :nodoc: + $1.to_i if sql_type =~ /\((.*)\)/ + end + def translate_exception_class(e, sql) message = "#{e.class.name}: #{e.message}: #{sql}" @logger.error message if @logger @@ -375,7 +474,13 @@ module ActiveRecord end def without_prepared_statement?(binds) - !@prepared_statements || binds.empty? + !prepared_statements || binds.empty? + end + + def column_for(table_name, column_name) # :nodoc: + column_name = column_name.to_s + columns(table_name).detect { |c| c.name == column_name } || + raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}") end end end |