bulk_insert
bulk_insert copied to clipboard
Incorrect default value inserted for JSON column types
In case of a JSON column, bulk_insert
will incorrectly insert the default value. It inserts "{}"
(ie. a string) instead of {}
(ie. an object).
Here's a failing test:
begin
require "bundler/inline"
rescue LoadError => e
$stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
raise e
end
gemfile(true) do
source "https://rubygems.org"
gem "rails"
gem "sqlite3"
gem "bulk_insert"
end
require "active_record"
require "minitest/autorun"
require "logger"
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
create_table :profiles, force: true do |t|
t.string :name
t.json :json_data, default: {}, null: false
end
end
class Profile < ActiveRecord::Base
end
class BugTest < Minitest::Test
def test_bulk_insert_json_default
worker = Profile.bulk_insert
worker.add(name: "Foo", json_data: {})
worker.add(name: "Bar")
worker.save!
profile1 = Profile.find_by!(name: "Foo")
profile2 = Profile.find_by!(name: "Bar")
assert_equal profile1.json_data, {}
assert_equal profile2.json_data, {}
end
end
The reason is the following code: https://github.com/jamis/bulk_insert/blob/ab5db0873098701904ac2fcdcab86e417d342895/lib/bulk_insert/worker.rb#L51-L64
In case a value does not exist, the column default is used. The column default in rails is expressed as a string (ie. in the example above: Profile.columns_hash["json_data"].default == {}
). This is problematic for JSON columns, as both "{}"
and {}
are valid JSON (one a string, the other an object).