Merge pull request #29699 from lugray/represent_boolean_as_integer

Change sqlite3 boolean serialization to use 1 and 0
This commit is contained in:
Matthew Draper 2017-07-12 23:21:07 +09:30 committed by GitHub
commit 58f10a31b3
12 changed files with 148 additions and 8 deletions

@ -1,3 +1,14 @@
* Change sqlite3 boolean serialization to use 1 and 0
SQLite natively recognizes 1 and 0 as true and false, but does not natively
recognize 't' and 'f' as was previously serialized.
This change in serialization requires a migration of stored boolean data
for SQLite databases, so it's implemented behind a configuration flag
whose default false value is deprecated.
*Lisa Ugray*
* Skip query caching when working with batches of records (`find_each`, `find_in_batches`,
`in_batches`).

@ -106,19 +106,19 @@ def quote_default_expression(value, column) # :nodoc:
end
def quoted_true
"'t'".freeze
"TRUE".freeze
end
def unquoted_true
"t".freeze
true
end
def quoted_false
"'f'".freeze
"FALSE".freeze
end
def unquoted_false
"f".freeze
false
end
# Quote date/time values for use in SQL input. Includes microseconds

@ -22,6 +22,22 @@ def quoted_binary(value)
"x'#{value.hex}'"
end
def quoted_true
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "1".freeze : "'t'".freeze
end
def unquoted_true
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 1 : "t".freeze
end
def quoted_false
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "0".freeze : "'f'".freeze
end
def unquoted_false
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 0 : "f".freeze
end
private
def _type_cast(value)

@ -72,6 +72,23 @@ class SQLite3Adapter < AbstractAdapter
boolean: { name: "boolean" }
}
##
# :singleton-method:
# Indicates whether boolean values are stored in sqlite3 databases as 1
# and 0 or 't' and 'f'. Leaving `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer`
# set to false is deprecated. SQLite databases have used 't' and 'f' to
# serialize boolean values and must have old data converted to 1 and 0
# (its native boolean serialization) before setting this flag to true.
# Conversion can be accomplished by setting up a rake task which runs
#
# ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 1)
# ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 0)
# for all models and all boolean columns, after which the flag must be set
# to true by adding the following to your application.rb file:
#
# ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true
class_attribute :represent_boolean_as_integer, default: false
class StatementPool < ConnectionAdapters::StatementPool
private
@ -512,5 +529,6 @@ def configure_connection
execute("PRAGMA foreign_keys = ON", "SCHEMA")
end
end
ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
end
end

@ -174,5 +174,29 @@ class Railtie < Rails::Railtie # :nodoc:
end
end
end
initializer "active_record.check_represent_sqlite3_boolean_as_integer" do
config.after_initialize do
ActiveSupport.on_load(:active_record_sqlite3adapter) do
unless ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer
ActiveSupport::Deprecation.warn <<-MSG
Leaving `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer`
set to false is deprecated. SQLite databases have used 't' and 'f' to serialize
boolean values and must have old data converted to 1 and 0 (its native boolean
serialization) before setting this flag to true. Conversion can be accomplished
by setting up a rake task which runs
ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 1)
ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 0)
for all models and all boolean columns, after which the flag must be set to
true by adding the following to your application.rb file:
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true
MSG
end
end
end
end
end
end

@ -9,11 +9,11 @@ def setup
end
def test_type_cast_true
assert_equal "t", @conn.type_cast(true)
assert_equal true, @conn.type_cast(true)
end
def test_type_cast_false
assert_equal "f", @conn.type_cast(false)
assert_equal false, @conn.type_cast(false)
end
def test_quote_float_nan

@ -5,6 +5,11 @@
class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase
def setup
@conn = ActiveRecord::Base.connection
@initial_represent_boolean_as_integer = ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer
end
def teardown
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = @initial_represent_boolean_as_integer
end
def test_type_cast_binary_encoding_without_logger
@ -15,11 +20,19 @@ def test_type_cast_binary_encoding_without_logger
end
def test_type_cast_true
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = false
assert_equal "t", @conn.type_cast(true)
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true
assert_equal 1, @conn.type_cast(true)
end
def test_type_cast_false
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = false
assert_equal "f", @conn.type_cast(false)
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true
assert_equal 0, @conn.type_cast(false)
end
def test_type_cast_bigdecimal

@ -8,11 +8,11 @@ def setup
end
def test_quoted_true
assert_equal "'t'", @quoter.quoted_true
assert_equal "TRUE", @quoter.quoted_true
end
def test_quoted_false
assert_equal "'f'", @quoter.quoted_false
assert_equal "FALSE", @quoter.quoted_false
end
def test_quote_column_name

@ -375,6 +375,28 @@ The MySQL adapter adds one additional configuration option:
* `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns as booleans. Defaults to `true`.
The SQLite3Adapter adapter adds one additional configuration option:
* `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer`
indicates whether boolean values are stored in sqlite3 databases as 1 and 0 or
't' and 'f'. Leaving `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer`
set to false is deprecated. SQLite databases have used 't' and 'f' to serialize
boolean values and must have old data converted to 1 and 0 (its native boolean
serialization) before setting this flag to true. Conversion can be accomplished
by setting up a rake task which runs
```ruby
ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 1)
ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 0)
```
for all models and all boolean columns, after which the flag must be set to true
by adding the following to your application.rb file:
```ruby
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true
```
The schema dumper adds one additional configuration option:
* `ActiveRecord::SchemaDumper.ignore_tables` accepts an array of tables that should _not_ be included in any generated schema file. This setting is ignored unless `config.active_record.schema_format == :ruby`.

@ -86,6 +86,10 @@ def load_defaults(target_version)
if respond_to?(:active_record)
active_record.cache_versioning = true
# Remove the temporary load hook from SQLite3Adapter when this is removed
ActiveSupport.on_load(:active_record_sqlite3adapter) do
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true
end
end
if respond_to?(:action_dispatch)

@ -21,3 +21,7 @@
# Add default protection from forgery to ActionController::Base instead of in
# ApplicationController.
# Rails.application.config.action_controller.default_protect_from_forgery = true
# Store boolean values are in sqlite3 databases as 1 and 0 instead of 't' and
# 'f' after migrating old data.
# ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true

@ -1585,6 +1585,34 @@ def index
assert_equal({}, Rails.application.config.my_custom_config)
end
test "default SQLite3Adapter.represent_boolean_as_integer for 5.1 is false" do
remove_from_config '.*config\.load_defaults.*\n'
add_to_top_of_config <<-RUBY
config.load_defaults 5.1
RUBY
app_file "app/models/post.rb", <<-RUBY
class Post < ActiveRecord::Base
end
RUBY
app "development"
Post.object_id # force lazy load hooks to run
assert_not ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer
end
test "default SQLite3Adapter.represent_boolean_as_integer for new installs is true" do
app_file "app/models/post.rb", <<-RUBY
class Post < ActiveRecord::Base
end
RUBY
app "development"
Post.object_id # force lazy load hooks to run
assert ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer
end
test "config_for containing ERB tags should evaluate" do
app_file "config/custom.yml", <<-RUBY
development: