diff options
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters')
49 files changed, 1615 insertions, 1614 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 9b74c3a10f..d0c5bbe17d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -1,6 +1,6 @@ -require 'thread' -require 'concurrent/map' -require 'monitor' +require "thread" +require "concurrent/map" +require "monitor" module ActiveRecord # Raised when a connection could not be obtained within the connection @@ -150,61 +150,61 @@ module ActiveRecord private - def internal_poll(timeout) - no_wait_poll || (timeout && wait_poll(timeout)) - end + def internal_poll(timeout) + no_wait_poll || (timeout && wait_poll(timeout)) + end - def synchronize(&block) - @lock.synchronize(&block) - end + def synchronize(&block) + @lock.synchronize(&block) + end # Test if the queue currently contains any elements. - def any? - !@queue.empty? - end + def any? + !@queue.empty? + end # A thread can remove an element from the queue without # waiting if and only if the number of currently available # connections is strictly greater than the number of waiting # threads. - def can_remove_no_wait? - @queue.size > @num_waiting - end + def can_remove_no_wait? + @queue.size > @num_waiting + end # Removes and returns the head of the queue if possible, or nil. - def remove - @queue.shift - end + def remove + @queue.shift + end # Remove and return the head the queue if the number of # available elements is strictly greater than the number of # threads currently waiting. Otherwise, return nil. - def no_wait_poll - remove if can_remove_no_wait? - end + def no_wait_poll + remove if can_remove_no_wait? + end # Waits on the queue up to +timeout+ seconds, then removes and # returns the head of the queue. - def wait_poll(timeout) - @num_waiting += 1 + def wait_poll(timeout) + @num_waiting += 1 - t0 = Time.now - elapsed = 0 - loop do - @cond.wait(timeout - elapsed) + t0 = Time.now + elapsed = 0 + loop do + @cond.wait(timeout - elapsed) - return remove if any? + return remove if any? - elapsed = Time.now - t0 - if elapsed >= timeout - msg = 'could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use' % - [timeout, elapsed] - raise ConnectionTimeoutError, msg + elapsed = Time.now - t0 + if elapsed >= timeout + msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" % + [timeout, elapsed] + raise ConnectionTimeoutError, msg + end end + ensure + @num_waiting -= 1 end - ensure - @num_waiting -= 1 - end end # Adds the ability to turn a basic fair FIFO queue into one @@ -274,11 +274,11 @@ module ActiveRecord include BiasableQueue private - def internal_poll(timeout) - conn = super - conn.lease if conn - conn - end + def internal_poll(timeout) + conn = super + conn.lease if conn + conn + end end # Every +frequency+ seconds, the reaper will call +reap+ on +pool+. @@ -339,7 +339,7 @@ module ActiveRecord # that case +conn.owner+ attr should be consulted. # Access and modification of +@thread_cached_conns+ does not require # synchronization. - @thread_cached_conns = Concurrent::Map.new(:initial_capacity => @size) + @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size) @connections = [] @automatic_reconnect = true @@ -584,111 +584,111 @@ module ActiveRecord private #-- # this is unfortunately not concurrent - def bulk_make_new_connections(num_new_conns_needed) - num_new_conns_needed.times do - # try_to_checkout_new_connection will not exceed pool's @size limit - if new_conn = try_to_checkout_new_connection - # make the new_conn available to the starving threads stuck @available Queue - checkin(new_conn) + def bulk_make_new_connections(num_new_conns_needed) + num_new_conns_needed.times do + # try_to_checkout_new_connection will not exceed pool's @size limit + if new_conn = try_to_checkout_new_connection + # make the new_conn available to the starving threads stuck @available Queue + checkin(new_conn) + end end end - end #-- # From the discussion on GitHub: # https://github.com/rails/rails/pull/14938#commitcomment-6601951 # This hook-in method allows for easier monkey-patching fixes needed by # JRuby users that use Fibers. - def connection_cache_key(thread) - thread - end + def connection_cache_key(thread) + thread + end # Take control of all existing connections so a "group" action such as # reload/disconnect can be performed safely. It is no longer enough to # wrap it in +synchronize+ because some pool's actions are allowed # to be performed outside of the main +synchronize+ block. - def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true) - with_new_connections_blocked do - attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout) - yield + def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true) + with_new_connections_blocked do + attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout) + yield + end end - end - def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true) - collected_conns = synchronize do - # account for our own connections - @connections.select {|conn| conn.owner == Thread.current} - end + def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true) + collected_conns = synchronize do + # account for our own connections + @connections.select { |conn| conn.owner == Thread.current } + end - newly_checked_out = [] - timeout_time = Time.now + (@checkout_timeout * 2) + newly_checked_out = [] + timeout_time = Time.now + (@checkout_timeout * 2) - @available.with_a_bias_for(Thread.current) do - loop do - synchronize do - return if collected_conns.size == @connections.size && @now_connecting == 0 - remaining_timeout = timeout_time - Time.now - remaining_timeout = 0 if remaining_timeout < 0 - conn = checkout_for_exclusive_access(remaining_timeout) - collected_conns << conn - newly_checked_out << conn + @available.with_a_bias_for(Thread.current) do + loop do + synchronize do + return if collected_conns.size == @connections.size && @now_connecting == 0 + remaining_timeout = timeout_time - Time.now + remaining_timeout = 0 if remaining_timeout < 0 + conn = checkout_for_exclusive_access(remaining_timeout) + collected_conns << conn + newly_checked_out << conn + end end end - end - rescue ExclusiveConnectionTimeoutError - # <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any - # timeouts and are expected to just give up: we've obtained as many connections - # as possible, note that in a case like that we don't return any of the - # +newly_checked_out+ connections. - - if raise_on_acquisition_timeout + rescue ExclusiveConnectionTimeoutError + # <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any + # timeouts and are expected to just give up: we've obtained as many connections + # as possible, note that in a case like that we don't return any of the + # +newly_checked_out+ connections. + + if raise_on_acquisition_timeout + release_newly_checked_out = true + raise + end + rescue Exception # if something else went wrong + # this can't be a "naked" rescue, because we have should return conns + # even for non-StandardErrors release_newly_checked_out = true raise + ensure + if release_newly_checked_out && newly_checked_out + # releasing only those conns that were checked out in this method, conns + # checked outside this method (before it was called) are not for us to release + newly_checked_out.each { |conn| checkin(conn) } + end end - rescue Exception # if something else went wrong - # this can't be a "naked" rescue, because we have should return conns - # even for non-StandardErrors - release_newly_checked_out = true - raise - ensure - if release_newly_checked_out && newly_checked_out - # releasing only those conns that were checked out in this method, conns - # checked outside this method (before it was called) are not for us to release - newly_checked_out.each {|conn| checkin(conn)} - end - end #-- # Must be called in a synchronize block. - def checkout_for_exclusive_access(checkout_timeout) - checkout(checkout_timeout) - rescue ConnectionTimeoutError - # this block can't be easily moved into attempt_to_checkout_all_existing_connections's - # rescue block, because doing so would put it outside of synchronize section, without - # being in a critical section thread_report might become inaccurate - msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds" - - thread_report = [] - @connections.each do |conn| - unless conn.owner == Thread.current - thread_report << "#{conn} is owned by #{conn.owner}" + def checkout_for_exclusive_access(checkout_timeout) + checkout(checkout_timeout) + rescue ConnectionTimeoutError + # this block can't be easily moved into attempt_to_checkout_all_existing_connections's + # rescue block, because doing so would put it outside of synchronize section, without + # being in a critical section thread_report might become inaccurate + msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds" + + thread_report = [] + @connections.each do |conn| + unless conn.owner == Thread.current + thread_report << "#{conn} is owned by #{conn.owner}" + end end - end - msg << " (#{thread_report.join(', ')})" if thread_report.any? + msg << " (#{thread_report.join(', ')})" if thread_report.any? - raise ExclusiveConnectionTimeoutError, msg - end + raise ExclusiveConnectionTimeoutError, msg + end - def with_new_connections_blocked - previous_value = nil - synchronize do - previous_value, @new_cons_enabled = @new_cons_enabled, false + def with_new_connections_blocked + previous_value = nil + synchronize do + previous_value, @new_cons_enabled = @new_cons_enabled, false + end + yield + ensure + synchronize { @new_cons_enabled = previous_value } end - yield - ensure - synchronize { @new_cons_enabled = previous_value } - end # Acquire a connection by one of 1) immediately removing one # from the queue of available connections, 2) creating a new @@ -701,86 +701,86 @@ module ActiveRecord #-- # Implementation detail: the connection returned by +acquire_connection+ # will already be "+connection.lease+ -ed" to the current thread. - def acquire_connection(checkout_timeout) - # NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to - # +conn.lease+ the returned connection (and to do this in a +synchronized+ - # section). This is not the cleanest implementation, as ideally we would - # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+ - # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections - # of the said methods and avoid an additional +synchronize+ overhead. - if conn = @available.poll || try_to_checkout_new_connection - conn - else - reap - @available.poll(checkout_timeout) + def acquire_connection(checkout_timeout) + # NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to + # +conn.lease+ the returned connection (and to do this in a +synchronized+ + # section). This is not the cleanest implementation, as ideally we would + # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+ + # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections + # of the said methods and avoid an additional +synchronize+ overhead. + if conn = @available.poll || try_to_checkout_new_connection + conn + else + reap + @available.poll(checkout_timeout) + end end - end #-- # if owner_thread param is omitted, this must be called in synchronize block - def remove_connection_from_thread_cache(conn, owner_thread = conn.owner) - @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn) - end - alias_method :release, :remove_connection_from_thread_cache + def remove_connection_from_thread_cache(conn, owner_thread = conn.owner) + @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn) + end + alias_method :release, :remove_connection_from_thread_cache - def new_connection - Base.send(spec.adapter_method, spec.config).tap do |conn| - conn.schema_cache = schema_cache.dup if schema_cache + def new_connection + Base.send(spec.adapter_method, spec.config).tap do |conn| + conn.schema_cache = schema_cache.dup if schema_cache + end end - end # If the pool is not at a +@size+ limit, establish new connection. Connecting # to the DB is done outside main synchronized section. #-- # Implementation constraint: a newly established connection returned by this # method must be in the +.leased+ state. - def try_to_checkout_new_connection - # first in synchronized section check if establishing new conns is allowed - # and increment @now_connecting, to prevent overstepping this pool's @size - # constraint - do_checkout = synchronize do - if @new_cons_enabled && (@connections.size + @now_connecting) < @size - @now_connecting += 1 - end - end - if do_checkout - begin - # if successfully incremented @now_connecting establish new connection - # outside of synchronized section - conn = checkout_new_connection - ensure - synchronize do - if conn - adopt_connection(conn) - # returned conn needs to be already leased - conn.lease + def try_to_checkout_new_connection + # first in synchronized section check if establishing new conns is allowed + # and increment @now_connecting, to prevent overstepping this pool's @size + # constraint + do_checkout = synchronize do + if @new_cons_enabled && (@connections.size + @now_connecting) < @size + @now_connecting += 1 + end + end + if do_checkout + begin + # if successfully incremented @now_connecting establish new connection + # outside of synchronized section + conn = checkout_new_connection + ensure + synchronize do + if conn + adopt_connection(conn) + # returned conn needs to be already leased + conn.lease + end + @now_connecting -= 1 end - @now_connecting -= 1 end end end - end - def adopt_connection(conn) - conn.pool = self - @connections << conn - end + def adopt_connection(conn) + conn.pool = self + @connections << conn + end - def checkout_new_connection - raise ConnectionNotEstablished unless @automatic_reconnect - new_connection - end + def checkout_new_connection + raise ConnectionNotEstablished unless @automatic_reconnect + new_connection + end - def checkout_and_verify(c) - c._run_checkout_callbacks do - c.verify! + def checkout_and_verify(c) + c._run_checkout_callbacks do + c.verify! + end + c + rescue + remove c + c.disconnect! + raise end - c - rescue - remove c - c.disconnect! - raise - end end # ConnectionHandler is a collection of ConnectionPool objects. It is used @@ -833,8 +833,8 @@ module ActiveRecord class ConnectionHandler def initialize # These caches are keyed by spec.name (ConnectionSpecification#name). - @owner_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k| - h[k] = Concurrent::Map.new(:initial_capacity => 2) + @owner_to_pool = Concurrent::Map.new(initial_capacity: 2) do |h,k| + h[k] = Concurrent::Map.new(initial_capacity: 2) end end @@ -848,7 +848,21 @@ module ActiveRecord spec = resolver.spec(config) remove_connection(spec.name) - owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec) + + message_bus = ActiveSupport::Notifications.instrumenter + payload = { + connection_id: object_id + } + if spec + payload[:spec_name] = spec.name + payload[:config] = spec.config + end + + message_bus.instrument("!connection.active_record", payload) do + owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec) + end + + owner_to_pool[spec.name] end # Returns true if there are any active connections among the connection @@ -881,7 +895,7 @@ module ActiveRecord # for (not necessarily the current class). def retrieve_connection(spec_name) #:nodoc: pool = retrieve_connection_pool(spec_name) - raise ConnectionNotEstablished, "No connection pool with id '#{spec_name}' found." unless pool + raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found." unless pool conn = pool.connection raise ConnectionNotEstablished, "No connection for '#{spec_name}' in connection pool" unless conn conn @@ -928,14 +942,14 @@ module ActiveRecord private - def owner_to_pool - @owner_to_pool[Process.pid] - end + def owner_to_pool + @owner_to_pool[Process.pid] + end - def pool_from_any_process_for(spec_name) - owner_to_pool = @owner_to_pool.values.find { |v| v[spec_name] } - owner_to_pool && owner_to_pool[spec_name] - end + def pool_from_any_process_for(spec_name) + owner_to_pool = @owner_to_pool.values.find { |v| v[spec_name] } + owner_to_pool && owner_to_pool[spec_name] + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb index 6711049588..95c72f1e20 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb @@ -1,7 +1,6 @@ module ActiveRecord module ConnectionAdapters # :nodoc: module DatabaseLimits - # Returns the maximum length of a table alias. def table_alias_length 255 @@ -61,7 +60,6 @@ module ActiveRecord def joins_per_query 256 end - end end end 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 507a925d32..def1dadb6a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -10,7 +10,7 @@ module ActiveRecord def to_sql(arel, binds = []) if arel.respond_to?(:ast) collected = visitor.accept(arel.ast, collector) - collected.compile(binds.dup, self) + collected.compile(binds, self) else arel end @@ -18,11 +18,12 @@ module ActiveRecord # This is used in the StatementCache object. It returns an object that # can be used to query the database repeatedly. - def cacheable_query(arel) # :nodoc: + def cacheable_query(klass, arel) # :nodoc: + collected = visitor.accept(arel.ast, collector) if prepared_statements - ActiveRecord::StatementCache.query visitor, arel.ast + klass.query(collected.value) else - ActiveRecord::StatementCache.partial_query visitor, arel.ast, collector + klass.partial_query(collected.value) end end @@ -81,21 +82,22 @@ module ActiveRecord # Executes +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. - def exec_query(sql, name = 'SQL', binds = [], prepare: false) + def exec_query(sql, name = "SQL", binds = [], prepare: false) raise NotImplementedError end # Executes insert +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. - def exec_insert(sql, name, binds, pk = nil, sequence_name = nil) + def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) + sql, binds = sql_for_insert(sql, pk, nil, sequence_name, binds) exec_query(sql, name, binds) end # Executes delete +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. - def exec_delete(sql, name, binds) + def exec_delete(sql, name = nil, binds = []) exec_query(sql, name, binds) end @@ -107,7 +109,7 @@ module ActiveRecord # Executes update +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. - def exec_update(sql, name, binds) + def exec_update(sql, name = nil, binds = []) exec_query(sql, name, binds) end @@ -120,8 +122,7 @@ 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, pk, sequence_name = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds) - value = exec_insert(sql, name, binds, pk, sequence_name) + value = exec_insert(to_sql(arel, binds), name, binds, pk, sequence_name) id_value || last_inserted_id(value) end alias create insert @@ -315,7 +316,7 @@ module ActiveRecord end end key_list = fixture.keys.map { |name| quote_column_name(name) } - value_list = prepare_binds_for_database(binds).map do |value| + value_list = binds.map(&:value_for_database).map do |value| begin quote(value) rescue TypeError @@ -323,7 +324,7 @@ module ActiveRecord end end - execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert' + execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", "Fixture Insert" end def empty_insert_statement_value @@ -342,8 +343,8 @@ module ActiveRecord def sanitize_limit(limit) if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral) limit - elsif limit.to_s.include?(',') - Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',') + elsif limit.to_s.include?(",") + Arel.sql limit.to_s.split(",").map { |i| Integer(i) }.join(",") else Integer(limit) end @@ -378,7 +379,7 @@ module ActiveRecord end def sql_for_insert(sql, pk, id_value, sequence_name, binds) - [sql, binds, pk, sequence_name] + [sql, binds] end def last_inserted_id(result) 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 0bdfd4f900..10c60080d5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -73,23 +73,23 @@ module ActiveRecord private - def cache_sql(sql, binds) - result = - if @query_cache[sql].key?(binds) - ActiveSupport::Notifications.instrument("sql.active_record", - :sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id) - @query_cache[sql][binds] - else - @query_cache[sql][binds] = yield - end - result.dup - end + def cache_sql(sql, binds) + result = + if @query_cache[sql].key?(binds) + ActiveSupport::Notifications.instrument("sql.active_record", + sql: sql, binds: binds, name: "CACHE", connection_id: object_id) + @query_cache[sql][binds] + else + @query_cache[sql][binds] = yield + end + result.dup + end # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such # queries should not be cached. - def locked?(arel) - arel.respond_to?(:locked) && arel.locked - end + def locked?(arel) + arel.respond_to?(:locked) && arel.locked + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 860ef17dca..bbd52b8a91 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/big_decimal/conversions' +require "active_support/core_ext/big_decimal/conversions" module ActiveRecord module ConnectionAdapters # :nodoc: @@ -112,19 +112,19 @@ module ActiveRecord end def quoted_true - "'t'" + "'t'".freeze end def unquoted_true - 't' + "t".freeze end def quoted_false - "'f'" + "'f'".freeze end def unquoted_false - 'f' + "f".freeze end # Quote date/time values for use in SQL input. Includes microseconds @@ -147,52 +147,52 @@ module ActiveRecord end def quoted_time(value) # :nodoc: - quoted_date(value).sub(/\A2000-01-01 /, '') - end - - def prepare_binds_for_database(binds) # :nodoc: - binds.map(&:value_for_database) + quoted_date(value).sub(/\A2000-01-01 /, "") end private - def types_which_need_no_typecasting - [nil, Numeric, String] - end - - def _quote(value) - case value - when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data - "'#{quote_string(value.to_s)}'" - when true then quoted_true - when false then quoted_false - when nil then "NULL" - # BigDecimals need to be put in a non-normalized form and quoted. - when BigDecimal then value.to_s('F') - when Numeric, ActiveSupport::Duration then value.to_s - when Type::Time::Value then "'#{quoted_time(value)}'" - when Date, Time then "'#{quoted_date(value)}'" - when Symbol then "'#{quote_string(value.to_s)}'" - when Class then "'#{value}'" - else raise TypeError, "can't quote #{value.class.name}" + def type_casted_binds(binds) + binds.map { |attr| type_cast(attr.value_for_database) } end - end - def _type_cast(value) - case value - when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data - value.to_s - when true then unquoted_true - when false then unquoted_false - # BigDecimals need to be put in a non-normalized form and quoted. - when BigDecimal then value.to_s('F') - when Type::Time::Value then quoted_time(value) - when Date, Time then quoted_date(value) - when *types_which_need_no_typecasting - value - else raise TypeError + def types_which_need_no_typecasting + [nil, Numeric, String] + end + + def _quote(value) + case value + when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data + "'#{quote_string(value.to_s)}'" + when true then quoted_true + when false then quoted_false + when nil then "NULL" + # BigDecimals need to be put in a non-normalized form and quoted. + when BigDecimal then value.to_s("F") + when Numeric, ActiveSupport::Duration then value.to_s + when Type::Time::Value then "'#{quoted_time(value)}'" + when Date, Time then "'#{quoted_date(value)}'" + when Symbol then "'#{quote_string(value.to_s)}'" + when Class then "'#{value}'" + else raise TypeError, "can't quote #{value.class.name}" + end + end + + def _type_cast(value) + case value + when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data + value.to_s + when true then unquoted_true + when false then unquoted_false + # BigDecimals need to be put in a non-normalized form and quoted. + when BigDecimal then value.to_s("F") + when Type::Time::Value then quoted_time(value) + when Date, Time then quoted_date(value) + when *types_which_need_no_typecasting + value + else raise TypeError + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index 6add697eeb..322684672f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/strip' +require "active_support/core_ext/string/strip" module ActiveRecord module ConnectionAdapters @@ -23,9 +23,9 @@ module ActiveRecord def visit_AlterTable(o) sql = "ALTER TABLE #{quote_table_name(o.name)} " - sql << o.adds.map { |col| accept col }.join(' ') - sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(' ') - sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(' ') + sql << o.adds.map { |col| accept col }.join(" ") + sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(" ") + sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ") end def visit_ColumnDefinition(o) 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 8dbafc5a4b..ffde4f2c93 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -11,7 +11,6 @@ module ActiveRecord # +columns+ attribute of said TableDefinition object, in order to be used # for generating a number of table creation or table changing SQL statements. class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type, :comment) #:nodoc: - def primary_key? primary_key || type.to_sym == :primary_key end @@ -61,9 +60,9 @@ module ActiveRecord end private - def default_primary_key - "id" - end + def default_primary_key + "id" + end end class ReferenceDefinition # :nodoc: @@ -103,51 +102,51 @@ module ActiveRecord protected - attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options + attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options private - def as_options(value, default = {}) - if value.is_a?(Hash) - value - else - default + def as_options(value, default = {}) + if value.is_a?(Hash) + value + else + default + end end - end - def polymorphic_options - as_options(polymorphic, options) - end + def polymorphic_options + as_options(polymorphic, options) + end - def index_options - as_options(index) - end + def index_options + as_options(index) + end - def foreign_key_options - as_options(foreign_key).merge(column: column_name) - end + def foreign_key_options + as_options(foreign_key).merge(column: column_name) + end - def columns - result = [[column_name, type, options]] - if polymorphic - result.unshift(["#{name}_type", :string, polymorphic_options]) + def columns + result = [[column_name, type, options]] + if polymorphic + result.unshift(["#{name}_type", :string, polymorphic_options]) + end + result end - result - end - def column_name - "#{name}_id" - end + def column_name + "#{name}_id" + end - def column_names - columns.map(&:first) - end + def column_names + columns.map(&:first) + end - def foreign_table_name - foreign_key_options.fetch(:to_table) do - Base.pluralize_table_names ? name.to_s.pluralize : name + def foreign_table_name + foreign_key_options.fetch(:to_table) do + Base.pluralize_table_names ? name.to_s.pluralize : name + end end - end end module ColumnMethods @@ -212,7 +211,7 @@ module ActiveRecord def initialize(name, temporary = false, options = nil, as = nil, comment: nil) @columns_hash = {} - @indexes = {} + @indexes = [] @foreign_keys = [] @primary_keys = nil @temporary = temporary @@ -304,7 +303,7 @@ module ActiveRecord # end def column(name, type, options = {}) name = name.to_s - type = type.to_sym + type = type.to_sym if type options = options.dup if @columns_hash[name] && @columns_hash[name].primary_key? @@ -328,7 +327,7 @@ module ActiveRecord # # index(:account_id, name: 'index_projects_on_account_id') def index(column_name, options = {}) - indexes[column_name] = options + indexes << [column_name, options] end def foreign_key(table_name, options = {}) # :nodoc: @@ -342,9 +341,7 @@ module ActiveRecord # <tt>:updated_at</tt> to the table. See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps] # # t.timestamps null: false - def timestamps(*args) - options = args.extract_options! - + def timestamps(**options) options[:null] = false if options[:null].nil? column(:created_at, :datetime, options) @@ -383,13 +380,13 @@ module ActiveRecord end private - def create_column_definition(name, type) - ColumnDefinition.new name, type - end + def create_column_definition(name, type) + ColumnDefinition.new name, type + end - def aliased_types(name, fallback) - 'timestamp' == name ? :datetime : fallback - end + def aliased_types(name, fallback) + "timestamp" == name ? :datetime : fallback + end end class AlterTable # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index 677a4c6bd0..8bb7362c2e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -40,7 +40,7 @@ module ActiveRecord default = schema_default(column) if column.has_default? spec[:default] = default unless default.nil? - spec[:null] = 'false' unless column.null + spec[:null] = "false" unless column.null if collation = schema_collation(column) spec[:collation] = collation @@ -58,48 +58,48 @@ module ActiveRecord private - def default_primary_key?(column) - schema_type(column) == :integer - end + def default_primary_key?(column) + schema_type(column) == :integer + end - def schema_type(column) - if column.bigint? - :bigint - else - column.type + def schema_type(column) + if column.bigint? + :bigint + else + column.type + end end - end - def schema_limit(column) - limit = column.limit unless column.bigint? - limit.inspect if limit && limit != native_database_types[column.type][:limit] - end + def schema_limit(column) + limit = column.limit unless column.bigint? + limit.inspect if limit && limit != native_database_types[column.type][:limit] + end - def schema_precision(column) - column.precision.inspect if column.precision - end + def schema_precision(column) + column.precision.inspect if column.precision + end - def schema_scale(column) - column.scale.inspect if column.scale - end + def schema_scale(column) + column.scale.inspect if column.scale + end - def schema_default(column) - type = lookup_cast_type_from_column(column) - default = type.deserialize(column.default) - if default.nil? - schema_expression(column) - else - type.type_cast_for_schema(default) + def schema_default(column) + type = lookup_cast_type_from_column(column) + default = type.deserialize(column.default) + if default.nil? + schema_expression(column) + else + type.type_cast_for_schema(default) + end end - end - def schema_expression(column) - "-> { #{column.default_function.inspect} }" if column.default_function - end + def schema_expression(column) + "-> { #{column.default_function.inspect} }" if column.default_function + end - def schema_collation(column) - column.collation.inspect if column.collation - end + def schema_collation(column) + column.collation.inspect if column.collation + end end end end 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 396cb0b07a..45d782e45e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1,6 +1,6 @@ -require 'active_record/migration/join_table' -require 'active_support/core_ext/string/access' -require 'digest' +require "active_record/migration/join_table" +require "active_support/core_ext/string/access" +require "digest" module ActiveRecord module ConnectionAdapters # :nodoc: @@ -25,7 +25,7 @@ module ActiveRecord # Truncates a table alias according to the limits of the current adapter. def table_alias_for(table_name) - table_name[0...table_alias_length].tr('.', '_') + table_name[0...table_alias_length].tr(".", "_") end # Returns the relation names useable to back Active Record models. @@ -129,14 +129,9 @@ module ActiveRecord # Returns just a table's primary key def primary_key(table_name) - pks = primary_keys(table_name) - warn <<-WARNING.strip_heredoc if pks.count > 1 - WARNING: Rails does not support composite primary key. - - #{table_name} has composite primary key. Composite primary key is ignored. - WARNING - - pks.first if pks.one? + pk = primary_keys(table_name) + pk = pk.first unless pk.size > 1 + pk end # Creates a new table with the name +table_name+. +table_name+ may either @@ -283,7 +278,7 @@ module ActiveRecord result = execute schema_creation.accept td unless supports_indexes_in_create? - td.indexes.each_pair do |column_name, index_options| + td.indexes.each do |column_name, index_options| add_index(table_name, column_name, index_options) end end @@ -344,7 +339,7 @@ module ActiveRecord column_options.reverse_merge!(null: false) type = column_options.delete(:type) || :integer - t1_column, t2_column = [table_1, table_2].map{ |t| t.to_s.singularize.foreign_key } + t1_column, t2_column = [table_1, table_2].map { |t| t.to_s.singularize.foreign_key } create_table(join_table_name, options.merge!(id: false)) do |td| td.send type, t1_column, column_options @@ -538,6 +533,10 @@ module ActiveRecord # add_column(:measurements, :huge_integer, :decimal, precision: 30) # # ALTER TABLE "measurements" ADD "huge_integer" decimal(30) # + # # Defines a column that stores an array of a type. + # add_column(:users, :skills, :text, array: true) + # # ALTER TABLE "users" ADD "skills" text[] + # # # Defines a column with a database-specific type. # add_column(:shapes, :triangle, 'polygon') # # ALTER TABLE "shapes" ADD "triangle" polygon @@ -767,7 +766,7 @@ module ActiveRecord raise ArgumentError, "You must specify the index name" end else - index_name(table_name, :column => options) + index_name(table_name, column: options) end end @@ -790,7 +789,7 @@ module ActiveRecord # [<tt>:type</tt>] # The reference column type. Defaults to +:integer+. # [<tt>:index</tt>] - # Add an appropriate index. Defaults to false. + # Add an appropriate index. Defaults to true. # See #add_index for usage of this option. # [<tt>:foreign_key</tt>] # Add an appropriate foreign key constraint. Defaults to false. @@ -967,7 +966,7 @@ module ActiveRecord def foreign_key_for(from_table, options_or_to_table = {}) # :nodoc: return unless supports_foreign_keys? - foreign_keys(from_table).detect {|fk| fk.defined_for? options_or_to_table } + foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table } end def foreign_key_for!(from_table, options_or_to_table = {}) # :nodoc: @@ -990,7 +989,7 @@ module ActiveRecord end def dump_schema_information #:nodoc: - versions = ActiveRecord::SchemaMigration.order('version').pluck(:version) + versions = ActiveRecord::SchemaMigration.order("version").pluck(:version) insert_versions_sql(versions) end @@ -998,8 +997,8 @@ module ActiveRecord sm_table = ActiveRecord::Migrator.schema_migrations_table_name if supports_multi_insert? - sql = "INSERT INTO #{sm_table} (version) VALUES " - sql << versions.map {|v| "('#{v}')" }.join(', ') + sql = "INSERT INTO #{sm_table} (version) VALUES\n" + sql << versions.map { |v| "('#{v}')" }.join(",\n") sql << ";\n\n" sql else @@ -1029,18 +1028,18 @@ module ActiveRecord sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name) migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i) - paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" } + paths = migrations_paths.map { |p| "#{p}/[0-9]*_*.rb" } versions = Dir[*paths].map do |filename| - filename.split('/').last.split('_').first.to_i + filename.split("/").last.split("_").first.to_i end unless migrated.include?(version) execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" end - inserting = (versions - migrated).select {|v| v < version} + inserting = (versions - migrated).select { |v| v < version } if inserting.any? - if (duplicate = inserting.detect {|v| inserting.count(v) > 1}) + if (duplicate = inserting.detect { |v| inserting.count(v) > 1 }) raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict." end execute insert_versions_sql(inserting) @@ -1048,7 +1047,8 @@ module ActiveRecord end def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: - if native = native_database_types[type.to_sym] + type = type.to_sym if type + if native = native_database_types[type] column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup if type == :decimal # ignore limit, use precision and scale @@ -1166,31 +1166,34 @@ module ActiveRecord end protected - def add_index_sort_order(option_strings, column_names, options = {}) - if options.is_a?(Hash) && order = options[:order] + + def add_index_sort_order(quoted_columns, **options) + if order = options[:order] case order when Hash - column_names.each {|name| option_strings[name] += " #{order[name].upcase}" if order.has_key?(name)} + quoted_columns.each { |name, column| column << " #{order[name].upcase}" if order[name].present? } when String - column_names.each {|name| option_strings[name] += " #{order.upcase}"} + quoted_columns.each { |name, column| column << " #{order.upcase}" if order.present? } end end - return option_strings + quoted_columns end # Overridden by the MySQL adapter for supporting index lengths - def quoted_columns_for_index(column_names, options = {}) - return [column_names] if column_names.is_a?(String) - - option_strings = Hash[column_names.map {|name| [name, '']}] - - # add index sort order if supported + def add_options_for_index_columns(quoted_columns, **options) if supports_index_sort_order? - option_strings = add_index_sort_order(option_strings, column_names, options) + quoted_columns = add_index_sort_order(quoted_columns, options) end - column_names.map {|name| quote_column_name(name) + option_strings[name]} + quoted_columns + end + + def quoted_columns_for_index(column_names, **options) + return [column_names] if column_names.is_a?(String) + + quoted_columns = Hash[column_names.map { |name| [name, quote_column_name(name).dup] }] + add_options_for_index_columns(quoted_columns, options).values end def index_name_for_remove(table_name, options = {}) @@ -1210,10 +1213,10 @@ module ActiveRecord end if column_names.any? - checks << lambda { |i| i.columns.join('_and_') == column_names.join('_and_') } + checks << lambda { |i| i.columns.join("_and_") == column_names.join("_and_") } end - raise ArgumentError "No name or columns specified" if checks.none? + raise ArgumentError, "No name or columns specified" if checks.none? matching_indexes = indexes(table_name).select { |i| checks.all? { |check| check[i] } } @@ -1250,49 +1253,49 @@ module ActiveRecord end private - def create_table_definition(*args) - TableDefinition.new(*args) - end - - def create_alter_table(name) - AlterTable.new create_table_definition(name) - end + def create_table_definition(*args) + TableDefinition.new(*args) + end - def index_name_options(column_names) # :nodoc: - if column_names.is_a?(String) - column_names = column_names.scan(/\w+/).join('_') + def create_alter_table(name) + AlterTable.new create_table_definition(name) end - { column: column_names } - end + def index_name_options(column_names) # :nodoc: + if column_names.is_a?(String) + column_names = column_names.scan(/\w+/).join("_") + end - def foreign_key_name(table_name, options) # :nodoc: - identifier = "#{table_name}_#{options.fetch(:column)}_fk" - hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) - options.fetch(:name) do - "fk_rails_#{hashed_identifier}" + { column: column_names } end - end - def validate_index_length!(table_name, new_name, internal = false) # :nodoc: - max_index_length = internal ? index_name_length : allowed_index_name_length + def foreign_key_name(table_name, options) # :nodoc: + identifier = "#{table_name}_#{options.fetch(:column)}_fk" + hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) + options.fetch(:name) do + "fk_rails_#{hashed_identifier}" + end + end + + def validate_index_length!(table_name, new_name, internal = false) # :nodoc: + max_index_length = internal ? index_name_length : allowed_index_name_length - if new_name.length > max_index_length - raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" + if new_name.length > max_index_length + raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" + end end - end - def extract_new_default_value(default_or_changes) - if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to) - default_or_changes[:to] - else - default_or_changes + def extract_new_default_value(default_or_changes) + if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to) + default_or_changes[:to] + else + default_or_changes + end end - end - def can_remove_index_by_name?(options) - options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty? - end + def can_remove_index_by_name?(options) + options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty? + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index ca795cb1ad..6bb072dd73 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -41,7 +41,6 @@ module ActiveRecord end class Transaction #:nodoc: - attr_reader :connection, :state, :records, :savepoint_name attr_writer :joinable @@ -101,7 +100,6 @@ module ActiveRecord end class SavepointTransaction < Transaction - def initialize(connection, savepoint_name, options, *args) super(connection, options, *args) if options[:isolation] @@ -124,7 +122,6 @@ module ActiveRecord end class RealTransaction < Transaction - def initialize(connection, options, *args) super if options[:isolation] @@ -195,7 +192,7 @@ module ActiveRecord raise ensure unless error - if Thread.current.status == 'aborting' + if Thread.current.status == "aborting" rollback_transaction if transaction else begin @@ -226,7 +223,6 @@ module ActiveRecord return unless error.is_a?(ActiveRecord::PreparedStatementCacheExpired) @connection.clear_cache! end - end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 5747e4d1ee..4dde525ebc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -1,11 +1,11 @@ -require 'active_record/type' -require 'active_record/connection_adapters/determine_if_preparable_visitor' -require 'active_record/connection_adapters/schema_cache' -require 'active_record/connection_adapters/sql_type_metadata' -require 'active_record/connection_adapters/abstract/schema_dumper' -require 'active_record/connection_adapters/abstract/schema_creation' -require 'arel/collectors/bind' -require 'arel/collectors/sql_string' +require "active_record/type" +require "active_record/connection_adapters/determine_if_preparable_visitor" +require "active_record/connection_adapters/schema_cache" +require "active_record/connection_adapters/sql_type_metadata" +require "active_record/connection_adapters/abstract/schema_dumper" +require "active_record/connection_adapters/abstract/schema_creation" +require "arel/collectors/bind" +require "arel/collectors/sql_string" module ActiveRecord module ConnectionAdapters # :nodoc: @@ -14,7 +14,7 @@ module ActiveRecord autoload :Column autoload :ConnectionSpecification - autoload_at 'active_record/connection_adapters/abstract/schema_definitions' do + autoload_at "active_record/connection_adapters/abstract/schema_definitions" do autoload :IndexDefinition autoload :ColumnDefinition autoload :ChangeColumnDefinition @@ -25,11 +25,11 @@ module ActiveRecord autoload :ReferenceDefinition end - autoload_at 'active_record/connection_adapters/abstract/connection_pool' do + autoload_at "active_record/connection_adapters/abstract/connection_pool" do autoload :ConnectionHandler end - autoload_under 'abstract' do + autoload_under "abstract" do autoload :SchemaStatements autoload :DatabaseStatements autoload :DatabaseLimits @@ -39,7 +39,7 @@ module ActiveRecord autoload :Savepoints end - autoload_at 'active_record/connection_adapters/abstract/transaction' do + autoload_at "active_record/connection_adapters/abstract/transaction" do autoload :TransactionManager autoload :NullTransaction autoload :RealTransaction @@ -61,7 +61,7 @@ module ActiveRecord # Most of the methods in the adapter are useful during migrations. Most # notably, the instance methods provided by SchemaStatements are very useful. class AbstractAdapter - ADAPTER_NAME = 'Abstract'.freeze + ADAPTER_NAME = "Abstract".freeze include Quoting, DatabaseStatements, SchemaStatements include DatabaseLimits include QueryCache @@ -120,17 +120,17 @@ module ActiveRecord include Comparable def initialize(version_string) - @version = version_string.split('.').map(&:to_i) + @version = version_string.split(".").map(&:to_i) end def <=>(version_string) - @version <=> version_string.split('.').map(&:to_i) + @version <=> version_string.split(".").map(&:to_i) end end class BindCollector < Arel::Collectors::Bind def compile(bvs, conn) - casted_binds = conn.prepare_binds_for_database(bvs) + casted_binds = bvs.map(&:value_for_database) super(casted_binds.map { |value| conn.quote(value) }) end end @@ -164,9 +164,9 @@ module ActiveRecord # this method must only be called while holding connection pool's mutex def lease if in_use? - msg = 'Cannot lease connection, ' + msg = "Cannot lease connection, " if @owner == Thread.current - msg << 'it is already leased by the current thread.' + msg << "it is already leased by the current thread." else msg << "it is already in use by a different thread: #{@owner}. " << "Current thread: #{Thread.current}." @@ -193,7 +193,7 @@ module ActiveRecord @owner = nil else - raise ActiveRecordError, 'Cannot expire connection, it is not currently leased.' + raise ActiveRecordError, "Cannot expire connection, it is not currently leased." end end @@ -206,7 +206,7 @@ module ActiveRecord @owner = Thread.current end else - raise ActiveRecordError, 'Cannot steal connection, it is not currently leased.' + raise ActiveRecordError, "Cannot steal connection, it is not currently leased." end end @@ -433,20 +433,16 @@ module ActiveRecord @connection end - def case_sensitive_comparison(table, attribute, column, value) - if value.nil? - table[attribute].eq(value) - else - table[attribute].eq(Arel::Nodes::BindParam.new) - end + def case_sensitive_comparison(attribute, column, value) # :nodoc: + attribute.eq(value) end - def case_insensitive_comparison(table, attribute, column, value) + def case_insensitive_comparison(attribute, column, value) # :nodoc: if can_perform_case_insensitive_comparison_for?(column) - table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new)) - else - table[attribute].eq(Arel::Nodes::BindParam.new) + value = attribute.relation.lower(value) + attribute = attribute.lower end + attribute.eq(value) end def can_perform_case_insensitive_comparison_for?(column) @@ -497,114 +493,115 @@ module ActiveRecord 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_precision m, %r(date)i, Type::Date - register_class_with_precision m, %r(time)i, Type::Time - register_class_with_precision 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) + 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_precision m, %r(date)i, Type::Date + register_class_with_precision m, %r(time)i, Type::Time + register_class_with_precision 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 - end - def reload_type_map # :nodoc: - type_map.clear - initialize_type_map(type_map) - 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) + 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 - end - def register_class_with_precision(mapping, key, klass) # :nodoc: - mapping.register_type(key) do |*args| - precision = extract_precision(args.last) - klass.new(precision: precision) + def register_class_with_precision(mapping, key, klass) # :nodoc: + mapping.register_type(key) do |*args| + precision = extract_precision(args.last) + klass.new(precision: precision) + end end - end - def extract_scale(sql_type) # :nodoc: - case sql_type + def extract_scale(sql_type) # :nodoc: + case sql_type when /\((\d+)\)/ then 0 when /\((\d+)(,(\d+))\)/ then $3.to_i + end end - end - - def extract_precision(sql_type) # :nodoc: - $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/ - end - def extract_limit(sql_type) # :nodoc: - case sql_type - when /^bigint/i - 8 - when /\((.*)\)/ - $1.to_i + def extract_precision(sql_type) # :nodoc: + $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/ end - end - def translate_exception_class(e, sql) - begin - message = "#{e.class.name}: #{e.message}: #{sql}" - rescue Encoding::CompatibilityError - message = "#{e.class.name}: #{e.message.force_encoding sql.encoding}: #{sql}" + def extract_limit(sql_type) # :nodoc: + case sql_type + when /^bigint/i + 8 + when /\((.*)\)/ + $1.to_i + end end - exception = translate_exception(e, message) - exception.set_backtrace e.backtrace - exception - end + def translate_exception_class(e, sql) + begin + message = "#{e.class.name}: #{e.message}: #{sql}" + rescue Encoding::CompatibilityError + message = "#{e.class.name}: #{e.message.force_encoding sql.encoding}: #{sql}" + end - def log(sql, name = "SQL", binds = [], statement_name = nil) - @instrumenter.instrument( - "sql.active_record", - :sql => sql, - :name => name, - :connection_id => object_id, - :statement_name => statement_name, - :binds => binds) { yield } - rescue => e - raise translate_exception_class(e, sql) - end + exception = translate_exception(e, message) + exception.set_backtrace e.backtrace + exception + end - def translate_exception(exception, message) - # override in derived class - ActiveRecord::StatementInvalid.new(message) - end + def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) + @instrumenter.instrument( + "sql.active_record", + sql: sql, + name: name, + binds: binds, + type_casted_binds: type_casted_binds, + statement_name: statement_name, + connection_id: object_id) { yield } + rescue => e + raise translate_exception_class(e, sql) + end - def without_prepared_statement?(binds) - !prepared_statements || binds.empty? - end + def translate_exception(exception, message) + # override in derived class + ActiveRecord::StatementInvalid.new(message) + 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 + def without_prepared_statement?(binds) + !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 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 3e77b92141..3a28879c15 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -1,14 +1,15 @@ -require 'active_record/connection_adapters/abstract_adapter' -require 'active_record/connection_adapters/statement_pool' -require 'active_record/connection_adapters/mysql/column' -require 'active_record/connection_adapters/mysql/explain_pretty_printer' -require 'active_record/connection_adapters/mysql/quoting' -require 'active_record/connection_adapters/mysql/schema_creation' -require 'active_record/connection_adapters/mysql/schema_definitions' -require 'active_record/connection_adapters/mysql/schema_dumper' -require 'active_record/connection_adapters/mysql/type_metadata' - -require 'active_support/core_ext/string/strip' +require "active_record/connection_adapters/abstract_adapter" +require "active_record/connection_adapters/statement_pool" +require "active_record/connection_adapters/mysql/column" +require "active_record/connection_adapters/mysql/explain_pretty_printer" +require "active_record/connection_adapters/mysql/quoting" +require "active_record/connection_adapters/mysql/schema_creation" +require "active_record/connection_adapters/mysql/schema_definitions" +require "active_record/connection_adapters/mysql/schema_dumper" +require "active_record/connection_adapters/mysql/type_metadata" + +require "active_support/core_ext/string/strip" +require "active_support/core_ext/regexp" module ActiveRecord module ConnectionAdapters @@ -67,16 +68,16 @@ module ActiveRecord @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) - if version < '5.0.0' + if version < "5.0.0" raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.0." end end - CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32'] + CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"] def internal_string_options_for_primary_key # :nodoc: super.tap { |options| - options[:collation] = collation.sub(/\A[^_]+/, 'utf8') if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) + options[:collation] = collation.sub(/\A[^_]+/, "utf8") if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) } end @@ -85,7 +86,7 @@ module ActiveRecord end def mariadb? # :nodoc: - full_version =~ /mariadb/i + /mariadb/i.match?(full_version) end # Returns true, since this connection adapter supports migrations. @@ -135,9 +136,9 @@ module ActiveRecord def supports_datetime_with_precision? if mariadb? - version >= '5.3.0' + version >= "5.3.0" else - version >= '5.6.4' + version >= "5.6.4" end end @@ -158,7 +159,7 @@ module ActiveRecord end def index_algorithms - { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' } + { default: "ALGORITHM = DEFAULT", copy: "ALGORITHM = COPY", inplace: "ALGORITHM = INPLACE" } end # HELPER METHODS =========================================== @@ -207,7 +208,7 @@ module ActiveRecord def explain(arel, binds = []) sql = "EXPLAIN #{to_sql(arel, binds)}" start = Time.now - result = exec_query(sql, 'EXPLAIN', binds) + result = exec_query(sql, "EXPLAIN", binds) elapsed = Time.now - start MySQL::ExplainPrettyPrinter.new.pp(result, elapsed) @@ -293,17 +294,17 @@ module ActiveRecord end def current_database - select_value 'SELECT DATABASE() as db' + select_value "SELECT DATABASE() as db" end # Returns the database character set. def charset - show_variable 'character_set_database' + show_variable "character_set_database" end # Returns the database collation strategy. def collation - show_variable 'collation_database' + show_variable "collation_database" end def tables(name = nil) # :nodoc: @@ -326,7 +327,7 @@ module ActiveRecord sql = "SELECT table_name FROM information_schema.tables " sql << "WHERE table_schema = #{quote(@config[:database])}" - select_values(sql, 'SCHEMA') + select_values(sql, "SCHEMA") end def truncate(table_name, name = nil) @@ -352,11 +353,11 @@ module ActiveRecord sql = "SELECT table_name FROM information_schema.tables " sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}" - select_values(sql, 'SCHEMA').any? + select_values(sql, "SCHEMA").any? end def views # :nodoc: - select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA') + select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", "SCHEMA") end def view_exists?(view_name) # :nodoc: @@ -367,17 +368,17 @@ module ActiveRecord sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'" sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}" - select_values(sql, 'SCHEMA').any? + select_values(sql, "SCHEMA").any? end # Returns an array of indexes for the given table. def indexes(table_name, name = nil) #:nodoc: indexes = [] current_index = nil - execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result| + execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| each_hash(result) do |row| if current_index != row[:Key_name] - next if row[:Key_name] == 'PRIMARY' # skip the primary key + next if row[:Key_name] == "PRIMARY" # skip the primary key current_index = row[:Key_name] mysql_index_type = row[:Index_type].downcase.to_sym @@ -409,15 +410,18 @@ module ActiveRecord end def table_comment(table_name) # :nodoc: - select_value(<<-SQL.strip_heredoc, 'SCHEMA') + schema, name = extract_schema_qualified_name(table_name) + + select_value(<<-SQL.strip_heredoc, "SCHEMA") SELECT table_comment FROM information_schema.tables - WHERE table_name=#{quote(table_name)} + WHERE table_schema = #{quote(schema)} + AND table_name = #{quote(name)} SQL end def create_table(table_name, **options) #:nodoc: - super(table_name, options: 'ENGINE=InnoDB', **options) + super(table_name, options: "ENGINE=InnoDB", **options) end def bulk_change_table(table_name, operations) #:nodoc: @@ -476,7 +480,7 @@ module ActiveRecord def change_column_default(table_name, column_name, default_or_changes) #:nodoc: default = extract_new_default_value(default_or_changes) column = column_for(table_name, column_name) - change_column table_name, column_name, column.sql_type, :default => default + change_column table_name, column_name, column.sql_type, default: default end def change_column_null(table_name, column_name, null, default = nil) #:nodoc: @@ -486,7 +490,7 @@ module ActiveRecord execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") end - change_column table_name, column_name, column.sql_type, :null => null + change_column table_name, column_name, column.sql_type, null: null end def change_column(table_name, column_name, type, options = {}) #:nodoc: @@ -514,7 +518,7 @@ module ActiveRecord schema, name = extract_schema_qualified_name(table_name) - fk_info = select_all(<<-SQL.strip_heredoc, 'SCHEMA') + fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA") SELECT fk.referenced_table_name AS 'to_table', fk.referenced_column_name AS 'primary_key', fk.column_name AS 'column', @@ -531,59 +535,65 @@ module ActiveRecord fk_info.map do |row| options = { - column: row['column'], - name: row['name'], - primary_key: row['primary_key'] + column: row["column"], + name: row["name"], + primary_key: row["primary_key"] } - options[:on_update] = extract_foreign_key_action(row['on_update']) - options[:on_delete] = extract_foreign_key_action(row['on_delete']) + options[:on_update] = extract_foreign_key_action(row["on_update"]) + options[:on_delete] = extract_foreign_key_action(row["on_delete"]) - ForeignKeyDefinition.new(table_name, row['to_table'], options) + ForeignKeyDefinition.new(table_name, row["to_table"], options) end end - def table_options(table_name) + def table_options(table_name) # :nodoc: + table_options = {} + create_table_info = create_table_info(table_name) # strip create_definitions and partition_options - raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip + raw_table_options = create_table_info.sub(/\A.*\n\) /m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip # strip AUTO_INCREMENT raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1') + table_options[:options] = raw_table_options + # strip COMMENT - raw_table_options.sub!(/ COMMENT='.+'/, '') + if raw_table_options.sub!(/ COMMENT='.+'/, "") + table_options[:comment] = table_comment(table_name) + end - raw_table_options + table_options end # Maps logical Rails types to MySQL-specific data types. def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil) sql = case type.to_s - when 'integer' - integer_to_sql(limit) - when 'text' - text_to_sql(limit) - when 'blob' - binary_to_sql(limit) - when 'binary' - if (0..0xfff) === limit - "varbinary(#{limit})" - else - binary_to_sql(limit) - end + when "integer" + integer_to_sql(limit) + when "text" + text_to_sql(limit) + when "blob" + binary_to_sql(limit) + when "binary" + if (0..0xfff) === limit + "varbinary(#{limit})" + else + binary_to_sql(limit) + end else - super(type, limit, precision, scale) + super(type, limit, precision, scale) end - sql << ' unsigned' if unsigned && type != :primary_key + sql << " unsigned" if unsigned && type != :primary_key sql end # SHOW VARIABLES LIKE 'name' def show_variable(name) - select_value("SELECT @@#{name}", 'SCHEMA') + select_value("SELECT @@#{name}", "SCHEMA") rescue ActiveRecord::StatementInvalid nil end @@ -593,7 +603,7 @@ module ActiveRecord schema, name = extract_schema_qualified_name(table_name) - select_values(<<-SQL.strip_heredoc, 'SCHEMA') + select_values(<<-SQL.strip_heredoc, "SCHEMA") SELECT column_name FROM information_schema.key_column_usage WHERE constraint_name = 'PRIMARY' @@ -603,12 +613,11 @@ module ActiveRecord SQL end - def case_sensitive_comparison(table, attribute, column, value) - if !value.nil? && column.collation && !column.case_sensitive? - table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new)) - else - super + def case_sensitive_comparison(attribute, column, value) # :nodoc: + if column.collation && !column.case_sensitive? + value = Arel::Nodes::Bin.new(value) end + attribute.eq(value) end def can_perform_case_insensitive_comparison_for?(column) @@ -625,10 +634,10 @@ module ActiveRecord # Convert Arel node to string s = s.to_sql unless s.is_a?(String) # Remove any ASC/DESC modifiers - s.gsub(/\s+(?:ASC|DESC)\b/i, '') + s.gsub(/\s+(?:ASC|DESC)\b/i, "") }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" } - [super, *order_columns].join(', ') + [super, *order_columns].join(", ") end def strict_mode? @@ -641,340 +650,333 @@ module ActiveRecord protected - def initialize_type_map(m) # :nodoc: - super - - register_class_with_limit m, %r(char)i, MysqlString - - m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1) - m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1) - m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1) - m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1) - m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1) - m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1) - m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1) - m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) - m.register_type %r(^float)i, Type::Float.new(limit: 24) - m.register_type %r(^double)i, Type::Float.new(limit: 53) - m.register_type %r(^json)i, MysqlJson.new - - register_integer_type m, %r(^bigint)i, limit: 8 - register_integer_type m, %r(^int)i, limit: 4 - register_integer_type m, %r(^mediumint)i, limit: 3 - register_integer_type m, %r(^smallint)i, limit: 2 - register_integer_type m, %r(^tinyint)i, limit: 1 + def initialize_type_map(m) # :nodoc: + super - m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans - m.alias_type %r(year)i, 'integer' - m.alias_type %r(bit)i, 'binary' + register_class_with_limit m, %r(char)i, MysqlString + + m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1) + m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1) + m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1) + m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1) + m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1) + m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1) + m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1) + m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) + m.register_type %r(^float)i, Type::Float.new(limit: 24) + m.register_type %r(^double)i, Type::Float.new(limit: 53) + m.register_type %r(^json)i, MysqlJson.new + + register_integer_type m, %r(^bigint)i, limit: 8 + register_integer_type m, %r(^int)i, limit: 4 + register_integer_type m, %r(^mediumint)i, limit: 3 + register_integer_type m, %r(^smallint)i, limit: 2 + register_integer_type m, %r(^tinyint)i, limit: 1 + + m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans + m.alias_type %r(year)i, "integer" + m.alias_type %r(bit)i, "binary" + + m.register_type(%r(enum)i) do |sql_type| + limit = sql_type[/^enum\((.+)\)/i, 1] + .split(",").map { |enum| enum.strip.length - 2 }.max + MysqlString.new(limit: limit) + end - m.register_type(%r(enum)i) do |sql_type| - limit = sql_type[/^enum\((.+)\)/i, 1] - .split(',').map{|enum| enum.strip.length - 2}.max - MysqlString.new(limit: limit) + m.register_type(%r(^set)i) do |sql_type| + limit = sql_type[/^set\((.+)\)/i, 1] + .split(",").map { |set| set.strip.length - 1 }.sum - 1 + MysqlString.new(limit: limit) + end end - m.register_type(%r(^set)i) do |sql_type| - limit = sql_type[/^set\((.+)\)/i, 1] - .split(',').map{|set| set.strip.length - 1}.sum - 1 - MysqlString.new(limit: limit) + def register_integer_type(mapping, key, options) # :nodoc: + mapping.register_type(key) do |sql_type| + if /\bunsigned\z/ === sql_type + Type::UnsignedInteger.new(options) + else + Type::Integer.new(options) + end + end end - end - def register_integer_type(mapping, key, options) # :nodoc: - mapping.register_type(key) do |sql_type| - if /\bunsigned\z/ === sql_type - Type::UnsignedInteger.new(options) + def extract_precision(sql_type) + if /time/ === sql_type + super || 0 else - Type::Integer.new(options) + super end end - end - def extract_precision(sql_type) - if /time/ === sql_type - super || 0 - else - super + def fetch_type_metadata(sql_type, extra = "") + MySQL::TypeMetadata.new(super(sql_type), extra: extra) end - end - - def fetch_type_metadata(sql_type, extra = "") - MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?) - end - def add_index_length(option_strings, column_names, options = {}) - if options.is_a?(Hash) && length = options[:length] - case length - when Hash - column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?} - when Integer - column_names.each {|name| option_strings[name] += "(#{length})"} + def add_index_length(quoted_columns, **options) + if length = options[:length] + case length + when Hash + quoted_columns.each { |name, column| column << "(#{length[name]})" if length[name].present? } + when Integer + quoted_columns.each { |name, column| column << "(#{length})" } + end end + + quoted_columns end - return option_strings - end + def add_options_for_index_columns(quoted_columns, **options) + quoted_columns = add_index_length(quoted_columns, options) + super + end - def quoted_columns_for_index(column_names, options = {}) - option_strings = Hash[column_names.map {|name| [name, '']}] + # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html + ER_DUP_ENTRY = 1062 + ER_NO_REFERENCED_ROW_2 = 1452 + ER_DATA_TOO_LONG = 1406 + ER_LOCK_DEADLOCK = 1213 + + def translate_exception(exception, message) + case error_number(exception) + when ER_DUP_ENTRY + RecordNotUnique.new(message) + when ER_NO_REFERENCED_ROW_2 + InvalidForeignKey.new(message) + when ER_DATA_TOO_LONG + ValueTooLong.new(message) + when ER_LOCK_DEADLOCK + Deadlocked.new(message) + else + super + end + end - # add index length - option_strings = add_index_length(option_strings, column_names, options) + def add_column_sql(table_name, column_name, type, options = {}) + td = create_table_definition(table_name) + cd = td.new_column_definition(column_name, type, options) + schema_creation.accept(AddColumnDefinition.new(cd)) + end - # add index sort order - option_strings = add_index_sort_order(option_strings, column_names, options) + def change_column_sql(table_name, column_name, type, options = {}) + column = column_for(table_name, column_name) - column_names.map {|name| quote_column_name(name) + option_strings[name]} - end + unless options_include_default?(options) + options[:default] = column.default + end - # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html - ER_DUP_ENTRY = 1062 - ER_NO_REFERENCED_ROW_2 = 1452 - ER_DATA_TOO_LONG = 1406 - ER_LOCK_DEADLOCK = 1213 + unless options.has_key?(:null) + options[:null] = column.null + end - def translate_exception(exception, message) - case error_number(exception) - when ER_DUP_ENTRY - RecordNotUnique.new(message) - when ER_NO_REFERENCED_ROW_2 - InvalidForeignKey.new(message) - when ER_DATA_TOO_LONG - ValueTooLong.new(message) - when ER_LOCK_DEADLOCK - TransactionSerializationError.new(message) - else - super + td = create_table_definition(table_name) + cd = td.new_column_definition(column.name, type, options) + schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) end - end - def add_column_sql(table_name, column_name, type, options = {}) - td = create_table_definition(table_name) - cd = td.new_column_definition(column_name, type, options) - schema_creation.accept(AddColumnDefinition.new(cd)) - end - - def change_column_sql(table_name, column_name, type, options = {}) - column = column_for(table_name, column_name) + def rename_column_sql(table_name, column_name, new_column_name) + column = column_for(table_name, column_name) + options = { + default: column.default, + null: column.null, + auto_increment: column.auto_increment? + } - unless options_include_default?(options) - options[:default] = column.default + current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", "SCHEMA")["Type"] + td = create_table_definition(table_name) + cd = td.new_column_definition(new_column_name, current_type, options) + schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) end - unless options.has_key?(:null) - options[:null] = column.null + def remove_column_sql(table_name, column_name, type = nil, options = {}) + "DROP #{quote_column_name(column_name)}" end - td = create_table_definition(table_name) - cd = td.new_column_definition(column.name, type, options) - schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) - end - - def rename_column_sql(table_name, column_name, new_column_name) - column = column_for(table_name, column_name) - options = { - default: column.default, - null: column.null, - auto_increment: column.auto_increment? - } - - current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"] - td = create_table_definition(table_name) - cd = td.new_column_definition(new_column_name, current_type, options) - schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) - end - - def remove_column_sql(table_name, column_name, type = nil, options = {}) - "DROP #{quote_column_name(column_name)}" - end - - def remove_columns_sql(table_name, *column_names) - column_names.map {|column_name| remove_column_sql(table_name, column_name) } - end + def remove_columns_sql(table_name, *column_names) + column_names.map { |column_name| remove_column_sql(table_name, column_name) } + end - def add_index_sql(table_name, column_name, options = {}) - index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options) - index_algorithm[0, 0] = ", " if index_algorithm.present? - "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}" - end + def add_index_sql(table_name, column_name, options = {}) + index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options) + index_algorithm[0, 0] = ", " if index_algorithm.present? + "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}" + end - def remove_index_sql(table_name, options = {}) - index_name = index_name_for_remove(table_name, options) - "DROP INDEX #{index_name}" - end + def remove_index_sql(table_name, options = {}) + index_name = index_name_for_remove(table_name, options) + "DROP INDEX #{index_name}" + end - def add_timestamps_sql(table_name, options = {}) - [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)] - end + def add_timestamps_sql(table_name, options = {}) + [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)] + end - def remove_timestamps_sql(table_name, options = {}) - [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)] - end + def remove_timestamps_sql(table_name, options = {}) + [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)] + end private # MySQL is too stupid to create a temporary table for use subquery, so we have # to give it some prompting in the form of a subsubquery. Ugh! - def subquery_for(key, select) - subsubselect = select.clone - subsubselect.projections = [key] + def subquery_for(key, select) + subsubselect = select.clone + subsubselect.projections = [key] - # Materialize subquery by adding distinct - # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' - subsubselect.distinct unless select.limit || select.offset || select.orders.any? + # Materialize subquery by adding distinct + # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' + subsubselect.distinct unless select.limit || select.offset || select.orders.any? - subselect = Arel::SelectManager.new(select.engine) - subselect.project Arel.sql(key.name) - subselect.from subsubselect.as('__active_record_temp') - end + subselect = Arel::SelectManager.new(select.engine) + subselect.project Arel.sql(key.name) + subselect.from subsubselect.as("__active_record_temp") + end - def supports_rename_index? - mariadb? ? false : version >= '5.7.6' - end + def supports_rename_index? + mariadb? ? false : version >= "5.7.6" + end - def configure_connection - variables = @config.fetch(:variables, {}).stringify_keys + def configure_connection + variables = @config.fetch(:variables, {}).stringify_keys - # By default, MySQL 'where id is null' selects the last inserted id; Turn this off. - variables['sql_auto_is_null'] = 0 + # By default, MySQL 'where id is null' selects the last inserted id; Turn this off. + variables["sql_auto_is_null"] = 0 - # Increase timeout so the server doesn't disconnect us. - wait_timeout = @config[:wait_timeout] - wait_timeout = 2147483 unless wait_timeout.is_a?(Integer) - variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout) + # Increase timeout so the server doesn't disconnect us. + wait_timeout = @config[:wait_timeout] + wait_timeout = 2147483 unless wait_timeout.is_a?(Integer) + variables["wait_timeout"] = self.class.type_cast_config_to_integer(wait_timeout) - defaults = [':default', :default].to_set + defaults = [":default", :default].to_set - # Make MySQL reject illegal values rather than truncating or blanking them, see - # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables - # If the user has provided another value for sql_mode, don't replace it. - if sql_mode = variables.delete('sql_mode') - sql_mode = quote(sql_mode) - elsif !defaults.include?(strict_mode?) - if strict_mode? - sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')" - else - sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')" - sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')" - sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')" + # Make MySQL reject illegal values rather than truncating or blanking them, see + # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables + # If the user has provided another value for sql_mode, don't replace it. + if sql_mode = variables.delete("sql_mode") + sql_mode = quote(sql_mode) + elsif !defaults.include?(strict_mode?) + if strict_mode? + sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')" + else + sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')" + sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')" + sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')" + end + sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')" end - sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')" - end - sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode - - # NAMES does not have an equals sign, see - # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430 - # (trailing comma because variable_assignments will always have content) - if @config[:encoding] - encoding = "NAMES #{@config[:encoding]}" - encoding << " COLLATE #{@config[:collation]}" if @config[:collation] - encoding << ", " - end - - # Gather up all of the SET variables... - variable_assignments = variables.map do |k, v| - if defaults.include?(v) - "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default - elsif !v.nil? - "@@SESSION.#{k} = #{quote(v)}" + sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode + + # NAMES does not have an equals sign, see + # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430 + # (trailing comma because variable_assignments will always have content) + if @config[:encoding] + encoding = "NAMES #{@config[:encoding]}" + encoding << " COLLATE #{@config[:collation]}" if @config[:collation] + encoding << ", " end - # or else nil; compact to clear nils out - end.compact.join(', ') - # ...and send them all in one query - @connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}" - end + # Gather up all of the SET variables... + variable_assignments = variables.map do |k, v| + if defaults.include?(v) + "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default + elsif !v.nil? + "@@SESSION.#{k} = #{quote(v)}" + end + # or else nil; compact to clear nils out + end.compact.join(", ") - def column_definitions(table_name) # :nodoc: - execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result| - each_hash(result) + # ...and send them all in one query + @connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}" end - end - def extract_foreign_key_action(specifier) # :nodoc: - case specifier - when 'CASCADE'; :cascade - when 'SET NULL'; :nullify + def column_definitions(table_name) # :nodoc: + execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| + each_hash(result) + end end - end - - def create_table_info(table_name) # :nodoc: - select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] - end - def create_table_definition(*args) # :nodoc: - MySQL::TableDefinition.new(*args) - end + def extract_foreign_key_action(specifier) # :nodoc: + case specifier + when "CASCADE"; :cascade + when "SET NULL"; :nullify + end + end - def extract_schema_qualified_name(string) # :nodoc: - schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/) - schema, name = @config[:database], schema unless name - [schema, name] - end + def create_table_info(table_name) # :nodoc: + select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] + end - def integer_to_sql(limit) # :nodoc: - case limit - when 1; 'tinyint' - when 2; 'smallint' - when 3; 'mediumint' - when nil, 4; 'int' - when 5..8; 'bigint' - else raise(ActiveRecordError, "No integer type has byte size #{limit}") + def create_table_definition(*args) # :nodoc: + MySQL::TableDefinition.new(*args) end - end - def text_to_sql(limit) # :nodoc: - 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 byte length #{limit}") + def extract_schema_qualified_name(string) # :nodoc: + schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/) + schema, name = @config[:database], schema unless name + [schema, name] end - end - def binary_to_sql(limit) # :nodoc: - case limit - when 0..0xff; 'tinyblob' - when nil, 0x100..0xffff; 'blob' - when 0x10000..0xffffff; 'mediumblob' - when 0x1000000..0xffffffff; 'longblob' - else raise(ActiveRecordError, "No binary type has byte length #{limit}") + def integer_to_sql(limit) # :nodoc: + case limit + when 1; "tinyint" + when 2; "smallint" + when 3; "mediumint" + when nil, 4; "int" + when 5..8; "bigint" + else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a decimal with scale 0 instead.") + end end - end - class MysqlJson < Type::Internal::AbstractJson # :nodoc: - def changed_in_place?(raw_old_value, new_value) - # Normalization is required because MySQL JSON data format includes - # the space between the elements. - super(serialize(deserialize(raw_old_value)), new_value) + def text_to_sql(limit) # :nodoc: + 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 byte length #{limit}") + end end - end - class MysqlString < Type::String # :nodoc: - def serialize(value) - case value - when true then MySQL::Quoting::QUOTED_TRUE - when false then MySQL::Quoting::QUOTED_FALSE - else super + def binary_to_sql(limit) # :nodoc: + case limit + when 0..0xff; "tinyblob" + when nil, 0x100..0xffff; "blob" + when 0x10000..0xffffff; "mediumblob" + when 0x1000000..0xffffffff; "longblob" + else raise(ActiveRecordError, "No binary type has byte length #{limit}") end end - private + class MysqlJson < Type::Internal::AbstractJson # :nodoc: + def changed_in_place?(raw_old_value, new_value) + # Normalization is required because MySQL JSON data format includes + # the space between the elements. + super(serialize(deserialize(raw_old_value)), new_value) + end + end - def cast_value(value) - case value - when true then MySQL::Quoting::QUOTED_TRUE - when false then MySQL::Quoting::QUOTED_FALSE - else super + class MysqlString < Type::String # :nodoc: + def serialize(value) + case value + when true then MySQL::Quoting::QUOTED_TRUE + when false then MySQL::Quoting::QUOTED_FALSE + else super + end end + + private + + def cast_value(value) + case value + when true then MySQL::Quoting::QUOTED_TRUE + when false then MySQL::Quoting::QUOTED_FALSE + else super + end + end end - end - ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2) - ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2) - ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2) + ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2) + ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2) + ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2) end end end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 28f0c8686a..1808173592 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -52,9 +52,9 @@ module ActiveRecord protected - def attributes_for_hash - [self.class, name, default, sql_type_metadata, null, table_name, default_function, collation] - end + def attributes_for_hash + [self.class, name, default, sql_type_metadata, null, table_name, default_function, collation] + end end class NullColumn < Column diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 346916337e..be6b55e53c 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -1,4 +1,4 @@ -require 'uri' +require "uri" module ActiveRecord module ConnectionAdapters @@ -19,7 +19,6 @@ module ActiveRecord # Expands a connection string into a hash. class ConnectionUrlResolver # :nodoc: - # == Example # # url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000" @@ -37,11 +36,11 @@ module ActiveRecord def initialize(url) raise "Database URL cannot be empty" if url.blank? @uri = uri_parser.parse(url) - @adapter = @uri.scheme && @uri.scheme.tr('-', '_') + @adapter = @uri.scheme && @uri.scheme.tr("-", "_") @adapter = "postgresql" if @adapter == "postgres" if @uri.opaque - @uri.opaque, @query = @uri.opaque.split('?', 2) + @uri.opaque, @query = @uri.opaque.split("?", 2) else @query = @uri.query end @@ -56,13 +55,13 @@ module ActiveRecord private - def uri - @uri - end + def uri + @uri + end - def uri_parser - @uri_parser ||= URI::Parser.new - end + def uri_parser + @uri_parser ||= URI::Parser.new + end # Converts the query parameters of the URI into a hash. # @@ -73,41 +72,41 @@ module ActiveRecord # # "localhost" # # => {} - def query_hash - Hash[(@query || '').split("&").map { |pair| pair.split("=") }] - end + def query_hash + Hash[(@query || "").split("&").map { |pair| pair.split("=") }] + end - def raw_config - if uri.opaque - query_hash.merge({ - "adapter" => @adapter, - "database" => uri.opaque }) - else - query_hash.merge({ - "adapter" => @adapter, - "username" => uri.user, - "password" => uri.password, - "port" => uri.port, - "database" => database_from_path, - "host" => uri.hostname }) + def raw_config + if uri.opaque + query_hash.merge( + "adapter" => @adapter, + "database" => uri.opaque) + else + query_hash.merge( + "adapter" => @adapter, + "username" => uri.user, + "password" => uri.password, + "port" => uri.port, + "database" => database_from_path, + "host" => uri.hostname) + end end - end # Returns name of the database. - def database_from_path - if @adapter == 'sqlite3' - # 'sqlite3:/foo' is absolute, because that makes sense. The - # corresponding relative version, 'sqlite3:foo', is handled - # elsewhere, as an "opaque". + def database_from_path + if @adapter == "sqlite3" + # 'sqlite3:/foo' is absolute, because that makes sense. The + # corresponding relative version, 'sqlite3:foo', is handled + # elsewhere, as an "opaque". - uri.path - else - # Only SQLite uses a filename as the "database" name; for - # anything else, a leading slash would be silly. + uri.path + else + # Only SQLite uses a filename as the "database" name; for + # anything else, a leading slash would be silly. - uri.path.sub(%r{^/}, "") + uri.path.sub(%r{^/}, "") + end end - end end ## @@ -213,16 +212,16 @@ module ActiveRecord # Resolver.new({}).resolve_connection("postgresql://localhost/foo") # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } # - def resolve_connection(spec) - case spec - when Symbol - resolve_symbol_connection spec - when String - resolve_url_connection spec - when Hash - resolve_hash_connection spec + def resolve_connection(spec) + case spec + when Symbol + resolve_symbol_connection spec + when String + resolve_url_connection spec + when Hash + resolve_hash_connection spec + end end - end # Takes the environment such as +:production+ or +:development+. # This requires that the @configurations was initialized with a key that @@ -231,34 +230,34 @@ module ActiveRecord # Resolver.new("production" => {}).resolve_symbol_connection(:production) # # => {} # - def resolve_symbol_connection(spec) - if config = configurations[spec.to_s] - resolve_connection(config).merge("name" => spec.to_s) - else - raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}") + def resolve_symbol_connection(spec) + if config = configurations[spec.to_s] + resolve_connection(config).merge("name" => spec.to_s) + else + raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}") + end end - end # Accepts a hash. Expands the "url" key that contains a # URL database connection to a full connection # hash and merges with the rest of the hash. # Connection details inside of the "url" key win any merge conflicts - def resolve_hash_connection(spec) - if spec["url"] && spec["url"] !~ /^jdbc:/ - connection_hash = resolve_url_connection(spec.delete("url")) - spec.merge!(connection_hash) + def resolve_hash_connection(spec) + if spec["url"] && spec["url"] !~ /^jdbc:/ + connection_hash = resolve_url_connection(spec.delete("url")) + spec.merge!(connection_hash) + end + spec end - spec - end # Takes a connection URL. # # Resolver.new({}).resolve_url_connection("postgresql://localhost/foo") # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } # - def resolve_url_connection(url) - ConnectionUrlResolver.new(url).to_hash - end + def resolve_url_connection(url) + ConnectionUrlResolver.new(url).to_hash + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/column.rb b/activerecord/lib/active_record/connection_adapters/mysql/column.rb index ea554b188c..296d9a15f8 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/column.rb @@ -2,21 +2,7 @@ module ActiveRecord module ConnectionAdapters module MySQL class Column < ConnectionAdapters::Column # :nodoc: - delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true - - def initialize(*) - super - extract_default - end - - def has_default? - return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns - super - end - - def blob_or_text_column? - /\A(?:tiny|medium|long)?blob\b/ === sql_type || type == :text - end + delegate :extra, to: :sql_type_metadata, allow_nil: true def unsigned? /\bunsigned\z/ === sql_type @@ -27,15 +13,7 @@ module ActiveRecord end def auto_increment? - extra == 'auto_increment' - end - - private - - def extract_default - if blob_or_text_column? - @default = null || strict ? nil : '' - end + extra == "auto_increment" end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb index 87f0ff7d85..c8238eb266 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -33,7 +33,7 @@ module ActiveRecord super end - def exec_query(sql, name = 'SQL', binds = [], prepare: false) + def exec_query(sql, name = "SQL", binds = [], prepare: false) if without_prepared_statement?(binds) execute_and_free(sql, name) do |result| ActiveRecord::Result.new(result.fields, result.to_a) if result @@ -45,7 +45,7 @@ module ActiveRecord end end - def exec_delete(sql, name, binds) + def exec_delete(sql, name = nil, binds = []) if without_prepared_statement?(binds) execute_and_free(sql, name) { @connection.affected_rows } else @@ -56,56 +56,56 @@ module ActiveRecord protected - def last_inserted_id(result) - @connection.last_id - end + def last_inserted_id(result) + @connection.last_id + end private - def select_result(sql, name = nil, binds = []) - if without_prepared_statement?(binds) - execute_and_free(sql, name) { |result| yield result } - else - exec_stmt_and_free(sql, name, binds, cache_stmt: true) { |_, result| yield result } - end - end - - def exec_stmt_and_free(sql, name, binds, cache_stmt: false) - if @connection - # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been - # made since we established the connection - @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + def select_result(sql, name = nil, binds = []) + if without_prepared_statement?(binds) + execute_and_free(sql, name) { |result| yield result } + else + exec_stmt_and_free(sql, name, binds, cache_stmt: true) { |_, result| yield result } + end end - type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } - - log(sql, name, binds) do - if cache_stmt - cache = @statements[sql] ||= { - stmt: @connection.prepare(sql) - } - stmt = cache[:stmt] - else - stmt = @connection.prepare(sql) + def exec_stmt_and_free(sql, name, binds, cache_stmt: false) + if @connection + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone end - begin - result = stmt.execute(*type_casted_binds) - rescue Mysql2::Error => e + type_casted_binds = type_casted_binds(binds) + + log(sql, name, binds, type_casted_binds) do if cache_stmt - @statements.delete(sql) + cache = @statements[sql] ||= { + stmt: @connection.prepare(sql) + } + stmt = cache[:stmt] else - stmt.close + stmt = @connection.prepare(sql) + end + + begin + result = stmt.execute(*type_casted_binds) + rescue Mysql2::Error => e + if cache_stmt + @statements.delete(sql) + else + stmt.close + end + raise e end - raise e - end - ret = yield stmt, result - result.free if result - stmt.close unless cache_stmt - ret + ret = yield stmt, result + result.free if result + stmt.close unless cache_stmt + ret + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb index 1820853196..925555703d 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb @@ -36,34 +36,34 @@ module ActiveRecord private - def compute_column_widths(result) - [].tap do |widths| - result.columns.each_with_index do |column, i| - cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s} - widths << cells_in_column.map(&:length).max + def compute_column_widths(result) + [].tap do |widths| + result.columns.each_with_index do |column, i| + cells_in_column = [column] + result.rows.map { |r| r[i].nil? ? "NULL" : r[i].to_s } + widths << cells_in_column.map(&:length).max + end end end - end - def build_separator(widths) - padding = 1 - '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+' - end + def build_separator(widths) + padding = 1 + "+" + widths.map { |w| "-" * (w + (padding*2)) }.join("+") + "+" + end - def build_cells(items, widths) - cells = [] - items.each_with_index do |item, i| - item = 'NULL' if item.nil? - justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust' - cells << item.to_s.send(justifier, widths[i]) + def build_cells(items, widths) + cells = [] + items.each_with_index do |item, i| + item = "NULL" if item.nil? + justifier = item.is_a?(Numeric) ? "rjust" : "ljust" + cells << item.to_s.send(justifier, widths[i]) + end + "| " + cells.join(" | ") + " |" end - '| ' + cells.join(' | ') + ' |' - end - def build_footer(nrows, elapsed) - rows_label = nrows == 1 ? 'row' : 'rows' - "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed - end + def build_footer(nrows, elapsed) + rows_label = nrows == 1 ? "row" : "rows" + "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb index fbab654112..9d11ad28d4 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb @@ -2,14 +2,14 @@ module ActiveRecord module ConnectionAdapters module MySQL module Quoting # :nodoc: - QUOTED_TRUE, QUOTED_FALSE = '1', '0' + QUOTED_TRUE, QUOTED_FALSE = "1".freeze, "0".freeze def quote_column_name(name) - @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`" + @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`".freeze end def quote_table_name(name) - @quoted_table_names[name] ||= super.gsub('.', '`.`') + @quoted_table_names[name] ||= super.gsub(".", "`.`").freeze end def quoted_true @@ -32,19 +32,19 @@ module ActiveRecord if supports_datetime_with_precision? super else - super.sub(/\.\d{6}\z/, '') + super.sub(/\.\d{6}\z/, "") end end private - def _quote(value) - if value.is_a?(Type::Binary::Data) - "x'#{value.hex}'" - else - super + def _quote(value) + if value.is_a?(Type::Binary::Data) + "x'#{value.hex}'" + else + super + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb index fd2dc2aee8..d808b50332 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb @@ -7,60 +7,60 @@ module ActiveRecord private - def visit_DropForeignKey(name) - "DROP FOREIGN KEY #{name}" - end - - def visit_ColumnDefinition(o) - o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.unsigned) - super - end - - def visit_AddColumnDefinition(o) - add_column_position!(super, column_options(o.column)) - end + def visit_DropForeignKey(name) + "DROP FOREIGN KEY #{name}" + end - def visit_ChangeColumnDefinition(o) - change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}" - add_column_position!(change_column_sql, column_options(o.column)) - end + def visit_ColumnDefinition(o) + o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.unsigned) + super + end - def add_table_options!(create_sql, options) - add_sql_comment!(super, options[:comment]) - end + def visit_AddColumnDefinition(o) + add_column_position!(super, column_options(o.column)) + end - def column_options(o) - column_options = super - column_options[:charset] = o.charset - column_options - end + def visit_ChangeColumnDefinition(o) + change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}" + add_column_position!(change_column_sql, column_options(o.column)) + end - def add_column_options!(sql, options) - if charset = options[:charset] - sql << " CHARACTER SET #{charset}" + def add_table_options!(create_sql, options) + add_sql_comment!(super, options[:comment]) end - if collation = options[:collation] - sql << " COLLATE #{collation}" + def column_options(o) + column_options = super + column_options[:charset] = o.charset + column_options end - add_sql_comment!(super, options[:comment]) - end + def add_column_options!(sql, options) + if charset = options[:charset] + sql << " CHARACTER SET #{charset}" + end + + if collation = options[:collation] + sql << " COLLATE #{collation}" + end - def add_column_position!(sql, options) - if options[:first] - sql << " FIRST" - elsif options[:after] - sql << " AFTER #{quote_column_name(options[:after])}" + add_sql_comment!(super, options[:comment]) end - sql - end + def add_column_position!(sql, options) + if options[:first] + sql << " FIRST" + elsif options[:after] + sql << " AFTER #{quote_column_name(options[:after])}" + end - def index_in_create(table_name, column_name, options) - index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options) - add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})", comment) - end + sql + end + + def index_in_create(table_name, column_name, options) + index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options) + add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})", comment) + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb index 157e75dbf7..ce773ed75b 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb @@ -80,9 +80,9 @@ module ActiveRecord private - def create_column_definition(name, type) - MySQL::ColumnDefinition.new(name, type) - end + def create_column_definition(name, type) + MySQL::ColumnDefinition.new(name, type) + end end class Table < ActiveRecord::ConnectionAdapters::Table diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb index 2ba9657f24..39221eeb0c 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -5,17 +5,17 @@ module ActiveRecord def column_spec_for_primary_key(column) if column.bigint? spec = { id: :bigint.inspect } - spec[:default] = schema_default(column) || 'nil' unless column.auto_increment? + spec[:default] = schema_default(column) || "nil" unless column.auto_increment? else spec = super end - spec[:unsigned] = 'true' if column.unsigned? + spec[:unsigned] = "true" if column.unsigned? spec end def prepare_column_options(column) spec = super - spec[:unsigned] = 'true' if column.unsigned? + spec[:unsigned] = "true" if column.unsigned? spec end @@ -25,29 +25,29 @@ module ActiveRecord private - def default_primary_key?(column) - super && column.auto_increment? - end + def default_primary_key?(column) + super && column.auto_increment? + end - def schema_type(column) - if column.sql_type == 'tinyblob' - :blob - else - super + def schema_type(column) + if column.sql_type == "tinyblob" + :blob + else + super + end end - end - def schema_precision(column) - super unless /time/ === column.sql_type && column.precision == 0 - end + def schema_precision(column) + super unless /time/ === column.sql_type && column.precision == 0 + end - def schema_collation(column) - if column.collation && table_name = column.table_name - @table_collation_cache ||= {} - @table_collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"] - column.collation.inspect if column.collation != @table_collation_cache[table_name] + def schema_collation(column) + if column.collation && table_name = column.table_name + @table_collation_cache ||= {} + @table_collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"] + column.collation.inspect if column.collation != @table_collation_cache[table_name] + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb index e1e3f7b472..24dcf852e1 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb @@ -2,13 +2,12 @@ module ActiveRecord module ConnectionAdapters module MySQL class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc: - attr_reader :extra, :strict + attr_reader :extra - def initialize(type_metadata, extra: "", strict: false) + def initialize(type_metadata, extra: "") super(type_metadata) @type_metadata = type_metadata @extra = extra - @strict = strict end def ==(other) @@ -23,9 +22,9 @@ module ActiveRecord protected - def attributes_for_hash - [self.class, @type_metadata, extra, strict] - end + def attributes_for_hash + [self.class, @type_metadata, extra] + end end 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 22d35f1db5..0130b4ef62 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -1,9 +1,9 @@ -require 'active_record/connection_adapters/abstract_mysql_adapter' -require 'active_record/connection_adapters/mysql/database_statements' +require "active_record/connection_adapters/abstract_mysql_adapter" +require "active_record/connection_adapters/mysql/database_statements" -gem 'mysql2', '>= 0.3.18', '< 0.5' -require 'mysql2' -raise 'mysql2 0.4.3 is not supported. Please upgrade to 0.4.4+' if Mysql2::VERSION == '0.4.3' +gem "mysql2", ">= 0.3.18", "< 0.5" +require "mysql2" +raise "mysql2 0.4.3 is not supported. Please upgrade to 0.4.4+" if Mysql2::VERSION == "0.4.3" module ActiveRecord module ConnectionHandling # :nodoc: @@ -11,7 +11,7 @@ module ActiveRecord def mysql2_connection(config) config = config.symbolize_keys - config[:username] = 'root' if config[:username].nil? + config[:username] = "root" if config[:username].nil? config[:flags] ||= 0 if Mysql2::Client.const_defined? :FOUND_ROWS @@ -35,7 +35,7 @@ module ActiveRecord module ConnectionAdapters class Mysql2Adapter < AbstractMysqlAdapter - ADAPTER_NAME = 'Mysql2'.freeze + ADAPTER_NAME = "Mysql2".freeze include MySQL::DatabaseStatements @@ -46,7 +46,7 @@ module ActiveRecord end def supports_json? - !mariadb? && version >= '5.7.8' + !mariadb? && version >= "5.7.8" end def supports_comments? @@ -65,7 +65,7 @@ module ActiveRecord def each_hash(result) # :nodoc: if block_given? - result.each(:as => :hash, :symbolize_keys => true) do |row| + result.each(as: :hash, symbolize_keys: true) do |row| yield row end else @@ -113,19 +113,19 @@ module ActiveRecord private - def connect - @connection = Mysql2::Client.new(@config) - configure_connection - end + def connect + @connection = Mysql2::Client.new(@config) + configure_connection + end - def configure_connection - @connection.query_options.merge!(:as => :array) - super - end + def configure_connection + @connection.query_options.merge!(as: :array) + super + end - def full_version - @full_version ||= @connection.server_info[:version] - end + def full_version + @full_version ||= @connection.server_info[:version] + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 6f2e03b370..7414eba6c5 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -4,7 +4,7 @@ module ActiveRecord module DatabaseStatements def explain(arel, binds = []) sql = "EXPLAIN #{to_sql(arel, binds)}" - PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds)) + PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds)) end def select_value(arel, name = nil, binds = []) @@ -74,9 +74,9 @@ module ActiveRecord # (2) $12.345.678,12 case data when /^-?\D+[\d,]+\.\d{2}$/ # (1) - data.gsub!(/[^-\d.]/, '') + data.gsub!(/[^-\d.]/, "") when /^-?\D+[\d.]+,\d{2}$/ # (2) - data.gsub!(/[^-\d,]/, '').sub!(/,/, '.') + data.gsub!(/[^-\d,]/, "").sub!(/,/, ".") end end end @@ -99,7 +99,7 @@ module ActiveRecord end end - def exec_query(sql, name = 'SQL', binds = [], prepare: false) + def exec_query(sql, name = "SQL", binds = [], prepare: false) execute_and_clear(sql, name, binds, prepare: prepare) do |result| types = {} fields = result.fields @@ -112,8 +112,8 @@ module ActiveRecord end end - def exec_delete(sql, name = 'SQL', binds = []) - execute_and_clear(sql, name, binds) {|result| result.cmd_tuples } + def exec_delete(sql, name = nil, binds = []) + execute_and_clear(sql, name, binds) { |result| result.cmd_tuples } end alias :exec_update :exec_delete @@ -124,24 +124,29 @@ module ActiveRecord pk = primary_key(table_ref) if table_ref end - if pk && use_insert_returning? + if pk = suppress_composite_primary_key(pk) sql = "#{sql} RETURNING #{quote_column_name(pk)}" end super end + protected :sql_for_insert - def exec_insert(sql, name, binds, pk = nil, sequence_name = nil) - val = exec_query(sql, name, binds) - if !use_insert_returning? && pk + def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) + if use_insert_returning? || pk == false + super + else + result = exec_query(sql, name, binds) unless sequence_name table_ref = extract_table_ref_from_insert_sql(sql) - sequence_name = default_sequence_name(table_ref, pk) - return val unless sequence_name + if table_ref + pk = primary_key(table_ref) if pk.nil? + pk = suppress_composite_primary_key(pk) + sequence_name = default_sequence_name(table_ref, pk) + end + return result unless sequence_name end last_insert_id_result(sequence_name) - else - val end end @@ -164,6 +169,12 @@ module ActiveRecord def exec_rollback_db_transaction execute "ROLLBACK" end + + private + + def suppress_composite_primary_key(pk) + pk unless pk.is_a?(Array) + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb index 789b88912c..99f3a5bbdf 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb @@ -26,12 +26,12 @@ module ActiveRecord pp = [] pp << header.center(width).rstrip - pp << '-' * width + pp << "-" * width - pp += lines.map {|line| " #{line}"} + pp += lines.map { |line| " #{line}" } nrows = result.rows.length - rows_label = nrows == 1 ? 'row' : 'rows' + rows_label = nrows == 1 ? "row" : "rows" pp << "(#{nrows} #{rows_label})" pp.join("\n") + "\n" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 68752cdd80..8c318886cf 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -1,25 +1,25 @@ -require 'active_record/connection_adapters/postgresql/oid/array' -require 'active_record/connection_adapters/postgresql/oid/bit' -require 'active_record/connection_adapters/postgresql/oid/bit_varying' -require 'active_record/connection_adapters/postgresql/oid/bytea' -require 'active_record/connection_adapters/postgresql/oid/cidr' -require 'active_record/connection_adapters/postgresql/oid/date_time' -require 'active_record/connection_adapters/postgresql/oid/decimal' -require 'active_record/connection_adapters/postgresql/oid/enum' -require 'active_record/connection_adapters/postgresql/oid/hstore' -require 'active_record/connection_adapters/postgresql/oid/inet' -require 'active_record/connection_adapters/postgresql/oid/json' -require 'active_record/connection_adapters/postgresql/oid/jsonb' -require 'active_record/connection_adapters/postgresql/oid/money' -require 'active_record/connection_adapters/postgresql/oid/point' -require 'active_record/connection_adapters/postgresql/oid/rails_5_1_point' -require 'active_record/connection_adapters/postgresql/oid/range' -require 'active_record/connection_adapters/postgresql/oid/specialized_string' -require 'active_record/connection_adapters/postgresql/oid/uuid' -require 'active_record/connection_adapters/postgresql/oid/vector' -require 'active_record/connection_adapters/postgresql/oid/xml' +require "active_record/connection_adapters/postgresql/oid/array" +require "active_record/connection_adapters/postgresql/oid/bit" +require "active_record/connection_adapters/postgresql/oid/bit_varying" +require "active_record/connection_adapters/postgresql/oid/bytea" +require "active_record/connection_adapters/postgresql/oid/cidr" +require "active_record/connection_adapters/postgresql/oid/date_time" +require "active_record/connection_adapters/postgresql/oid/decimal" +require "active_record/connection_adapters/postgresql/oid/enum" +require "active_record/connection_adapters/postgresql/oid/hstore" +require "active_record/connection_adapters/postgresql/oid/inet" +require "active_record/connection_adapters/postgresql/oid/json" +require "active_record/connection_adapters/postgresql/oid/jsonb" +require "active_record/connection_adapters/postgresql/oid/money" +require "active_record/connection_adapters/postgresql/oid/point" +require "active_record/connection_adapters/postgresql/oid/rails_5_1_point" +require "active_record/connection_adapters/postgresql/oid/range" +require "active_record/connection_adapters/postgresql/oid/specialized_string" +require "active_record/connection_adapters/postgresql/oid/uuid" +require "active_record/connection_adapters/postgresql/oid/vector" +require "active_record/connection_adapters/postgresql/oid/xml" -require 'active_record/connection_adapters/postgresql/oid/type_map_initializer' +require "active_record/connection_adapters/postgresql/oid/type_map_initializer" module ActiveRecord module ConnectionAdapters diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb index 87593ef704..1a66afb23a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb @@ -8,7 +8,7 @@ module ActiveRecord attr_reader :subtype, :delimiter delegate :type, :user_input_in_time_zone, :limit, to: :subtype - def initialize(subtype, delimiter = ',') + def initialize(subtype, delimiter = ",") @subtype = subtype @delimiter = delimiter @@ -56,13 +56,13 @@ module ActiveRecord private - def type_cast_array(value, method) - if value.is_a?(::Array) - value.map { |item| type_cast_array(item, method) } - else - @subtype.public_send(method, value) + def type_cast_array(value, method) + if value.is_a?(::Array) + value.map { |item| type_cast_array(item, method) } + else + @subtype.public_send(method, value) + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb index ea0fa2517f..74bff229ea 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb @@ -7,7 +7,7 @@ module ActiveRecord :bit end - def cast(value) + def cast_value(value) if ::String === value case value when /^0x/i @@ -16,7 +16,7 @@ module ActiveRecord value # Bit-string notation end else - value + value.to_s end end @@ -43,7 +43,7 @@ module ActiveRecord protected - attr_reader :value + attr_reader :value end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb index 838cb63281..5225609e37 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb @@ -1,4 +1,4 @@ -require 'ipaddr' +require "ipaddr" module ActiveRecord module ConnectionAdapters diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb index 424769f765..b7acbf7178 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb @@ -5,8 +5,8 @@ module ActiveRecord class DateTime < Type::DateTime # :nodoc: def cast_value(value) case value - when 'infinity' then ::Float::INFINITY - when '-infinity' then -::Float::INFINITY + when "infinity" then ::Float::INFINITY + when "-infinity" then -::Float::INFINITY when / BC$/ astronomical_year = format("%04d", -value[/^\d+/].to_i + 1) super(value.sub(/ BC$/, "").sub(/^\d+/, astronomical_year)) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb index 91d339f32c..950d23d516 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb @@ -9,9 +9,9 @@ module ActiveRecord private - def cast_value(value) - value.to_s - end + def cast_value(value) + value.to_s + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb index 9270fc9f21..2d3e6a925d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb @@ -12,7 +12,7 @@ module ActiveRecord def deserialize(value) if value.is_a?(::String) ::Hash[value.scan(HstorePair).map { |k, v| - v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1') + v = v.upcase == "NULL" ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1') k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1') [k, v] }] @@ -23,7 +23,7 @@ module ActiveRecord def serialize(value) if value.is_a?(::Hash) - value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(', ') + value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(", ") else value end @@ -35,23 +35,23 @@ module ActiveRecord private - HstorePair = begin - quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/ - unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/ - /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/ - end + HstorePair = begin + quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/ + unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/ + /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/ + end - def escape_hstore(value) - if value.nil? - 'NULL' - else - if value == "" - '""' + def escape_hstore(value) + if value.nil? + "NULL" else - '"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1') + if value == "" + '""' + else + '"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1') + end end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb index dcc12ae2a4..7a91272d1c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb @@ -25,9 +25,9 @@ module ActiveRecord value.sub!(/^\((.+)\)$/, '-\1') # (4) case value when /^-?\D+[\d,]+\.\d{2}$/ # (1) - value.gsub!(/[^-\d.]/, '') + value.gsub!(/[^-\d.]/, "") when /^-?\D+[\d.]+,\d{2}$/ # (2) - value.gsub!(/[^-\d,]/, '').sub!(/,/, '.') + value.gsub!(/[^-\d,]/, "").sub!(/,/, ".") end super(value) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb index bf565bcf47..bb4db2564c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb @@ -12,10 +12,10 @@ module ActiveRecord def cast(value) case value when ::String - if value[0] == '(' && value[-1] == ')' + if value[0] == "(" && value[-1] == ")" value = value[1...-1] end - cast(value.split(',')) + cast(value.split(",")) when ::Array value.map { |v| Float(v) } else @@ -33,9 +33,9 @@ module ActiveRecord private - def number_for_point(number) - number.to_s.gsub(/\.0$/, '') - end + def number_for_point(number) + number.to_s.gsub(/\.0$/, "") + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb index 4da240edb2..845ff5b6a9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb @@ -15,8 +15,8 @@ module ActiveRecord case value when ::String return if value.blank? - - if value[0] == '(' && value[-1] == ')' + + if value[0] == "(" && value[-1] == ")" value = value[1...-1] end x, y = value.split(",") @@ -38,13 +38,13 @@ module ActiveRecord private - def number_for_point(number) - number.to_s.gsub(/\.0$/, '') - end + def number_for_point(number) + number.to_s.gsub(/\.0$/, "") + end - def build_point(x, y) - ActiveRecord::Point.new(Float(x), Float(y)) - end + def build_point(x, y) + ActiveRecord::Point.new(Float(x), Float(y)) + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb index a8d2310035..2c714f4018 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/filters' +require "active_support/core_ext/string/filters" module ActiveRecord module ConnectionAdapters @@ -14,11 +14,11 @@ module ActiveRecord end def type_cast_for_schema(value) - value.inspect.gsub('Infinity', '::Float::INFINITY') + value.inspect.gsub("Infinity", "::Float::INFINITY") end def cast_value(value) - return if value == 'empty' + return if value == "empty" return value unless value.is_a?(::String) extracted = extract_bounds(value) @@ -55,37 +55,37 @@ module ActiveRecord private - def type_cast_single(value) - infinity?(value) ? value : @subtype.deserialize(value) - end + def type_cast_single(value) + infinity?(value) ? value : @subtype.deserialize(value) + end - def type_cast_single_for_database(value) - infinity?(value) ? '' : @subtype.serialize(value) - end + def type_cast_single_for_database(value) + infinity?(value) ? "" : @subtype.serialize(value) + end - def extract_bounds(value) - from, to = value[1..-2].split(',') - { - from: (value[1] == ',' || from == '-infinity') ? infinity(negative: true) : from, - to: (value[-2] == ',' || to == 'infinity') ? infinity : to, - exclude_start: (value[0] == '('), - exclude_end: (value[-1] == ')') - } - end + def extract_bounds(value) + from, to = value[1..-2].split(",") + { + from: (value[1] == "," || from == "-infinity") ? infinity(negative: true) : from, + to: (value[-2] == "," || to == "infinity") ? infinity : to, + exclude_start: (value[0] == "("), + exclude_end: (value[-1] == ")") + } + end - def infinity(negative: false) - if subtype.respond_to?(:infinity) - subtype.infinity(negative: negative) - elsif negative - -::Float::INFINITY - else - ::Float::INFINITY + def infinity(negative: false) + if subtype.respond_to?(:infinity) + subtype.infinity(negative: negative) + elsif negative + -::Float::INFINITY + else + ::Float::INFINITY + end end - end - def infinity?(value) - value.respond_to?(:infinite?) && value.infinite? - end + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb index 6155e53632..d9ae1aa7a2 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb @@ -13,13 +13,13 @@ module ActiveRecord end def run(records) - nodes = records.reject { |row| @store.key? row['oid'].to_i } - mapped, nodes = nodes.partition { |row| @store.key? row['typname'] } - ranges, nodes = nodes.partition { |row| row['typtype'] == 'r'.freeze } - enums, nodes = nodes.partition { |row| row['typtype'] == 'e'.freeze } - domains, nodes = nodes.partition { |row| row['typtype'] == 'd'.freeze } - arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in'.freeze } - composites, nodes = nodes.partition { |row| row['typelem'].to_i != 0 } + nodes = records.reject { |row| @store.key? row["oid"].to_i } + mapped, nodes = nodes.partition { |row| @store.key? row["typname"] } + ranges, nodes = nodes.partition { |row| row["typtype"] == "r".freeze } + enums, nodes = nodes.partition { |row| row["typtype"] == "e".freeze } + domains, nodes = nodes.partition { |row| row["typtype"] == "d".freeze } + arrays, nodes = nodes.partition { |row| row["typinput"] == "array_in".freeze } + composites, nodes = nodes.partition { |row| row["typelem"].to_i != 0 } mapped.each { |row| register_mapped_type(row) } enums.each { |row| register_enum_type(row) } @@ -42,66 +42,66 @@ module ActiveRecord end private - def register_mapped_type(row) - alias_type row['oid'], row['typname'] - end + def register_mapped_type(row) + alias_type row["oid"], row["typname"] + end - def register_enum_type(row) - register row['oid'], OID::Enum.new - end + def register_enum_type(row) + register row["oid"], OID::Enum.new + end - def register_array_type(row) - register_with_subtype(row['oid'], row['typelem'].to_i) do |subtype| - OID::Array.new(subtype, row['typdelim']) + def register_array_type(row) + register_with_subtype(row["oid"], row["typelem"].to_i) do |subtype| + OID::Array.new(subtype, row["typdelim"]) + end end - end - def register_range_type(row) - register_with_subtype(row['oid'], row['rngsubtype'].to_i) do |subtype| - OID::Range.new(subtype, row['typname'].to_sym) + def register_range_type(row) + register_with_subtype(row["oid"], row["rngsubtype"].to_i) do |subtype| + OID::Range.new(subtype, row["typname"].to_sym) + end end - end - def register_domain_type(row) - if base_type = @store.lookup(row["typbasetype"].to_i) - register row['oid'], base_type - else - warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}." + def register_domain_type(row) + if base_type = @store.lookup(row["typbasetype"].to_i) + register row["oid"], base_type + else + warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}." + end end - end - def register_composite_type(row) - if subtype = @store.lookup(row['typelem'].to_i) - register row['oid'], OID::Vector.new(row['typdelim'], subtype) + def register_composite_type(row) + if subtype = @store.lookup(row["typelem"].to_i) + register row["oid"], OID::Vector.new(row["typdelim"], subtype) + end end - end - def register(oid, oid_type = nil, &block) - oid = assert_valid_registration(oid, oid_type || block) - if block_given? - @store.register_type(oid, &block) - else - @store.register_type(oid, oid_type) + def register(oid, oid_type = nil, &block) + oid = assert_valid_registration(oid, oid_type || block) + if block_given? + @store.register_type(oid, &block) + else + @store.register_type(oid, oid_type) + end end - end - def alias_type(oid, target) - oid = assert_valid_registration(oid, target) - @store.alias_type(oid, target) - end + def alias_type(oid, target) + oid = assert_valid_registration(oid, target) + @store.alias_type(oid, target) + end - def register_with_subtype(oid, target_oid) - if @store.key?(target_oid) - register(oid) do |_, *args| - yield @store.lookup(target_oid, *args) + def register_with_subtype(oid, target_oid) + if @store.key?(target_oid) + register(oid) do |_, *args| + yield @store.lookup(target_oid, *args) + end end end - end - def assert_valid_registration(oid, oid_type) - raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil? - oid.to_i - end + def assert_valid_registration(oid, oid_type) + raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil? + oid.to_i + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 6414459cd1..b5031d890f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -28,7 +28,7 @@ module ActiveRecord # - "schema.name".table_name # - "schema.name"."table.name" def quote_table_name(name) # :nodoc: - @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted + @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze end # Quotes schema names for use in SQL queries. @@ -42,7 +42,7 @@ module ActiveRecord # Quotes column names for use in SQL queries. def quote_column_name(name) # :nodoc: - @quoted_column_names[name] ||= PGconn.quote_ident(super) + @quoted_column_names[name] ||= PGconn.quote_ident(super).freeze end # Quote date/time values for use in SQL input. @@ -58,7 +58,7 @@ module ActiveRecord def quote_default_expression(value, column) # :nodoc: if value.is_a?(Proc) value.call - elsif column.type == :uuid && value =~ /\(\)/ + elsif column.type == :uuid && value.include?("()") value # Does not quote function default values for UUID columns elsif column.respond_to?(:array?) value = type_cast_from_column(column, value) @@ -74,42 +74,42 @@ module ActiveRecord private - def _quote(value) - case value - when Type::Binary::Data - "'#{escape_bytea(value.to_s)}'" - when OID::Xml::Data - "xml '#{quote_string(value.to_s)}'" - when OID::Bit::Data - if value.binary? - "B'#{value}'" - elsif value.hex? - "X'#{value}'" - end - when Float - if value.infinite? || value.nan? - "'#{value}'" + def _quote(value) + case value + when Type::Binary::Data + "'#{escape_bytea(value.to_s)}'" + when OID::Xml::Data + "xml '#{quote_string(value.to_s)}'" + when OID::Bit::Data + if value.binary? + "B'#{value}'" + elsif value.hex? + "X'#{value}'" + end + when Float + if value.infinite? || value.nan? + "'#{value}'" + else + super + end else super end - else - super end - end - def _type_cast(value) - case value - when Type::Binary::Data - # Return a bind param hash with format as binary. - # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc - # for more information - { value: value.to_s, format: 1 } - when OID::Xml::Data, OID::Bit::Data - value.to_s - else - super + def _type_cast(value) + case value + when Type::Binary::Data + # Return a bind param hash with format as binary. + # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc + # for more information + { value: value.to_s, format: 1 } + when OID::Xml::Data, OID::Bit::Data + value.to_s + else + super + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb index 6399bddbee..a11dbe7dce 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -31,7 +31,7 @@ module ActiveRecord # a record (as primary keys cannot be +nil+). This might be done via the # +SecureRandom.uuid+ method and a +before_save+ callback, for instance. def primary_key(name, type = :primary_key, **options) - options[:default] = options.fetch(:default, 'uuid_generate_v4()') if type == :uuid + options[:default] = options.fetch(:default, "uuid_generate_v4()") if type == :uuid super end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb index a1e10fd364..c20baf655c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb @@ -5,7 +5,7 @@ module ActiveRecord def column_spec_for_primary_key(column) spec = super if schema_type(column) == :uuid - spec[:default] ||= 'nil' + spec[:default] ||= "nil" end spec end @@ -13,7 +13,7 @@ module ActiveRecord # Adds +:array+ option to the default set def prepare_column_options(column) spec = super - spec[:array] = 'true' if column.array? + spec[:array] = "true" if column.array? spec end @@ -24,23 +24,23 @@ module ActiveRecord private - def default_primary_key?(column) - schema_type(column) == :serial - end + def default_primary_key?(column) + schema_type(column) == :serial + end - def schema_type(column) - return super unless column.serial? + def schema_type(column) + return super unless column.serial? - if column.bigint? - :bigserial - else - :serial + if column.bigint? + :bigserial + else + :serial + end end - end - def schema_expression(column) - super unless column.serial? - end + def schema_expression(column) + super unless column.serial? + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 45507e206a..696f2cd703 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/strip' +require "active_support/core_ext/string/strip" module ActiveRecord module ConnectionAdapters @@ -6,17 +6,17 @@ module ActiveRecord class SchemaCreation < AbstractAdapter::SchemaCreation private - def visit_ColumnDefinition(o) - o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array) - super - end + def visit_ColumnDefinition(o) + o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array) + super + end - def add_column_options!(sql, options) - if options[:collation] - sql << " COLLATE \"#{options[:collation]}\"" + def add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + super end - super - end end module SchemaStatements @@ -36,26 +36,26 @@ module ActiveRecord # create_database config[:database], config # create_database 'foo_development', encoding: 'unicode' def create_database(name, options = {}) - options = { encoding: 'utf8' }.merge!(options.symbolize_keys) + options = { encoding: "utf8" }.merge!(options.symbolize_keys) option_string = options.inject("") do |memo, (key, value)| memo += case key - when :owner - " OWNER = \"#{value}\"" - when :template - " TEMPLATE = \"#{value}\"" - when :encoding - " ENCODING = '#{value}'" - when :collation - " LC_COLLATE = '#{value}'" - when :ctype - " LC_CTYPE = '#{value}'" - when :tablespace - " TABLESPACE = \"#{value}\"" - when :connection_limit - " CONNECTION LIMIT = #{value}" + when :owner + " OWNER = \"#{value}\"" + when :template + " TEMPLATE = \"#{value}\"" + when :encoding + " ENCODING = '#{value}'" + when :collation + " LC_COLLATE = '#{value}'" + when :ctype + " LC_CTYPE = '#{value}'" + when :tablespace + " TABLESPACE = \"#{value}\"" + when :connection_limit + " CONNECTION LIMIT = #{value}" else - "" + "" end end @@ -78,11 +78,11 @@ module ActiveRecord MSG end - select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA') + select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", "SCHEMA") end def data_sources # :nodoc - select_values(<<-SQL, 'SCHEMA') + select_values(<<-SQL, "SCHEMA") SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace @@ -108,7 +108,7 @@ module ActiveRecord name = Utils.extract_schema_qualified_name(name.to_s) return false unless name.identifier - select_value(<<-SQL, 'SCHEMA').to_i > 0 + select_value(<<-SQL, "SCHEMA").to_i > 0 SELECT COUNT(*) FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace @@ -119,7 +119,7 @@ module ActiveRecord end def views # :nodoc: - select_values(<<-SQL, 'SCHEMA') + select_values(<<-SQL, "SCHEMA") SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace @@ -132,7 +132,7 @@ module ActiveRecord name = Utils.extract_schema_qualified_name(view_name.to_s) return false unless name.identifier - select_values(<<-SQL, 'SCHEMA').any? + select_values(<<-SQL, "SCHEMA").any? SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace @@ -148,7 +148,7 @@ module ActiveRecord # Returns true if schema exists. def schema_exists?(name) - select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", 'SCHEMA').to_i > 0 + select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", "SCHEMA").to_i > 0 end # Verifies existence of an index with a given name. @@ -156,7 +156,7 @@ module ActiveRecord table = Utils.extract_schema_qualified_name(table_name.to_s) index = Utils.extract_schema_qualified_name(index_name.to_s) - select_value(<<-SQL, 'SCHEMA').to_i > 0 + select_value(<<-SQL, "SCHEMA").to_i > 0 SELECT COUNT(*) FROM pg_class t INNER JOIN pg_index d ON t.oid = d.indrelid @@ -173,7 +173,7 @@ module ActiveRecord def indexes(table_name, name = nil) table = Utils.extract_schema_qualified_name(table_name.to_s) - result = query(<<-SQL, 'SCHEMA') + result = query(<<-SQL, "SCHEMA") SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid, pg_catalog.obj_description(i.oid, 'pg_class') AS comment, (SELECT COUNT(*) FROM pg_opclass o @@ -238,11 +238,15 @@ module ActiveRecord PostgreSQLColumn.new(*args) end + def table_options(table_name) # :nodoc: + { comment: table_comment(table_name) } + end + # Returns a comment stored in database for given table def table_comment(table_name) # :nodoc: name = Utils.extract_schema_qualified_name(table_name.to_s) if name.identifier - select_value(<<-SQL.strip_heredoc, 'SCHEMA') + select_value(<<-SQL.strip_heredoc, "SCHEMA") SELECT pg_catalog.obj_description(c.oid, 'pg_class') FROM pg_catalog.pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace @@ -255,32 +259,32 @@ module ActiveRecord # Returns the current database name. def current_database - select_value('select current_database()', 'SCHEMA') + select_value("select current_database()", "SCHEMA") end # Returns the current schema name. def current_schema - select_value('SELECT current_schema', 'SCHEMA') + select_value("SELECT current_schema", "SCHEMA") end # Returns the current database encoding format. def encoding - select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA') + select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA") end # Returns the current database collation. def collation - select_value("SELECT datcollate FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA') + select_value("SELECT datcollate FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA") end # Returns the current database ctype. def ctype - select_value("SELECT datctype FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA') + select_value("SELECT datctype FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA") end # Returns an array of schema names. def schema_names - select_values(<<-SQL, 'SCHEMA') + select_values(<<-SQL, "SCHEMA") SELECT nspname FROM pg_namespace WHERE nspname !~ '^pg_.*' @@ -290,7 +294,7 @@ module ActiveRecord end # Creates a schema for the given schema name. - def create_schema schema_name + def create_schema(schema_name) execute "CREATE SCHEMA #{quote_schema_name(schema_name)}" end @@ -306,37 +310,37 @@ 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}", 'SCHEMA') + execute("SET search_path TO #{schema_csv}", "SCHEMA") @schema_search_path = schema_csv end end # Returns the active schema search path. def schema_search_path - @schema_search_path ||= select_value('SHOW search_path', 'SCHEMA') + @schema_search_path ||= select_value("SHOW search_path", "SCHEMA") end # Returns the current client message level. def client_min_messages - select_value('SHOW client_min_messages', 'SCHEMA') + select_value("SHOW client_min_messages", "SCHEMA") end # Set the client message level. def client_min_messages=(level) - execute("SET client_min_messages TO '#{level}'", 'SCHEMA') + execute("SET client_min_messages TO '#{level}'", "SCHEMA") end # Returns the sequence name for a table's primary key or some other specified key. - def default_sequence_name(table_name, pk = nil) #:nodoc: - result = serial_sequence(table_name, pk || 'id') + def default_sequence_name(table_name, pk = "id") #:nodoc: + result = serial_sequence(table_name, pk) return nil unless result Utils.extract_schema_qualified_name(result).to_s rescue ActiveRecord::StatementInvalid - PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s + PostgreSQL::Name.new(nil, "#{table_name}_#{pk}_seq").to_s end def serial_sequence(table, column) - select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA') + select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", "SCHEMA") end # Sets the sequence of a table's primary key to the specified value. @@ -347,7 +351,7 @@ module ActiveRecord if sequence quoted_sequence = quote_table_name(sequence) - select_value("SELECT setval('#{quoted_sequence}', #{value})", 'SCHEMA') + select_value("SELECT setval('#{quoted_sequence}', #{value})", "SCHEMA") else @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger end @@ -370,7 +374,7 @@ module ActiveRecord if pk && sequence quoted_sequence = quote_table_name(sequence) - select_value(<<-end_sql, 'SCHEMA') + select_value(<<-end_sql, "SCHEMA") SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false) end_sql end @@ -380,7 +384,7 @@ 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 = query(<<-end_sql, 'SCHEMA')[0] + result = query(<<-end_sql, "SCHEMA")[0] SELECT attr.attname, nsp.nspname, seq.relname FROM pg_class seq, pg_attribute attr, @@ -400,7 +404,7 @@ module ActiveRecord end_sql if result.nil? or result.empty? - result = query(<<-end_sql, 'SCHEMA')[0] + result = query(<<-end_sql, "SCHEMA")[0] SELECT attr.attname, nsp.nspname, CASE WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL @@ -431,7 +435,7 @@ module ActiveRecord end def primary_keys(table_name) # :nodoc: - select_values(<<-SQL.strip_heredoc, 'SCHEMA') + select_values(<<-SQL.strip_heredoc, "SCHEMA") WITH pk_constraint AS ( SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint WHERE contype = 'p' @@ -579,7 +583,7 @@ module ActiveRecord end def foreign_keys(table_name) - fk_info = select_all(<<-SQL.strip_heredoc, 'SCHEMA') + fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA") SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete FROM pg_constraint c JOIN pg_class t1 ON c.conrelid = t1.oid @@ -595,23 +599,23 @@ module ActiveRecord fk_info.map do |row| options = { - column: row['column'], - name: row['name'], - primary_key: row['primary_key'] + column: row["column"], + name: row["name"], + primary_key: row["primary_key"] } - options[:on_delete] = extract_foreign_key_action(row['on_delete']) - options[:on_update] = extract_foreign_key_action(row['on_update']) + options[:on_delete] = extract_foreign_key_action(row["on_delete"]) + options[:on_update] = extract_foreign_key_action(row["on_update"]) - ForeignKeyDefinition.new(table_name, row['to_table'], options) + ForeignKeyDefinition.new(table_name, row["to_table"], options) end end def extract_foreign_key_action(specifier) # :nodoc: case specifier - when 'c'; :cascade - when 'n'; :nullify - when 'r'; :restrict + when "c"; :cascade + when "n"; :nullify + when "r"; :restrict end end @@ -622,47 +626,47 @@ module ActiveRecord # Maps logical Rails types to PostgreSQL-specific data types. def type_to_sql(type, limit = nil, precision = nil, scale = nil, array = nil) sql = case type.to_s - when 'binary' + when "binary" # PostgreSQL doesn't support limits on binary (bytea) columns. # The hard limit is 1GB, because of a 32-bit size field, and TOAST. - case limit - when nil, 0..0x3fffffff; super(type) - else raise(ActiveRecordError, "No binary type has byte size #{limit}.") - end - when 'text' + case limit + when nil, 0..0x3fffffff; super(type) + else raise(ActiveRecordError, "No binary type has byte size #{limit}.") + end + when "text" # PostgreSQL doesn't support limits on text columns. # The hard limit is 1GB, according to section 8.3 in the manual. - case limit - when nil, 0..0x3fffffff; super(type) - else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.") - end - when 'integer' - case limit - when 1, 2; 'smallint' - when nil, 3, 4; 'integer' - when 5..8; 'bigint' - else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.") - end + case limit + when nil, 0..0x3fffffff; super(type) + else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.") + end + when "integer" + case limit + when 1, 2; "smallint" + when nil, 3, 4; "integer" + when 5..8; "bigint" + else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.") + end else - super(type, limit, precision, scale) + super(type, limit, precision, scale) end - sql << '[]' if array && type != :primary_key + sql << "[]" if array && type != :primary_key sql end # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and # requires that the ORDER BY include the distinct column. def columns_for_distinct(columns, orders) #:nodoc: - order_columns = orders.reject(&:blank?).map{ |s| + order_columns = orders.reject(&:blank?).map { |s| # Convert Arel node to string s = s.to_sql unless s.is_a?(String) # Remove any ASC/DESC modifiers - s.gsub(/\s+(?:ASC|DESC)\b/i, '') - .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '') + s.gsub(/\s+(?:ASC|DESC)\b/i, "") + .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "") }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" } - [super, *order_columns].join(', ') + [super, *order_columns].join(", ") end def fetch_type_metadata(column_name, sql_type, oid, fmod) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb index b2c49989a4..bcef8ac715 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb @@ -27,9 +27,9 @@ module ActiveRecord protected - def attributes_for_hash - [self.class, @type_metadata, oid, fmod] - end + def attributes_for_hash + [self.class, @type_metadata, oid, fmod] + 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 ddfc560747..bd53123511 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1,6 +1,6 @@ # Make sure we're using pg high enough for type casts and Ruby 2.2+ compatibility -gem 'pg', '~> 0.18' -require 'pg' +gem "pg", "~> 0.18" +require "pg" require "active_record/connection_adapters/abstract_adapter" require "active_record/connection_adapters/postgresql/column" @@ -67,7 +67,7 @@ module ActiveRecord # In addition, default connection parameters of libpq can be set per environment variables. # See http://www.postgresql.org/docs/current/static/libpq-envars.html . class PostgreSQLAdapter < AbstractAdapter - ADAPTER_NAME = 'PostgreSQL'.freeze + ADAPTER_NAME = "PostgreSQL".freeze NATIVE_DATABASE_TYPES = { primary_key: "serial primary key", @@ -173,7 +173,7 @@ module ActiveRecord end def index_algorithms - { concurrently: 'CONCURRENTLY' } + { concurrently: "CONCURRENTLY" } end class StatementPool < ConnectionAdapters::StatementPool @@ -227,7 +227,7 @@ module ActiveRecord @type_map = Type::HashLookupTypeMap.new initialize_type_map(type_map) - @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"] + @local_tz = execute("SHOW TIME ZONE", "SCHEMA").first["TimeZone"] @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true end @@ -242,7 +242,7 @@ module ActiveRecord # Is this connection alive and ready for queries? def active? - @connection.query 'SELECT 1' + @connection.query "SELECT 1" true rescue PGError false @@ -259,9 +259,9 @@ module ActiveRecord clear_cache! reset_transaction unless @connection.transaction_status == ::PG::PQTRANS_IDLE - @connection.query 'ROLLBACK' + @connection.query "ROLLBACK" end - @connection.query 'DISCARD ALL' + @connection.query "DISCARD ALL" configure_connection end @@ -287,7 +287,7 @@ module ActiveRecord end def set_standard_conforming_strings - execute('SET standard_conforming_strings = on', 'SCHEMA') + execute("SET standard_conforming_strings = on", "SCHEMA") end def supports_ddl_transactions? @@ -344,7 +344,7 @@ module ActiveRecord def extension_enabled?(name) if supports_extensions? res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", - 'SCHEMA' + "SCHEMA" res.cast_values.first end end @@ -359,7 +359,7 @@ module ActiveRecord # Returns the configured supported identifier length supported by PostgreSQL def table_alias_length - @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i + @table_alias_length ||= query("SHOW max_identifier_length", "SCHEMA")[0][0].to_i end # Set the authorized user for this session @@ -381,7 +381,7 @@ module ActiveRecord end def lookup_cast_type(sql_type) # :nodoc: - oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first['oid'].to_i + oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first["oid"].to_i super(oid) end @@ -407,6 +407,7 @@ module ActiveRecord FOREIGN_KEY_VIOLATION = "23503" UNIQUE_VIOLATION = "23505" SERIALIZATION_FAILURE = "40001" + DEADLOCK_DETECTED = "40P01" def translate_exception(exception, message) return exception unless exception.respond_to?(:result) @@ -419,7 +420,9 @@ module ActiveRecord when VALUE_LIMIT_VIOLATION ValueTooLong.new(message) when SERIALIZATION_FAILURE - TransactionSerializationError.new(message) + SerializationFailure.new(message) + when DEADLOCK_DETECTED + Deadlocked.new(message) else super end @@ -427,7 +430,7 @@ module ActiveRecord private - def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc: + def get_oid_type(oid, fmod, column_name, sql_type = "") # :nodoc: if !type_map.key?(oid) load_additional_types(type_map, [oid]) end @@ -441,51 +444,51 @@ module ActiveRecord end def initialize_type_map(m) # :nodoc: - register_class_with_limit m, 'int2', Type::Integer - register_class_with_limit m, 'int4', Type::Integer - register_class_with_limit m, 'int8', Type::Integer - m.alias_type 'oid', 'int2' - m.register_type 'float4', Type::Float.new - m.alias_type 'float8', 'float4' - m.register_type 'text', Type::Text.new - register_class_with_limit m, 'varchar', Type::String - m.alias_type 'char', 'varchar' - m.alias_type 'name', 'varchar' - m.alias_type 'bpchar', 'varchar' - m.register_type 'bool', Type::Boolean.new - register_class_with_limit m, 'bit', OID::Bit - register_class_with_limit m, 'varbit', OID::BitVarying - m.alias_type 'timestamptz', 'timestamp' - m.register_type 'date', Type::Date.new - - m.register_type 'money', OID::Money.new - m.register_type 'bytea', OID::Bytea.new - m.register_type 'point', OID::Point.new - m.register_type 'hstore', OID::Hstore.new - m.register_type 'json', OID::Json.new - m.register_type 'jsonb', OID::Jsonb.new - m.register_type 'cidr', OID::Cidr.new - m.register_type 'inet', OID::Inet.new - m.register_type 'uuid', OID::Uuid.new - m.register_type 'xml', OID::Xml.new - m.register_type 'tsvector', OID::SpecializedString.new(:tsvector) - m.register_type 'macaddr', OID::SpecializedString.new(:macaddr) - m.register_type 'citext', OID::SpecializedString.new(:citext) - m.register_type 'ltree', OID::SpecializedString.new(:ltree) - m.register_type 'line', OID::SpecializedString.new(:line) - m.register_type 'lseg', OID::SpecializedString.new(:lseg) - m.register_type 'box', OID::SpecializedString.new(:box) - m.register_type 'path', OID::SpecializedString.new(:path) - m.register_type 'polygon', OID::SpecializedString.new(:polygon) - m.register_type 'circle', OID::SpecializedString.new(:circle) + register_class_with_limit m, "int2", Type::Integer + register_class_with_limit m, "int4", Type::Integer + register_class_with_limit m, "int8", Type::Integer + m.alias_type "oid", "int2" + m.register_type "float4", Type::Float.new + m.alias_type "float8", "float4" + m.register_type "text", Type::Text.new + register_class_with_limit m, "varchar", Type::String + m.alias_type "char", "varchar" + m.alias_type "name", "varchar" + m.alias_type "bpchar", "varchar" + m.register_type "bool", Type::Boolean.new + register_class_with_limit m, "bit", OID::Bit + register_class_with_limit m, "varbit", OID::BitVarying + m.alias_type "timestamptz", "timestamp" + m.register_type "date", Type::Date.new + + m.register_type "money", OID::Money.new + m.register_type "bytea", OID::Bytea.new + m.register_type "point", OID::Point.new + m.register_type "hstore", OID::Hstore.new + m.register_type "json", OID::Json.new + m.register_type "jsonb", OID::Jsonb.new + m.register_type "cidr", OID::Cidr.new + m.register_type "inet", OID::Inet.new + m.register_type "uuid", OID::Uuid.new + m.register_type "xml", OID::Xml.new + m.register_type "tsvector", OID::SpecializedString.new(:tsvector) + m.register_type "macaddr", OID::SpecializedString.new(:macaddr) + m.register_type "citext", OID::SpecializedString.new(:citext) + m.register_type "ltree", OID::SpecializedString.new(:ltree) + m.register_type "line", OID::SpecializedString.new(:line) + m.register_type "lseg", OID::SpecializedString.new(:lseg) + m.register_type "box", OID::SpecializedString.new(:box) + m.register_type "path", OID::SpecializedString.new(:path) + m.register_type "polygon", OID::SpecializedString.new(:polygon) + m.register_type "circle", OID::SpecializedString.new(:circle) # FIXME: why are we keeping these types as strings? - m.alias_type 'interval', 'varchar' + m.alias_type "interval", "varchar" - register_class_with_precision m, 'time', Type::Time - register_class_with_precision m, 'timestamp', OID::DateTime + register_class_with_precision m, "time", Type::Time + register_class_with_precision m, "timestamp", OID::DateTime - m.register_type 'numeric' do |_, fmod, sql_type| + m.register_type "numeric" do |_, fmod, sql_type| precision = extract_precision(sql_type) scale = extract_scale(sql_type) @@ -523,26 +526,26 @@ module ActiveRecord def extract_value_from_default(default) # :nodoc: case default # Quoted types - when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m + when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m # The default 'now'::date is CURRENT_DATE - if $1 == "now".freeze && $2 == "date".freeze - nil - else - $1.gsub("''".freeze, "'".freeze) - end + if $1 == "now".freeze && $2 == "date".freeze + nil + else + $1.gsub("''".freeze, "'".freeze) + end # Boolean types - when 'true'.freeze, 'false'.freeze - default + when "true".freeze, "false".freeze + default # Numeric types - when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/ - $1 + when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/ + $1 # Object identifier types - when /\A-?\d+\z/ - $1 + 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 + nil end end @@ -576,7 +579,7 @@ module ActiveRecord query += initializer.query_conditions_for_initial_load(type_map) end - execute_and_clear(query, 'SCHEMA', []) do |records| + execute_and_clear(query, "SCHEMA", []) do |records| initializer.run(records) end end @@ -597,15 +600,15 @@ module ActiveRecord end def exec_no_cache(sql, name, binds) - type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } - log(sql, name, binds) { @connection.async_exec(sql, type_casted_binds) } + type_casted_binds = type_casted_binds(binds) + log(sql, name, binds, type_casted_binds) { @connection.async_exec(sql, type_casted_binds) } end def exec_cache(sql, name, binds) stmt_key = prepare_statement(sql) - type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } + type_casted_binds = type_casted_binds(binds) - log(sql, name, binds, stmt_key) do + log(sql, name, binds, type_casted_binds, stmt_key) do @connection.exec_prepared(stmt_key, type_casted_binds) end rescue ActiveRecord::StatementInvalid => e @@ -631,7 +634,7 @@ module ActiveRecord # # Check here for more details: # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573 - CACHED_PLAN_HEURISTIC = 'cached plan must not change result type'.freeze + CACHED_PLAN_HEURISTIC = "cached plan must not change result type".freeze def is_cached_plan_failure?(e) pgerror = e.cause code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE) @@ -687,7 +690,7 @@ module ActiveRecord if @config[:encoding] @connection.set_client_encoding(@config[:encoding]) end - self.client_min_messages = @config[:min_messages] || 'warning' + self.client_min_messages = @config[:min_messages] || "warning" self.schema_search_path = @config[:schema_search_path] || @config[:schema_order] # Use standard-conforming strings so we don't have to do the E'...' dance. @@ -697,27 +700,27 @@ module ActiveRecord # TIMESTAMP WITH ZONE types in UTC. # (SET TIME ZONE does not use an equals sign like other SET variables) if ActiveRecord::Base.default_timezone == :utc - execute("SET time zone 'UTC'", 'SCHEMA') + execute("SET time zone 'UTC'", "SCHEMA") elsif @local_tz - execute("SET time zone '#{@local_tz}'", 'SCHEMA') + execute("SET time zone '#{@local_tz}'", "SCHEMA") end # SET statements from :variables config hash # http://www.postgresql.org/docs/current/static/sql-set.html variables = @config[:variables] || {} variables.map do |k, v| - if v == ':default' || v == :default + if v == ":default" || v == :default # Sets the value to the global or compile default - execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA') + execute("SET SESSION #{k} TO DEFAULT", "SCHEMA") elsif !v.nil? - execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA') + execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA") end end end # Returns the current ID of a table's sequence. def last_insert_id_result(sequence_name) # :nodoc: - exec_query("SELECT currval('#{sequence_name}')", 'SQL') + exec_query("SELECT currval('#{sequence_name}')", "SQL") end # Returns the list of a table's column names, data types, and default values. @@ -739,7 +742,7 @@ module ActiveRecord # - format_type includes the column size constraint, e.g. varchar(50) # - ::regclass is a function that gives the id for a table name def column_definitions(table_name) # :nodoc: - query(<<-end_sql, 'SCHEMA') + query(<<-end_sql, "SCHEMA") SELECT a.attname, format_type(a.atttypid, a.atttypmod), pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod, (SELECT c.collname FROM pg_collation c, pg_type t @@ -791,13 +794,13 @@ module ActiveRecord def add_pg_decoders coders_by_name = { - 'int2' => PG::TextDecoder::Integer, - 'int4' => PG::TextDecoder::Integer, - 'int8' => PG::TextDecoder::Integer, - 'oid' => PG::TextDecoder::Integer, - 'float4' => PG::TextDecoder::Float, - 'float8' => PG::TextDecoder::Float, - 'bool' => PG::TextDecoder::Boolean, + "int2" => PG::TextDecoder::Integer, + "int4" => PG::TextDecoder::Integer, + "int8" => PG::TextDecoder::Integer, + "oid" => PG::TextDecoder::Integer, + "float4" => PG::TextDecoder::Float, + "float8" => PG::TextDecoder::Float, + "bool" => PG::TextDecoder::Boolean, } known_coder_types = coders_by_name.keys.map { |n| quote(n) } query = <<-SQL % known_coder_types.join(", ") @@ -807,7 +810,7 @@ module ActiveRecord SQL coders = execute_and_clear(query, "SCHEMA", []) do |result| result - .map { |row| construct_coder(row, coders_by_name[row['typname']]) } + .map { |row| construct_coder(row, coders_by_name[row["typname"]]) } .compact end @@ -818,7 +821,7 @@ module ActiveRecord def construct_coder(row, coder_class) return unless coder_class - coder_class.new(oid: row['oid'].to_i, name: row['typname']) + coder_class.new(oid: row["oid"].to_i, name: row["typname"]) end ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql) diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index eee142378c..8219f132c3 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -33,8 +33,7 @@ module ActiveRecord @data_sources[name] = connection.data_source_exists?(name) end alias table_exists? data_source_exists? - deprecate :table_exists? => "use #data_source_exists? instead" - + deprecate table_exists?: "use #data_source_exists? instead" # Add internal cache for table with +table_name+. def add(table_name) @@ -49,7 +48,7 @@ module ActiveRecord @data_sources[name] end alias tables data_sources - deprecate :tables => "use #data_sources instead" + deprecate tables: "use #data_sources instead" # Get the columns for a table def columns(table_name) @@ -85,7 +84,7 @@ module ActiveRecord @data_sources.delete name end alias clear_table_cache! clear_data_source_cache! - deprecate :clear_table_cache! => "use #clear_data_source_cache! instead" + deprecate clear_table_cache!: "use #clear_data_source_cache! instead" def marshal_dump # if we get current version during initialization, it happens stack over flow. diff --git a/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb b/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb index ccb7e154ee..9e12ae0de8 100644 --- a/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb +++ b/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb @@ -24,9 +24,9 @@ module ActiveRecord protected - def attributes_for_hash - [self.class, sql_type, type, limit, precision, scale] - end + def attributes_for_hash + [self.class, sql_type, type, limit, precision, scale] + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb index a946f5ebd0..6fe3e1211e 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb @@ -10,7 +10,7 @@ module ActiveRecord # def pp(result) result.rows.map do |row| - row.join('|') + row.join("|") end.join("\n") + "\n" end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb index d5a181d3e2..f01ed67b0f 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb @@ -11,7 +11,7 @@ module ActiveRecord end def quote_column_name(name) - @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}") + @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}").freeze end def quoted_time(value) @@ -20,28 +20,28 @@ module ActiveRecord private - def _quote(value) - if value.is_a?(Type::Binary::Data) - "x'#{value.hex}'" - else - super + def _quote(value) + if value.is_a?(Type::Binary::Data) + "x'#{value.hex}'" + else + super + end end - end - def _type_cast(value) - case value - when BigDecimal - value.to_f - when String - if value.encoding == Encoding::ASCII_8BIT - super(value.encode(Encoding::UTF_8)) + def _type_cast(value) + case value + when BigDecimal + value.to_f + when String + if value.encoding == Encoding::ASCII_8BIT + super(value.encode(Encoding::UTF_8)) + else + super + end else super end - else - super end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index eb2268157b..e2b534b511 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -1,11 +1,11 @@ -require 'active_record/connection_adapters/abstract_adapter' -require 'active_record/connection_adapters/statement_pool' -require 'active_record/connection_adapters/sqlite3/explain_pretty_printer' -require 'active_record/connection_adapters/sqlite3/quoting' -require 'active_record/connection_adapters/sqlite3/schema_creation' +require "active_record/connection_adapters/abstract_adapter" +require "active_record/connection_adapters/statement_pool" +require "active_record/connection_adapters/sqlite3/explain_pretty_printer" +require "active_record/connection_adapters/sqlite3/quoting" +require "active_record/connection_adapters/sqlite3/schema_creation" -gem 'sqlite3', '~> 1.3.6' -require 'sqlite3' +gem "sqlite3", "~> 1.3.6" +require "sqlite3" module ActiveRecord module ConnectionHandling # :nodoc: @@ -18,7 +18,7 @@ module ActiveRecord # Allow database path relative to Rails.root, but only if the database # path is not the special path that tells sqlite to build a database only # in memory. - if ':memory:' != config[:database] + if ":memory:" != config[:database] config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root) dirname = File.dirname(config[:database]) Dir.mkdir(dirname) unless File.directory?(dirname) @@ -26,7 +26,7 @@ module ActiveRecord db = SQLite3::Database.new( config[:database].to_s, - :results_as_hash => true + results_as_hash: true ) db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout] @@ -49,12 +49,12 @@ module ActiveRecord # # * <tt>:database</tt> - Path to the database file. class SQLite3Adapter < AbstractAdapter - ADAPTER_NAME = 'SQLite'.freeze + ADAPTER_NAME = "SQLite".freeze include SQLite3::Quoting NATIVE_DATABASE_TYPES = { - primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL', + primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL", string: { name: "varchar" }, text: { name: "text" }, integer: { name: "integer" }, @@ -70,9 +70,9 @@ module ActiveRecord class StatementPool < ConnectionAdapters::StatementPool private - def dealloc(stmt) - stmt[:stmt].close unless stmt[:stmt].closed? - end + def dealloc(stmt) + stmt[:stmt].close unless stmt[:stmt].closed? + end end def schema_creation # :nodoc: @@ -99,7 +99,7 @@ module ActiveRecord end def supports_partial_index? - sqlite_version >= '3.8.0' + sqlite_version >= "3.8.0" end # Returns true, since this connection adapter supports prepared statement @@ -130,7 +130,7 @@ module ActiveRecord end def supports_multi_insert? - sqlite_version >= '3.7.11' + sqlite_version >= "3.7.11" end def active? @@ -159,7 +159,7 @@ module ActiveRecord end # Returns 62. SQLite supports index names up to 64 - # characters. The rest is used by rails internally to perform + # characters. The rest is used by Rails internally to perform # temporary rename operations def allowed_index_name_length index_name_length - 2 @@ -184,13 +184,13 @@ module ActiveRecord def explain(arel, binds = []) sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" - SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', [])) + SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", [])) end def exec_query(sql, name = nil, binds = [], prepare: false) - type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } + type_casted_binds = type_casted_binds(binds) - log(sql, name, binds) do + log(sql, name, binds, type_casted_binds) do # Don't cache statements if they are not prepared unless prepare stmt = @connection.prepare(sql) @@ -203,22 +203,22 @@ module ActiveRecord ensure stmt.close end - stmt = records else cache = @statements[sql] ||= { - :stmt => @connection.prepare(sql) + stmt: @connection.prepare(sql) } stmt = cache[:stmt] cols = cache[:cols] ||= stmt.columns stmt.reset! stmt.bind_params(type_casted_binds) + records = stmt.to_a end - ActiveRecord::Result.new(cols, stmt.to_a) + ActiveRecord::Result.new(cols, records) end end - def exec_delete(sql, name = 'SQL', binds = []) + def exec_delete(sql, name = "SQL", binds = []) exec_query(sql, name, binds) @connection.changes end @@ -233,15 +233,15 @@ module ActiveRecord end def begin_db_transaction #:nodoc: - log('begin transaction',nil) { @connection.transaction } + log("begin transaction",nil) { @connection.transaction } end def commit_db_transaction #:nodoc: - log('commit transaction',nil) { @connection.commit } + log("commit transaction",nil) { @connection.commit } end def exec_rollback_db_transaction #:nodoc: - log('rollback transaction',nil) { @connection.rollback } + log("rollback transaction",nil) { @connection.rollback } end # SCHEMA STATEMENTS ======================================== @@ -263,7 +263,7 @@ module ActiveRecord end def data_sources - select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", 'SCHEMA') + select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", "SCHEMA") end def table_exists?(table_name) @@ -282,11 +282,11 @@ module ActiveRecord sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'" sql << " AND name = #{quote(table_name)}" - select_values(sql, 'SCHEMA').any? + select_values(sql, "SCHEMA").any? end def views # :nodoc: - select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", 'SCHEMA') + select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", "SCHEMA") end def view_exists?(view_name) # :nodoc: @@ -295,7 +295,7 @@ module ActiveRecord sql = "SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'" sql << " AND name = #{quote(view_name)}" - select_values(sql, 'SCHEMA').any? + select_values(sql, "SCHEMA").any? end # Returns an array of +Column+ objects for the table specified by +table_name+. @@ -311,16 +311,16 @@ module ActiveRecord field["dflt_value"] = $1.gsub('""', '"') end - collation = field['collation'] - sql_type = field['type'] + collation = field["collation"] + sql_type = field["type"] type_metadata = fetch_type_metadata(sql_type) - new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0, table_name, nil, collation) + new_column(field["name"], field["dflt_value"], type_metadata, field["notnull"].to_i == 0, table_name, nil, collation) end end # Returns an array of indexes for the given table. def indexes(table_name, name = nil) #:nodoc: - exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row| + exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row| sql = <<-SQL SELECT sql FROM sqlite_master @@ -330,22 +330,22 @@ module ActiveRecord FROM sqlite_temp_master WHERE name=#{quote(row['name'])} AND type='index' SQL - index_sql = exec_query(sql).first['sql'] + index_sql = exec_query(sql).first["sql"] match = /\sWHERE\s+(.+)$/i.match(index_sql) where = match[1] if match IndexDefinition.new( table_name, - row['name'], - row['unique'] != 0, + row["name"], + row["unique"] != 0, exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col| - col['name'] + col["name"] }, nil, nil, where) end end def primary_keys(table_name) # :nodoc: - pks = table_structure(table_name).select { |f| f['pk'] > 0 } - pks.sort_by { |f| f['pk'] }.map { |f| f['name'] } + pks = table_structure(table_name).select { |f| f["pk"] > 0 } + pks.sort_by { |f| f["pk"] }.map { |f| f["name"] } end def remove_index(table_name, options = {}) #:nodoc: @@ -418,25 +418,25 @@ module ActiveRecord def rename_column(table_name, column_name, new_column_name) #:nodoc: column = column_for(table_name, column_name) - alter_table(table_name, rename: {column.name => new_column_name.to_s}) + alter_table(table_name, rename: { column.name => new_column_name.to_s }) rename_column_indexes(table_name, column.name, new_column_name) end protected def table_structure(table_name) - structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA') + structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA") raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? table_structure_with_collation(table_name, structure) end def alter_table(table_name, options = {}) #:nodoc: altered_table_name = "a#{table_name}" - caller = lambda {|definition| yield definition if block_given?} + caller = lambda { |definition| yield definition if block_given? } transaction do move_table(table_name, altered_table_name, - options.merge(:temporary => true)) + options.merge(temporary: true)) move_table(altered_table_name, table_name, &caller) end end @@ -460,9 +460,9 @@ module ActiveRecord next if column_name == from_primary_key @definition.column(column_name, column.type, - :limit => column.limit, :default => column.default, - :precision => column.precision, :scale => column.scale, - :null => column.null, collation: column.collation) + limit: column.limit, default: column.default, + precision: column.precision, scale: column.scale, + null: column.null, collation: column.collation) end yield @definition if block_given? end @@ -482,7 +482,7 @@ module ActiveRecord end to_column_names = columns(to).map(&:name) - columns = index.columns.map {|c| rename[c] || c }.select do |column| + columns = index.columns.map { |c| rename[c] || c }.select do |column| to_column_names.include?(column) end @@ -496,20 +496,20 @@ module ActiveRecord end def copy_table_contents(from, to, columns, rename = {}) #:nodoc: - column_mappings = Hash[columns.map {|name| [name, name]}] + column_mappings = Hash[columns.map { |name| [name, name] }] rename.each { |a| column_mappings[a.last] = a.first } from_columns = columns(from).collect(&:name) - columns = columns.find_all{|col| from_columns.include?(column_mappings[col])} + columns = columns.find_all { |col| from_columns.include?(column_mappings[col]) } from_columns_to_copy = columns.map { |col| column_mappings[col] } - quoted_columns = columns.map { |col| quote_column_name(col) } * ',' - quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ',' + quoted_columns = columns.map { |col| quote_column_name(col) } * "," + quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * "," exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns}) SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}") end def sqlite_version - @sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)')) + @sqlite_version ||= SQLite3Adapter::Version.new(select_value("select sqlite_version(*)")) end def translate_exception(exception, message) @@ -538,24 +538,24 @@ module ActiveRecord # Result will have following sample string # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, # "password_digest" varchar COLLATE "NOCASE"); - result = exec_query(sql, 'SCHEMA').first + result = exec_query(sql, "SCHEMA").first if result # Splitting with left parentheses and picking up last will return all # columns separated with comma(,). - columns_string = result["sql"].split('(').last + columns_string = result["sql"].split("(").last - columns_string.split(',').each do |column_string| + columns_string.split(",").each do |column_string| # This regex will match the column name and collation type and will save # the value in $1 and $2 respectively. - collation_hash[$1] = $2 if (COLLATE_REGEX =~ column_string) + collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string end basic_structure.map! do |column| - column_name = column['name'] + column_name = column["name"] if collation_hash.has_key? column_name - column['collation'] = collation_hash[column_name] + column["collation"] = collation_hash[column_name] end column diff --git a/activerecord/lib/active_record/connection_adapters/statement_pool.rb b/activerecord/lib/active_record/connection_adapters/statement_pool.rb index 9b0ed3e08b..273b1b0b5c 100644 --- a/activerecord/lib/active_record/connection_adapters/statement_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/statement_pool.rb @@ -47,13 +47,13 @@ module ActiveRecord private - def cache - @cache[Process.pid] - end + def cache + @cache[Process.pid] + end - def dealloc(stmt) - raise NotImplementedError - end + def dealloc(stmt) + raise NotImplementedError + end end end end |