diff options
author | Rafael Mendonça França <rafaelmfranca@gmail.com> | 2014-08-12 11:10:42 -0300 |
---|---|---|
committer | Rafael Mendonça França <rafaelmfranca@gmail.com> | 2014-08-12 11:10:42 -0300 |
commit | a2400308eab88b5eff27e05d1f7624345fb33b54 (patch) | |
tree | 38630de37ac59c50061734d9f353ebb798ba2023 /activerecord/lib/active_record/connection_adapters | |
parent | 045d7173167043389dbe8bd961425c1b1cc86d48 (diff) | |
parent | 82e28492e7c581cdeea904464a18eb11118f4ac0 (diff) | |
download | rails-a2400308eab88b5eff27e05d1f7624345fb33b54.tar.gz rails-a2400308eab88b5eff27e05d1f7624345fb33b54.tar.bz2 rails-a2400308eab88b5eff27e05d1f7624345fb33b54.zip |
Merge branch 'master' into loofah
Conflicts:
actionpack/CHANGELOG.md
actionpack/test/controller/integration_test.rb
actionview/CHANGELOG.md
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters')
22 files changed, 305 insertions, 216 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 cb75070e3a..a5fa9d6adc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -462,23 +462,44 @@ module ActiveRecord # # For example, suppose that you have 5 models, with the following hierarchy: # - # | - # +-- Book - # | | - # | +-- ScaryBook - # | +-- GoodBook - # +-- Author - # +-- BankAccount + # class Author < ActiveRecord::Base + # end # - # Suppose that Book is to connect to a separate database (i.e. one other - # than the default database). Then Book, ScaryBook and GoodBook will all use - # the same connection pool. Likewise, Author and BankAccount will use the - # same connection pool. However, the connection pool used by Author/BankAccount - # is not the same as the one used by Book/ScaryBook/GoodBook. + # class BankAccount < ActiveRecord::Base + # end # - # Normally there is only a single ConnectionHandler instance, accessible via - # ActiveRecord::Base.connection_handler. Active Record models use this to - # determine the connection pool that they should use. + # class Book < ActiveRecord::Base + # establish_connection "library_db" + # end + # + # class ScaryBook < Book + # end + # + # class GoodBook < Book + # end + # + # And a database.yml that looked like this: + # + # development: + # database: my_application + # host: localhost + # + # library_db: + # database: library + # host: some.library.org + # + # Your primary database in the development environment is "my_application" + # but the Book model connects to a separate database called "library_db" + # (this can even be a database on a different machine). + # + # Book, ScaryBook and GoodBook will all use the same connection pool to + # "library_db" while Author, BankAccount, and any other models you create + # will use the default connection pool to "my_application". + # + # The various connection pools are managed by a single instance of + # ConnectionHandler accessible via ActiveRecord::Base.connection_handler. + # All Active Record models use this handler to determine the connection pool that they + # should use. class ConnectionHandler def initialize # These caches are keyed by klass.name, NOT klass. Keying them by klass 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 e8ce00d92b..98e96099cb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -203,62 +203,30 @@ module ActiveRecord if options[:isolation] raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction" end - yield else - within_new_transaction(options) { yield } + transaction_manager.within_new_transaction(options) { yield } end rescue ActiveRecord::Rollback # rollbacks are silently swallowed end - def within_new_transaction(options = {}) #:nodoc: - transaction = begin_transaction(options) - yield - rescue Exception => error - rollback_transaction if transaction - raise - ensure - begin - commit_transaction unless error - rescue Exception - rollback_transaction - raise - end - end - - def open_transactions - @transaction.number - end + attr_reader :transaction_manager #:nodoc: - def current_transaction #:nodoc: - @transaction - end + delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction, :commit_transaction, :rollback_transaction, to: :transaction_manager def transaction_open? - @transaction.open? - end - - def begin_transaction(options = {}) #:nodoc: - @transaction = @transaction.begin(options) - end - - def commit_transaction #:nodoc: - @transaction = @transaction.commit - end - - def rollback_transaction #:nodoc: - @transaction = @transaction.rollback + current_transaction.open? end def reset_transaction #:nodoc: - @transaction = ClosedTransaction.new(self) + @transaction_manager = TransactionManager.new(self) end # Register a record with the current transaction so that its after_commit and after_rollback callbacks # can be called. def add_transaction_record(record) - @transaction.add_record(record) + current_transaction.add_record(record) end # Begins the transaction (and turns off auto-committing). 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 98e6795f10..e44ccb7d81 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -280,12 +280,22 @@ module ActiveRecord column(:updated_at, :datetime, options) end + # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided. + # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+ + # by default, the <tt>:type</tt> option can be used to specify a different type. + # + # t.references(:user) + # t.references(:user, type: "string") + # t.belongs_to(:supplier, polymorphic: true) + # + # See SchemaStatements#add_reference def references(*args) options = args.extract_options! polymorphic = options.delete(:polymorphic) index_options = options.delete(:index) + type = options.delete(:type) || :integer args.each do |col| - column("#{col}_id", :integer, options) + column("#{col}_id", type, options) column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options end @@ -500,11 +510,14 @@ module ActiveRecord end # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided. - # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. + # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+ + # by default, the <tt>:type</tt> option can be used to specify a different type. # # t.references(:user) + # t.references(:user, type: "string") # t.belongs_to(:supplier, polymorphic: true) # + # See SchemaStatements#add_reference def references(*args) options = args.extract_options! args.each do |ref_name| @@ -519,6 +532,7 @@ module ActiveRecord # t.remove_references(:user) # t.remove_belongs_to(:supplier, polymorphic: true) # + # See SchemaStatements#remove_reference def remove_references(*args) options = args.extract_options! args.each do |ref_name| 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 5814c2b711..10753defc2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -602,12 +602,18 @@ module ActiveRecord end # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided. + # The reference column is an +integer+ by default, the <tt>:type</tt> option can be used to specify + # a different type. # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable. # - # ====== Create a user_id column + # ====== Create a user_id integer column # # add_reference(:products, :user) # + # ====== Create a user_id string column + # + # add_reference(:products, :user, type: :string) + # # ====== Create a supplier_id and supplier_type columns # # add_belongs_to(:products, :supplier, polymorphic: true) @@ -619,7 +625,8 @@ module ActiveRecord def add_reference(table_name, ref_name, options = {}) polymorphic = options.delete(:polymorphic) index_options = options.delete(:index) - add_column(table_name, "#{ref_name}_id", :integer, options) + type = options.delete(:type) || :integer + add_column(table_name, "#{ref_name}_id", type, options) add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index bc4884b538..4a7f2aaca8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -1,20 +1,7 @@ module ActiveRecord module ConnectionAdapters - class Transaction #:nodoc: - attr_reader :connection - - def initialize(connection) - @connection = connection - @state = TransactionState.new - end - - def state - @state - end - end - class TransactionState - attr_accessor :parent + attr_reader :parent VALID_STATES = Set.new([:committed, :rolledback, nil]) @@ -43,82 +30,24 @@ module ActiveRecord end end - class ClosedTransaction < Transaction #:nodoc: - def number - 0 - end - - def begin(options = {}) - RealTransaction.new(connection, self, options) - end - - def closed? - true - end - - def open? - false - end - - def joinable? - false - end - - # This is a noop when there are no open transactions - def add_record(record) - end + class NullTransaction #:nodoc: + def initialize; end + def closed?; true; end + def open?; false; end + def joinable?; false; end + def add_record(record); end end - class OpenTransaction < Transaction #:nodoc: - attr_reader :parent, :records - attr_writer :joinable - - def initialize(connection, parent, options = {}) - super connection - - @parent = parent - @records = [] - @finishing = false - @joinable = options.fetch(:joinable, true) - end - - # This state is necessary so that we correctly handle stuff that might - # happen in a commit/rollback. But it's kinda distasteful. Maybe we can - # find a better way to structure it in the future. - def finishing? - @finishing - end - - def joinable? - @joinable && !finishing? - end - - def number - if finishing? - parent.number - else - parent.number + 1 - end - end - - def begin(options = {}) - if finishing? - parent.begin - else - SavepointTransaction.new(connection, self, options) - end - end + class Transaction #:nodoc: - def rollback - @finishing = true - perform_rollback - parent - end + attr_reader :connection, :state, :records, :savepoint_name + attr_writer :joinable - def commit - @finishing = true - perform_commit - parent + def initialize(connection, options) + @connection = connection + @state = TransactionState.new + @records = [] + @joinable = options.fetch(:joinable, true) end def add_record(record) @@ -129,19 +58,25 @@ module ActiveRecord end end - def rollback_records + def rollback @state.set_state(:rolledback) + end + + def rollback_records records.uniq.each do |record| begin - record.rolledback!(parent.closed?) + record.rolledback! full_rollback? rescue => e record.logger.error(e) if record.respond_to?(:logger) && record.logger end end end - def commit_records + def commit @state.set_state(:committed) + end + + def commit_records records.uniq.each do |record| begin record.committed! @@ -151,19 +86,40 @@ module ActiveRecord end end - def closed? - false + def full_rollback?; true; end + def joinable?; @joinable; end + def closed?; false; end + def open?; !closed?; end + end + + class SavepointTransaction < Transaction + + def initialize(connection, savepoint_name, options) + super(connection, options) + if options[:isolation] + raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction" + end + connection.create_savepoint(@savepoint_name = savepoint_name) + end + + def rollback + super + connection.rollback_to_savepoint(savepoint_name) + rollback_records end - def open? - true + def commit + super + connection.release_savepoint(savepoint_name) end + + def full_rollback?; false; end end - class RealTransaction < OpenTransaction #:nodoc: - def initialize(connection, parent, options = {}) - super + class RealTransaction < Transaction + def initialize(connection, options) + super if options[:isolation] connection.begin_isolated_db_transaction(options[:isolation]) else @@ -171,37 +127,71 @@ module ActiveRecord end end - def perform_rollback + def rollback + super connection.rollback_db_transaction rollback_records end - def perform_commit + def commit + super connection.commit_db_transaction commit_records end end - class SavepointTransaction < OpenTransaction #:nodoc: - def initialize(connection, parent, options = {}) - if options[:isolation] - raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction" - end + class TransactionManager #:nodoc: + def initialize(connection) + @stack = [] + @connection = connection + end - super - connection.create_savepoint + def begin_transaction(options = {}) + transaction = + if @stack.empty? + RealTransaction.new(@connection, options) + else + SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options) + end + @stack.push(transaction) + transaction end - def perform_rollback - connection.rollback_to_savepoint - rollback_records + def commit_transaction + @stack.pop.commit end - def perform_commit - @state.set_state(:committed) - @state.parent = parent.state - connection.release_savepoint + def rollback_transaction + @stack.pop.rollback + end + + def within_new_transaction(options = {}) + transaction = begin_transaction options + yield + rescue Exception => error + transaction.rollback if transaction + raise + ensure + begin + transaction.commit unless error + rescue Exception + transaction.rollback + raise + ensure + @stack.pop if transaction + end end + + def open_transactions + @stack.size + end + + def current_transaction + @stack.last || NULL_TRANSACTION + end + + private + NULL_TRANSACTION = NullTransaction.new 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 f8c054eb69..a1b6671664 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -45,7 +45,8 @@ module ActiveRecord end autoload_at 'active_record/connection_adapters/abstract/transaction' do - autoload :ClosedTransaction + autoload :TransactionManager + autoload :NullTransaction autoload :RealTransaction autoload :SavepointTransaction autoload :TransactionState @@ -357,7 +358,7 @@ module ActiveRecord end def current_savepoint_name - "active_record_#{open_transactions}" + current_transaction.savepoint_name end # Check the connection back in to the connection pool 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 ccb957d2c8..e5417a9556 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -473,7 +473,7 @@ module ActiveRecord end def rename_index(table_name, old_name, new_name) - if (version[0] == 5 && version[1] >= 7) || version[0] >= 6 + if supports_rename_index? execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}" else super @@ -774,10 +774,22 @@ module ActiveRecord private + def version + @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i } + end + + def mariadb? + full_version =~ /mariadb/i + end + def supports_views? version[0] >= 5 end + def supports_rename_index? + mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6 + end + def configure_connection variables = @config.fetch(:variables, {}).stringify_keys diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 1f1e2c46f4..5f9cc6edd0 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -16,7 +16,7 @@ module ActiveRecord attr_reader :name, :cast_type, :null, :sql_type, :default, :default_function delegate :type, :precision, :scale, :limit, :klass, :accessor, - :text?, :number?, :binary?, :changed?, + :number?, :binary?, :changed?, :type_cast_from_user, :type_cast_from_database, :type_cast_for_database, :type_cast_for_schema, to: :cast_type diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 2fcb085ab2..d28a54b8f9 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -32,7 +32,7 @@ module ActiveRecord # } def initialize(url) raise "Database URL cannot be empty" if url.blank? - @uri = URI.parse(url) + @uri = uri_parser.parse(url) @adapter = @uri.scheme.gsub('-', '_') @adapter = "postgresql" if @adapter == "postgres" @@ -160,7 +160,7 @@ module ActiveRecord # config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } } # spec = Resolver.new(config).spec(:production) # spec.adapter_method - # # => "sqlite3" + # # => "sqlite3_connection" # spec.config # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } # @@ -250,7 +250,7 @@ module ActiveRecord # 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_string_connection(spec.delete("url")) + connection_hash = resolve_url_connection(spec.delete("url")) spec.merge!(connection_hash) end spec diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 0a14cdfe89..39d52e6349 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -269,8 +269,8 @@ module ActiveRecord super end - def version - @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i } + def full_version + @full_version ||= @connection.info[:version] end def set_field_encoding field_name diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index ad07a46e51..a03bc28744 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -470,9 +470,9 @@ module ActiveRecord rows end - # Returns the version of the connected MySQL server. - def version - @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i } + # Returns the full version of the connected MySQL server. + def full_version + @full_version ||= @connection.server_info end def set_field_encoding field_name diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index d05ce61330..d28a2b4fa0 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -14,6 +14,7 @@ require 'active_record/connection_adapters/postgresql/oid/hstore' require 'active_record/connection_adapters/postgresql/oid/inet' require 'active_record/connection_adapters/postgresql/oid/integer' 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/range' 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 243ecd13cf..1dbb40ca1d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb @@ -19,6 +19,32 @@ module ActiveRecord value end end + + def type_cast_for_database(value) + Data.new(super) if value + end + + class Data + def initialize(value) + @value = value + end + + def to_s + value + end + + def binary? + /\A[01]*\Z/ === value + end + + def hex? + /\A[0-9A-F]*\Z/i === value + end + + protected + + attr_reader :value + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb new file mode 100644 index 0000000000..34ed32ad35 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb @@ -0,0 +1,23 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Jsonb < Json # :nodoc: + def type + :jsonb + end + + def changed_in_place?(raw_old_value, new_value) + # Postgres does not preserve insignificant whitespaces when + # roundtripping jsonb columns. This causes some false positives for + # the comparison here. Therefore, we need to parse and re-dump the + # raw value here to ensure the insignificant whitespaces are + # consitent with our encoder's output. + raw_old_value = type_cast_for_database(type_cast_from_database(raw_old_value)) + super(raw_old_value, new_value) + end + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb index 89728b0fe2..dd97393eac 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb @@ -3,12 +3,21 @@ module ActiveRecord module PostgreSQL module OID # :nodoc: class Uuid < Type::Value # :nodoc: + RFC_4122 = %r{\A\{?[a-fA-F0-9]{4}-? + [a-fA-F0-9]{4}-? + [a-fA-F0-9]{4}-? + [1-5][a-fA-F0-9]{3}-? + [8-Bab][a-fA-F0-9]{3}-? + [a-fA-F0-9]{4}-? + [a-fA-F0-9]{4}-? + [a-fA-F0-9]{4}-?\}?\z}x + def type :uuid end def type_cast(value) - value.presence + value.to_s[RFC_4122, 0] end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb index 7323f12763..334af7c598 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb @@ -7,10 +7,6 @@ module ActiveRecord :xml end - def text? - false - end - def type_cast_for_database(value) return unless value Data.new(super) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 5359c5b666..cf5c8d288e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -18,8 +18,6 @@ module ActiveRecord def quote(value, column = nil) #:nodoc: return super unless column - sql_type = type_to_sql(column.type, column.limit, column.precision, column.scale) - case value when Float if value.infinite? || value.nan? @@ -27,16 +25,6 @@ module ActiveRecord else super end - when String - case sql_type - when /^bit/ - case value - when /\A[01]*\Z/ then "B'#{value}'" # Bit-string notation - when /\A[0-9A-F]*\Z/i then "X'#{value}'" # Hexadecimal notation - end - else - super - end else super end @@ -100,6 +88,12 @@ module ActiveRecord "'#{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 else super end @@ -112,7 +106,7 @@ module ActiveRecord # 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 + when OID::Xml::Data, OID::Bit::Data value.to_s else super 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 0867e5ef54..83554bbf74 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -64,6 +64,10 @@ module ActiveRecord column(name, :json, options) end + def jsonb(name, options = {}) + column(name, :jsonb, options) + end + def citext(name, options = {}) column(name, :citext, options) 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 e09672d239..7042817672 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -423,8 +423,16 @@ module ActiveRecord def change_column_default(table_name, column_name, default) clear_cache! column = column_for(table_name, column_name) + return unless column - execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote_default_value(default, column)}" if column + alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s" + if default.nil? + # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will + # cast the default to the columns type, which leaves us with a default like "default NULL::character varying". + execute alter_column_query % "DROP DEFAULT" + else + execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}" + end end def change_column_null(table_name, column_name, null, default = nil) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index f660fc41cf..eede374678 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -451,6 +451,7 @@ module ActiveRecord 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 diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index 4d8afcf16a..a10ce330c7 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -1,4 +1,3 @@ - module ActiveRecord module ConnectionAdapters class SchemaCache @@ -20,6 +19,7 @@ module ActiveRecord # A cached lookup for table existence. def table_exists?(name) + prepare_tables if @tables.empty? return @tables[name] if @tables.key? name @tables[name] = connection.table_exists?(name) @@ -83,6 +83,12 @@ module ActiveRecord def marshal_load(array) @version, @columns, @columns_hash, @primary_keys, @tables = array end + + private + + def prepare_tables + connection.tables.each { |table| @tables[table] = true } + 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 32fb51d31a..faf1cdc686 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -50,6 +50,16 @@ module ActiveRecord end end + class SQLite3String < Type::String # :nodoc: + def type_cast_for_database(value) + if value.is_a?(::String) && value.encoding == Encoding::ASCII_8BIT + value.encode(Encoding::UTF_8) + else + super + end + end + end + # The SQLite3 adapter works SQLite 3.6.16 or newer # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3). # @@ -220,13 +230,23 @@ module ActiveRecord # QUOTING ================================================== def _quote(value) # :nodoc: - if value.is_a?(Type::Binary::Data) + case value + when Type::Binary::Data "x'#{value.hex}'" else super end end + def _type_cast(value) # :nodoc: + case value + when BigDecimal + value.to_f + else + super + end + end + def quote_string(s) #:nodoc: @connection.class.quote(s) end @@ -249,19 +269,6 @@ module ActiveRecord end end - def type_cast(value, column) # :nodoc: - return value.to_f if BigDecimal === value - return super unless String === value - return super unless column && value - - value = super - if column.type == :string && value.encoding == Encoding::ASCII_8BIT - logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger - value = value.encode Encoding::UTF_8 - end - value - end - # DATABASE STATEMENTS ====================================== def explain(arel, binds = []) @@ -378,7 +385,7 @@ module ActiveRecord table_name && tables(nil, table_name).any? end - # Returns an array of +SQLite3Column+ objects for the table specified by +table_name+. + # Returns an array of +Column+ objects for the table specified by +table_name+. def columns(table_name) #:nodoc: table_structure(table_name).map do |field| case field["dflt_value"] @@ -503,6 +510,7 @@ module ActiveRecord def initialize_type_map(m) super m.register_type(/binary/i, SQLite3Binary.new) + register_class_with_limit m, %r(char)i, SQLite3String end def select(sql, name = nil, binds = []) #:nodoc: |