Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect default value inserted for JSON column types #82

Open
fschwahn opened this issue Oct 7, 2022 · 0 comments
Open

Incorrect default value inserted for JSON column types #82

fschwahn opened this issue Oct 7, 2022 · 0 comments

Comments

@fschwahn
Copy link

fschwahn commented Oct 7, 2022

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:

mapped = @columns.map.with_index do |column, index|
value_exists = values.is_a?(Hash) ? values.key?(column.name) : (index < values.length)
if !value_exists
if column.default.present?
column.default
elsif column.name == "created_at" || column.name == "updated_at"
:__timestamp_placeholder
else
nil
end
else
values.is_a?(Hash) ? values[column.name] : values[index]
end
end

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant