diff options
41 files changed, 309 insertions, 135 deletions
@@ -118,7 +118,7 @@ platforms :ruby, :mswin, :mswin64, :mingw, :x64_mingw do gem "racc", ">=1.4.6", require: false # Active Record. - gem "sqlite3", "~> 1.3", ">= 1.3.6" + gem "sqlite3", "~> 1.4" group :db do gem "pg", ">= 0.18.0" diff --git a/Gemfile.lock b/Gemfile.lock index 7617942267..b6b1aaf92d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -278,6 +278,7 @@ GEM image_processing (1.7.1) mini_magick (~> 4.0) ruby-vips (>= 2.0.13, < 3) + jar-dependencies (0.4.0) jaro_winkler (1.5.2) jaro_winkler (1.5.2-java) jdbc-mysql (5.1.46) @@ -468,9 +469,7 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sqlite3 (1.3.13) - sqlite3 (1.3.13-x64-mingw32) - sqlite3 (1.3.13-x86-mingw32) + sqlite3 (1.4.0) stackprof (0.2.12) sucker_punch (2.1.1) concurrent-ruby (~> 1.0) @@ -582,7 +581,7 @@ DEPENDENCIES sidekiq sneakers sprockets-export - sqlite3 (~> 1.3, >= 1.3.6) + sqlite3 (~> 1.4) stackprof sucker_punch turbolinks (~> 5) diff --git a/actionpack/test/controller/new_base/render_file_test.rb b/actionpack/test/controller/new_base/render_file_test.rb index 82325c5bb2..01d0223519 100644 --- a/actionpack/test/controller/new_base/render_file_test.rb +++ b/actionpack/test/controller/new_base/render_file_test.rb @@ -17,12 +17,12 @@ module RenderFile def relative_path @secret = "in the sauce" - render file: "../../fixtures/test/render_file_with_ivar" + render file: "../actionpack/test/fixtures/test/render_file_with_ivar" end def relative_path_with_dot @secret = "in the sauce" - render file: "../../fixtures/test/dot.directory/render_file_with_ivar" + render file: "../actionpack/test/fixtures/test/dot.directory/render_file_with_ivar" end def pathname diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 6d198ca42f..8bb6617eaa 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -323,11 +323,12 @@ class ExpiresInRenderTest < ActionController::TestCase end def test_dynamic_render_with_file - # This is extremely bad, but should be possible to do. assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__)) - response = assert_deprecated { get :dynamic_render_with_file, params: { id: '../\\../test/abstract_unit.rb' } } - assert_equal File.read(File.expand_path("../../test/abstract_unit.rb", __dir__)), - response.body + assert_deprecated do + assert_raises ActionView::MissingTemplate do + get :dynamic_render_with_file, params: { id: '../\\../test/abstract_unit.rb' } + end + end end def test_dynamic_render_with_absolute_path @@ -351,9 +352,11 @@ class ExpiresInRenderTest < ActionController::TestCase def test_permitted_dynamic_render_file_hash assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__)) - response = assert_deprecated { get :dynamic_render_permit, params: { id: { file: '../\\../test/abstract_unit.rb' } } } - assert_equal File.read(File.expand_path("../../test/abstract_unit.rb", __dir__)), - response.body + assert_deprecated do + assert_raises ActionView::MissingTemplate do + get :dynamic_render_permit, params: { id: { file: '../\\../test/abstract_unit.rb' } } + end + end end def test_dynamic_render_file_hash diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index fd3d025cbf..b9a8cc129c 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -130,9 +130,8 @@ module ActionView end alias :find_template :find - def find_file(name, prefixes = [], partial = false, keys = [], options = {}) - @view_paths.find_file(*args_for_lookup(name, prefixes, partial, keys, options)) - end + alias :find_file :find + deprecate :find_file def find_all(name, prefixes = [], partial = false, keys = [], options = {}) @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options)) diff --git a/actionview/lib/action_view/path_set.rb b/actionview/lib/action_view/path_set.rb index 691b53e2da..d54749d8e3 100644 --- a/actionview/lib/action_view/path_set.rb +++ b/actionview/lib/action_view/path_set.rb @@ -48,12 +48,11 @@ module ActionView #:nodoc: find_all(*args).first || raise(MissingTemplate.new(self, *args)) end - def find_file(path, prefixes = [], *args) - _find_all(path, prefixes, args, true).first || raise(MissingTemplate.new(self, path, prefixes, *args)) - end + alias :find_file :find + deprecate :find_file def find_all(path, prefixes = [], *args) - _find_all path, prefixes, args, false + _find_all path, prefixes, args end def exists?(path, prefixes, *args) @@ -71,15 +70,11 @@ module ActionView #:nodoc: private - def _find_all(path, prefixes, args, outside_app) + def _find_all(path, prefixes, args) prefixes = [prefixes] if String === prefixes prefixes.each do |prefix| paths.each do |resolver| - if outside_app - templates = resolver.find_all_anywhere(path, prefix, *args) - else - templates = resolver.find_all(path, prefix, *args) - end + templates = resolver.find_all(path, prefix, *args) return templates unless templates.empty? end end diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index 1faef9ca81..4aab3f913f 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -30,7 +30,7 @@ module ActionView Template::RawFile.new(options[:file]) else ActiveSupport::Deprecation.warn "render file: should be given the absolute path to a file" - @lookup_context.with_fallbacks.find_file(options[:file], nil, false, keys, @details) + @lookup_context.with_fallbacks.find_template(options[:file], nil, false, keys, @details) end elsif options.key?(:inline) handler = Template.handler_for_extension(options[:type] || "erb") diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 095e6cc3a1..d5cb3c823a 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -118,17 +118,12 @@ module ActionView locals = locals.map(&:to_s).sort!.freeze cached(key, [name, prefix, partial], details, locals) do - find_templates(name, prefix, partial, details, false, locals) + find_templates(name, prefix, partial, details, locals) end end - def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = []) - locals = locals.map(&:to_s).sort!.freeze - - cached(key, [name, prefix, partial], details, locals) do - find_templates(name, prefix, partial, details, true, locals) - end - end + alias :find_all_anywhere :find_all + deprecate :find_all_anywhere def find_all_with_query(query) # :nodoc: @cache.cache_query(query) { find_template_paths(File.join(@path, query)) } @@ -141,8 +136,8 @@ module ActionView # This is what child classes implement. No defaults are needed # because Resolver guarantees that the arguments are present and # normalized. - def find_templates(name, prefix, partial, details, outside_app_allowed = false, locals = []) - raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed = false, locals = []) method" + def find_templates(name, prefix, partial, details, locals = []) + raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method" end # Handles templates caching. If a key is given and caching is on @@ -179,14 +174,14 @@ module ActionView private - def find_templates(name, prefix, partial, details, outside_app_allowed = false, locals) + def find_templates(name, prefix, partial, details, locals) path = Path.build(name, prefix, partial) - query(path, details, details[:formats], outside_app_allowed, locals) + query(path, details, details[:formats], locals) end - def query(path, details, formats, outside_app_allowed, locals) + def query(path, details, formats, locals) template_paths = find_template_paths_from_details(path, details) - template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed + template_paths = reject_files_external_to_app(template_paths) template_paths.map do |template| build_template(template, path.virtual, locals) @@ -360,6 +355,8 @@ module ActionView # The same as FileSystemResolver but does not allow templates to store # a virtual path since it is invalid for such resolvers. class FallbackFileSystemResolver < FileSystemResolver #:nodoc: + private_class_method :new + def self.instances [new(""), new("/")] end @@ -367,5 +364,9 @@ module ActionView def build_template(template, virtual_path, locals) super(template, nil, locals) end + + def reject_files_external_to_app(files) + files + end end end diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb index a16dc0096e..11ae9f731c 100644 --- a/actionview/lib/action_view/testing/resolvers.rb +++ b/actionview/lib/action_view/testing/resolvers.rb @@ -23,7 +23,7 @@ module ActionView #:nodoc: private - def query(path, exts, _, _, locals) + def query(path, exts, _, locals) query = +"" EXTENSIONS.each_key do |ext| query << "(" << exts[ext].map { |e| e && Regexp.escape(".#{e}") }.join("|") << "|)" @@ -47,7 +47,7 @@ module ActionView #:nodoc: end class NullResolver < PathResolver - def query(path, exts, _, _, locals) + def query(path, exts, _, locals) handler, format, variant = extract_handler_and_format_and_variant(path) [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: format, variant: variant, locals: locals)] end diff --git a/actionview/test/template/fallback_file_system_resolver_test.rb b/actionview/test/template/fallback_file_system_resolver_test.rb index 304cdb8a03..fa770f3a15 100644 --- a/actionview/test/template/fallback_file_system_resolver_test.rb +++ b/actionview/test/template/fallback_file_system_resolver_test.rb @@ -4,7 +4,7 @@ require "abstract_unit" class FallbackFileSystemResolverTest < ActiveSupport::TestCase def setup - @root_resolver = ActionView::FallbackFileSystemResolver.new("/") + @root_resolver = ActionView::FallbackFileSystemResolver.send(:new, "/") end def test_should_have_no_virtual_path diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb index 3e357fe1a7..72e1f50fdf 100644 --- a/actionview/test/template/lookup_context_test.rb +++ b/actionview/test/template/lookup_context_test.rb @@ -143,16 +143,16 @@ class LookupContextTest < ActiveSupport::TestCase assert_deprecated do @lookup_context.with_fallbacks do assert_equal 3, @lookup_context.view_paths.size - assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("") - assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("/") + assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.instances[0] + assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.instances[1] end end @lookup_context = @lookup_context.with_fallbacks assert_equal 3, @lookup_context.view_paths.size - assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("") - assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("/") + assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.instances[0] + assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.instances[1] end test "add fallbacks just once in nested fallbacks calls" do 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 0ded1a5318..68498b5dc5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -810,6 +810,7 @@ module ActiveRecord def new_connection Base.send(spec.adapter_method, spec.config).tap do |conn| conn.schema_cache = schema_cache.dup if schema_cache + conn.check_version end end 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 93b1c4e632..a7753e3e9c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -7,7 +7,8 @@ module ActiveRecord module QueryCache class << self def included(base) #:nodoc: - dirties_query_cache base, :insert, :update, :delete, :rollback_to_savepoint, :rollback_db_transaction + dirties_query_cache base, :insert, :update, :delete, :truncate, :truncate_tables, + :rollback_to_savepoint, :rollback_db_transaction base.set_callback :checkout, :after, :configure_query_cache! base.set_callback :checkin, :after, :disable_query_cache! diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 7aad306d50..200184c2f9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -133,8 +133,6 @@ module ActiveRecord @advisory_locks_enabled = self.class.type_cast_config_to_boolean( config.fetch(:advisory_locks, true) ) - - check_version end def replica? @@ -575,9 +573,17 @@ module ActiveRecord "INSERT #{insert.into} #{insert.values_list}" end + def get_database_version # :nodoc: + end + + def database_version # :nodoc: + schema_cache.database_version + end + + def check_version # :nodoc: + end + private - def check_version - end def type_map @type_map ||= Type::TypeMap.new.tap do |mapping| 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 8ca2cfa9ed..f5da19f0f6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -55,8 +55,8 @@ module ActiveRecord super(connection, logger, config) end - def version #:nodoc: - @version ||= Version.new(version_string) + def get_database_version #:nodoc: + Version.new(version_string) end def mariadb? # :nodoc: @@ -68,11 +68,11 @@ module ActiveRecord end def supports_index_sort_order? - !mariadb? && version >= "8.0.1" + !mariadb? && database_version >= "8.0.1" end def supports_expression_index? - !mariadb? && version >= "8.0.13" + !mariadb? && database_version >= "8.0.13" end def supports_transaction_isolation? @@ -96,16 +96,16 @@ module ActiveRecord end def supports_datetime_with_precision? - mariadb? || version >= "5.6.4" + mariadb? || database_version >= "5.6.4" end def supports_virtual_columns? - mariadb? || version >= "5.7.5" + mariadb? || database_version >= "5.7.5" end # See https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html for more details. def supports_optimizer_hints? - !mariadb? && version >= "5.7.7" + !mariadb? && database_version >= "5.7.7" end def supports_advisory_locks? @@ -526,12 +526,13 @@ module ActiveRecord sql end - private - def check_version - if version < "5.5.8" - raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.5.8." - end + def check_version # :nodoc: + if database_version < "5.5.8" + raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8." end + end + + private def initialize_type_map(m = type_map) super @@ -702,7 +703,7 @@ module ActiveRecord end def supports_rename_index? - mariadb? ? false : version >= "5.7.6" + mariadb? ? false : database_version >= "5.7.6" end def configure_connection 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 57518b02fa..a37557361a 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -64,7 +64,7 @@ module ActiveRecord end def extract_expression_for_virtual_column(column) - if @connection.mariadb? && @connection.version < "10.2.5" + if @connection.mariadb? && @connection.database_version < "10.2.5" create_table_info = @connection.send(:create_table_info, column.table_name) column_name = @connection.quote_column_name(column.name) if %r/#{column_name} #{Regexp.quote(column.sql_type)}(?: COLLATE \w+)? AS \((?<expression>.+?)\) #{column.extra}/ =~ create_table_info diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb index 4018f0815c..a1f90a0642 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb @@ -126,9 +126,9 @@ module ActiveRecord def row_format_dynamic_by_default? if mariadb? - version >= "10.2.2" + database_version >= "10.2.2" else - version >= "5.7.9" + database_version >= "5.7.9" 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 9bdaa00336..0dc880c731 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -43,7 +43,7 @@ module ActiveRecord end def supports_json? - !mariadb? && version >= "5.7.8" + !mariadb? && database_version >= "5.7.8" end def supports_comments? 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 a38c1325c0..41d1a8e4ab 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -287,7 +287,7 @@ module ActiveRecord quoted_sequence = quote_table_name(sequence) max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA") if max_pk.nil? - if postgresql_version >= 100000 + if database_version >= 100000 minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA") else minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA") diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 29f764e8f4..0ed4e61d18 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -201,7 +201,7 @@ module ActiveRecord end def supports_insert_on_conflict? - postgresql_version >= 90500 + database_version >= 90500 end alias supports_insert_on_duplicate_skip? supports_insert_on_conflict? alias supports_insert_on_duplicate_update? supports_insert_on_conflict? @@ -344,7 +344,7 @@ module ActiveRecord end def supports_pgcrypto_uuid? - postgresql_version >= 90400 + database_version >= 90400 end def supports_optimizer_hints? @@ -424,7 +424,7 @@ module ActiveRecord } # Returns the version of the connected PostgreSQL server. - def postgresql_version + def get_database_version @connection.server_version end @@ -446,12 +446,13 @@ module ActiveRecord sql end - private - def check_version - if postgresql_version < 90300 - raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.3." - end + def check_version # :nodoc: + if database_version < 90300 + raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3." end + end + + private # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html VALUE_LIMIT_VIOLATION = "22001" diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index 07453b4403..dbfe1e4a34 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -26,21 +26,23 @@ module ActiveRecord end def encode_with(coder) - coder["columns"] = @columns - coder["columns_hash"] = @columns_hash - coder["primary_keys"] = @primary_keys - coder["data_sources"] = @data_sources - coder["indexes"] = @indexes - coder["version"] = connection.migration_context.current_version + coder["columns"] = @columns + coder["columns_hash"] = @columns_hash + coder["primary_keys"] = @primary_keys + coder["data_sources"] = @data_sources + coder["indexes"] = @indexes + coder["version"] = connection.migration_context.current_version + coder["database_version"] = database_version end def init_with(coder) - @columns = coder["columns"] - @columns_hash = coder["columns_hash"] - @primary_keys = coder["primary_keys"] - @data_sources = coder["data_sources"] - @indexes = coder["indexes"] || {} - @version = coder["version"] + @columns = coder["columns"] + @columns_hash = coder["columns_hash"] + @primary_keys = coder["primary_keys"] + @data_sources = coder["data_sources"] + @indexes = coder["indexes"] || {} + @version = coder["version"] + @database_version = coder["database_version"] end def primary_keys(table_name) @@ -91,6 +93,10 @@ module ActiveRecord @indexes[table_name] ||= connection.indexes(table_name) end + def database_version # :nodoc: + @database_version ||= connection.get_database_version + end + # Clears out internal caches def clear! @columns.clear @@ -99,6 +105,7 @@ module ActiveRecord @data_sources.clear @indexes.clear @version = nil + @database_version = nil end def size @@ -117,11 +124,11 @@ module ActiveRecord def marshal_dump # if we get current version during initialization, it happens stack over flow. @version = connection.migration_context.current_version - [@version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes] + [@version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, database_version] end def marshal_load(array) - @version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes = array + @version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array @indexes = @indexes || {} end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb index 84dcae49b9..ffa75172b5 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb @@ -14,7 +14,7 @@ module ActiveRecord log(sql, name) do ActiveSupport::Dependencies.interlock.permit_concurrent_loads do - @connection.execute_batch(sql) + @connection.execute_batch2(sql) 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 ff23a525b9..1801924c09 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -10,7 +10,7 @@ require "active_record/connection_adapters/sqlite3/schema_definitions" require "active_record/connection_adapters/sqlite3/schema_dumper" require "active_record/connection_adapters/sqlite3/schema_statements" -gem "sqlite3", "~> 1.3", ">= 1.3.6" +gem "sqlite3", "~> 1.4" require "sqlite3" module ActiveRecord @@ -111,7 +111,7 @@ module ActiveRecord end def supports_expression_index? - sqlite_version >= "3.9.0" + database_version >= "3.9.0" end def requires_reloading? @@ -135,7 +135,7 @@ module ActiveRecord end def supports_insert_on_conflict? - sqlite_version >= "3.24.0" + database_version >= "3.24.0" end alias supports_insert_on_duplicate_skip? supports_insert_on_conflict? alias supports_insert_on_duplicate_update? supports_insert_on_conflict? @@ -397,6 +397,16 @@ module ActiveRecord sql end + def get_database_version # :nodoc: + SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)")) + end + + def check_version # :nodoc: + if database_version < "3.8.0" + raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.8." + end + end + private # See https://www.sqlite.org/limits.html, # the default value is 999 when not configured. @@ -404,12 +414,6 @@ module ActiveRecord 999 end - def check_version - if sqlite_version < "3.8.0" - raise "Your version of SQLite (#{sqlite_version}) is too old. Active Record supports SQLite >= 3.8." - end - end - def initialize_type_map(m = type_map) super register_class_with_limit m, %r(int)i, SQLite3Integer @@ -527,10 +531,6 @@ module ActiveRecord SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}") end - def sqlite_version - @sqlite_version ||= SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)")) - end - def translate_exception(exception, message:, sql:, binds:) case exception.message # SQLite 3.8.2 returns a newly formatted error message: diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb index 88b0c828ae..e6166581f1 100644 --- a/activerecord/lib/active_record/internal_metadata.rb +++ b/activerecord/lib/active_record/internal_metadata.rb @@ -17,7 +17,7 @@ module ActiveRecord end def table_name - "#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}" + "#{table_name_prefix}#{internal_metadata_table_name}#{table_name_suffix}" end def []=(key, value) @@ -44,6 +44,10 @@ module ActiveRecord end end end + + def drop_table + connection.drop_table table_name, if_exists: true + end end end end diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index 1fca1a18f6..74547de862 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -19,7 +19,7 @@ module ActiveRecord end def table_name - "#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}" + "#{table_name_prefix}#{schema_migrations_table_name}#{table_name_suffix}" end def table_exists? diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 155d2b0b98..7285c15477 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -186,8 +186,8 @@ module ActiveRecord ActiveRecord::Base.connected_to(database: { truncation: configuration }) do table_names = ActiveRecord::Base.connection.tables table_names -= [ - ActiveRecord::Base.schema_migrations_table_name, - ActiveRecord::Base.internal_metadata_table_name + SchemaMigration.table_name, + InternalMetadata.table_name ] ActiveRecord::Base.connection.truncate_tables(*table_names) diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 2b20d842e8..ba04859bf0 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -485,23 +485,51 @@ module ActiveRecord end def test_truncate - assert_operator @connection.query_value("SELECT COUNT(*) FROM posts"), :>, 0 + assert_operator Post.count, :>, 0 @connection.truncate("posts") - assert_equal 0, @connection.query_value("SELECT COUNT(*) FROM posts") + assert_equal 0, Post.count + end + + def test_truncate_with_query_cache + @connection.enable_query_cache! + + assert_operator Post.count, :>, 0 + + @connection.truncate("posts") + + assert_equal 0, Post.count + ensure + @connection.disable_query_cache! end def test_truncate_tables - assert_operator @connection.query_value("SELECT COUNT(*) FROM posts"), :>, 0 - assert_operator @connection.query_value("SELECT COUNT(*) FROM authors"), :>, 0 - assert_operator @connection.query_value("SELECT COUNT(*) FROM author_addresses"), :>, 0 + assert_operator Post.count, :>, 0 + assert_operator Author.count, :>, 0 + assert_operator AuthorAddress.count, :>, 0 @connection.truncate_tables("author_addresses", "authors", "posts") - assert_equal 0, @connection.query_value("SELECT COUNT(*) FROM posts") - assert_equal 0, @connection.query_value("SELECT COUNT(*) FROM authors") - assert_equal 0, @connection.query_value("SELECT COUNT(*) FROM author_addresses") + assert_equal 0, Post.count + assert_equal 0, Author.count + assert_equal 0, AuthorAddress.count + end + + def test_truncate_tables_with_query_cache + @connection.enable_query_cache! + + assert_operator Post.count, :>, 0 + assert_operator Author.count, :>, 0 + assert_operator AuthorAddress.count, :>, 0 + + @connection.truncate_tables("author_addresses", "authors", "posts") + + assert_equal 0, Post.count + assert_equal 0, Author.count + assert_equal 0, AuthorAddress.count + ensure + @connection.disable_query_cache! end # test resetting sequences in odd tables in PostgreSQL diff --git a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb index 00a075e063..cbe55f1d53 100644 --- a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb +++ b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb @@ -46,10 +46,7 @@ class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase def stub_version(full_version_string) @connection.stub(:full_version, full_version_string) do - @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version) yield end - ensure - @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version) end end diff --git a/activerecord/test/cases/adapters/mysql2/sp_test.rb b/activerecord/test/cases/adapters/mysql2/sp_test.rb index 7b6dce71e9..626ef59570 100644 --- a/activerecord/test/cases/adapters/mysql2/sp_test.rb +++ b/activerecord/test/cases/adapters/mysql2/sp_test.rb @@ -9,7 +9,7 @@ class Mysql2StoredProcedureTest < ActiveRecord::Mysql2TestCase def setup @connection = ActiveRecord::Base.connection - unless ActiveRecord::Base.connection.version >= "5.6.0" + unless ActiveRecord::Base.connection.database_version >= "5.6.0" skip("no stored procedure support") end end diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb index 8c6f046553..14c262f4ce 100644 --- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb +++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb @@ -247,7 +247,7 @@ class PostgreSQLGeometricLineTest < ActiveRecord::PostgreSQLTestCase class PostgresqlLine < ActiveRecord::Base; end setup do - unless ActiveRecord::Base.connection.send(:postgresql_version) >= 90400 + unless ActiveRecord::Base.connection.database_version >= 90400 skip("line type is not fully implemented") end @connection = ActiveRecord::Base.connection diff --git a/activerecord/test/cases/adapters/postgresql/partitions_test.rb b/activerecord/test/cases/adapters/postgresql/partitions_test.rb index 0ac9ca1200..4015bc94f9 100644 --- a/activerecord/test/cases/adapters/postgresql/partitions_test.rb +++ b/activerecord/test/cases/adapters/postgresql/partitions_test.rb @@ -12,7 +12,7 @@ class PostgreSQLPartitionsTest < ActiveRecord::PostgreSQLTestCase end def test_partitions_table_exists - skip unless ActiveRecord::Base.connection.postgresql_version >= 100000 + skip unless ActiveRecord::Base.connection.database_version >= 100000 @connection.create_table :partitioned_events, force: true, id: false, options: "partition by range (issued_at)" do |t| t.timestamp :issued_at diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb index 5113548091..89a9c30f9b 100644 --- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb +++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb @@ -6,8 +6,9 @@ module ActiveRecord module ConnectionAdapters class SchemaCacheTest < ActiveRecord::TestCase def setup - @connection = ActiveRecord::Base.connection - @cache = SchemaCache.new @connection + @connection = ActiveRecord::Base.connection + @cache = SchemaCache.new @connection + @database_version = @connection.get_database_version end def test_primary_key @@ -28,6 +29,7 @@ module ActiveRecord assert new_cache.data_sources("posts") assert_equal "id", new_cache.primary_keys("posts") assert_equal 1, new_cache.indexes("posts").size + assert_equal @database_version.to_s, new_cache.database_version.to_s end end @@ -55,6 +57,20 @@ module ActiveRecord @connection.schema_cache = old_cache end + def test_yaml_loads_5_1_dump_without_database_version_still_queries_for_database_version + @cache = YAML.load(File.read(schema_dump_path)) + + # Simulate assignment in railtie after loading the cache. + old_cache, @connection.schema_cache = @connection.schema_cache, @cache + + # We can't verify queries get executed because the database version gets + # cached in both MySQL and PostgreSQL outside of the schema cache. + assert_nil @cache.instance_variable_get(:@database_version) + assert_equal @database_version.to_s, @cache.database_version.to_s + ensure + @connection.schema_cache = old_cache + end + def test_primary_key_for_non_existent_table assert_nil @cache.primary_keys("omgponies") end @@ -74,6 +90,14 @@ module ActiveRecord assert_equal indexes, @cache.indexes("posts") end + def test_caches_database_version + @cache.database_version # cache database_version + + assert_no_queries do + assert_equal @database_version.to_s, @cache.database_version.to_s + end + end + def test_clearing @cache.columns("posts") @cache.columns_hash("posts") @@ -84,6 +108,7 @@ module ActiveRecord @cache.clear! assert_equal 0, @cache.size + assert_nil @cache.instance_variable_get(:@database_version) end def test_dump_and_load @@ -101,6 +126,7 @@ module ActiveRecord assert @cache.data_sources("posts") assert_equal "id", @cache.primary_keys("posts") assert_equal 1, @cache.indexes("posts").size + assert_equal @database_version.to_s, @cache.database_version.to_s end end diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index 5d02e59ef6..50a86b0a19 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -89,7 +89,7 @@ if current_adapter?(:PostgreSQLAdapter) test "schema dump includes default expression" do output = dump_table_schema("defaults") - if ActiveRecord::Base.connection.postgresql_version >= 100000 + if ActiveRecord::Base.connection.database_version >= 100000 assert_match %r/t\.date\s+"modified_date",\s+default: -> { "CURRENT_DATE" }/, output assert_match %r/t\.datetime\s+"modified_time",\s+default: -> { "CURRENT_TIMESTAMP" }/, output else diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index f95d082907..298ee40df1 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -53,7 +53,7 @@ def supports_default_expression? true elsif current_adapter?(:Mysql2Adapter) conn = ActiveRecord::Base.connection - !conn.mariadb? && conn.version >= "8.0.13" + !conn.mariadb? && conn.database_version >= "8.0.13" end end diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb index dbbba9c5fa..cce3461e18 100644 --- a/activerecord/test/cases/migration/columns_test.rb +++ b/activerecord/test/cases/migration/columns_test.rb @@ -136,7 +136,7 @@ module ActiveRecord def test_remove_column_with_multi_column_index # MariaDB starting with 10.2.8 # Dropping a column that is part of a multi-column UNIQUE constraint is not permitted. - skip if current_adapter?(:Mysql2Adapter) && connection.mariadb? && connection.version >= "10.2.8" + skip if current_adapter?(:Mysql2Adapter) && connection.mariadb? && connection.database_version >= "10.2.8" add_column "test_models", :hat_size, :integer add_column "test_models", :hat_style, :string, limit: 100 diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb index ba21923d79..5f1057f093 100644 --- a/activerecord/test/cases/migration/foreign_key_test.rb +++ b/activerecord/test/cases/migration/foreign_key_test.rb @@ -155,7 +155,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys? class ForeignKeyChangeColumnWithSuffixTest < ForeignKeyChangeColumnTest setup do - ActiveRecord::Base.table_name_suffix = "_p" + ActiveRecord::Base.table_name_suffix = "_s" end teardown do diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index 06f11108f9..4b27512fa1 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -50,6 +50,9 @@ module ActiveRecord protected_environments = ActiveRecord::Base.protected_environments current_env = ActiveRecord::Base.connection.migration_context.current_environment + InternalMetadata.create_table + InternalMetadata[:environment] = current_env + assert_called_on_instance_of( ActiveRecord::MigrationContext, :current_version, @@ -73,6 +76,10 @@ module ActiveRecord def test_raises_an_error_when_called_with_protected_environment_which_name_is_a_symbol protected_environments = ActiveRecord::Base.protected_environments current_env = ActiveRecord::Base.connection.migration_context.current_environment + + InternalMetadata.create_table + InternalMetadata[:environment] = current_env + assert_called_on_instance_of( ActiveRecord::MigrationContext, :current_version, @@ -951,11 +958,22 @@ module ActiveRecord fixtures :authors, :author_addresses + def setup + SchemaMigration.create_table + SchemaMigration.create!(version: SchemaMigration.table_name) + InternalMetadata.create_table + InternalMetadata.create!(key: InternalMetadata.table_name) + end + def teardown + SchemaMigration.drop_table + InternalMetadata.drop_table ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler } end def test_truncate_tables + assert_operator SchemaMigration.count, :>, 0 + assert_operator InternalMetadata.count, :>, 0 assert_operator Author.count, :>, 0 assert_operator AuthorAddress.count, :>, 0 @@ -969,12 +987,54 @@ module ActiveRecord ) end + assert_operator SchemaMigration.count, :>, 0 + assert_operator InternalMetadata.count, :>, 0 assert_equal 0, Author.count assert_equal 0, AuthorAddress.count ensure ActiveRecord::Base.configurations = old_configurations end end + + class DatabaseTasksTruncateAllWithPrefixTest < DatabaseTasksTruncateAllTest + setup do + ActiveRecord::Base.table_name_prefix = "p_" + + SchemaMigration.reset_table_name + SchemaMigration.reset_column_information + InternalMetadata.reset_table_name + InternalMetadata.reset_column_information + end + + teardown do + ActiveRecord::Base.table_name_prefix = nil + + SchemaMigration.reset_table_name + SchemaMigration.reset_column_information + InternalMetadata.reset_table_name + InternalMetadata.reset_column_information + end + end + + class DatabaseTasksTruncateAllWithSuffixTest < DatabaseTasksTruncateAllTest + setup do + ActiveRecord::Base.table_name_suffix = "_s" + + SchemaMigration.reset_table_name + SchemaMigration.reset_column_information + InternalMetadata.reset_table_name + InternalMetadata.reset_column_information + end + + teardown do + ActiveRecord::Base.table_name_suffix = nil + + SchemaMigration.reset_table_name + SchemaMigration.reset_column_information + InternalMetadata.reset_table_name + InternalMetadata.reset_column_information + end + end end class DatabaseTasksTruncateAllWithMultipleDatabasesTest < ActiveRecord::TestCase diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index e076f10ece..10538eff0f 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -1305,6 +1305,21 @@ The `:source` option specifies the source association name for a `has_one :throu The `:source_type` option specifies the source association type for a `has_one :through` association that proceeds through a polymorphic association. +```ruby +class Book < ApplicationRecord + has_one :format, polymorphic: true + has_one :dust_jacket, through: :format, source: :dust_jacket, source_type: "Hardback" +end + +class Paperback < ApplicationRecord; end + +class Hardback < ApplicationRecord + has_one :dust_jacket +end + +class DustJacket < ApplicationRecord; end +``` + ##### `:through` The `:through` option specifies a join model through which to perform the query. `has_one :through` associations were discussed in detail [earlier in this guide](#the-has-one-through-association). @@ -1717,6 +1732,20 @@ The `:source` option specifies the source association name for a `has_many :thro The `:source_type` option specifies the source association type for a `has_many :through` association that proceeds through a polymorphic association. +```ruby +class Author < ApplicationRecord + has_many :books + has_many :paperbacks, through: :books, source: :format, source_type: "Paperback" +end + +class Book < ApplicationRecord + has_one :format, polymorphic: true +end + +class Hardback < ApplicationRecord; end +class Paperback < ApplicationRecord; end +``` + ##### `:through` The `:through` option specifies a join model through which to perform the query. `has_many :through` associations provide a way to implement many-to-many relationships, as discussed [earlier in this guide](#the-has-many-through-association). diff --git a/railties/lib/rails/generators/database.rb b/railties/lib/rails/generators/database.rb index 18fc7be3ff..cc6e7b50e5 100644 --- a/railties/lib/rails/generators/database.rb +++ b/railties/lib/rails/generators/database.rb @@ -15,7 +15,7 @@ module Rails case database when "mysql" then ["mysql2", [">= 0.4.4"]] when "postgresql" then ["pg", [">= 0.18", "< 2.0"]] - when "sqlite3" then ["sqlite3", ["~> 1.3", ">= 1.3.6"]] + when "sqlite3" then ["sqlite3", ["~> 1.4"]] when "oracle" then ["activerecord-oracle_enhanced-adapter", nil] when "frontbase" then ["ruby-frontbase", nil] when "sqlserver" then ["activerecord-sqlserver-adapter", nil] diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index d30cd9e718..6bbb12f433 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -526,7 +526,7 @@ class AppGeneratorTest < Rails::Generators::TestCase if defined?(JRUBY_VERSION) assert_gem "activerecord-jdbcsqlite3-adapter" else - assert_gem "sqlite3", "'~> 1.3', '>= 1.3.6'" + assert_gem "sqlite3", "'~> 1.4'" end end @@ -678,6 +678,22 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_inclusion_of_listen_related_configuration_on_other_rubies + ruby_engine = Object.send(:remove_const, :RUBY_ENGINE) + Object.const_set(:RUBY_ENGINE, "MyRuby") + begin + run_generator + if RbConfig::CONFIG["host_os"] =~ /darwin|linux/ + assert_listen_related_configuration + else + assert_no_listen_related_configuration + end + ensure + Object.send(:remove_const, :RUBY_ENGINE) + Object.const_set(:RUBY_ENGINE, ruby_engine) + end + end + def test_non_inclusion_of_listen_related_configuration_if_skip_listen run_generator [destination_root, "--skip-listen"] assert_no_listen_related_configuration diff --git a/railties/test/generators/db_system_change_generator_test.rb b/railties/test/generators/db_system_change_generator_test.rb index d3d27b616a..607db96906 100644 --- a/railties/test/generators/db_system_change_generator_test.rb +++ b/railties/test/generators/db_system_change_generator_test.rb @@ -68,7 +68,7 @@ module Rails assert_file("Gemfile") do |content| assert_match "# Use sqlite3 as the database for Active Record", content - assert_match "gem 'sqlite3', '~> 1.3', '>= 1.3.6'", content + assert_match "gem 'sqlite3', '~> 1.4'", content end end |