This project is archived and is in readonly mode.

#1117 incomplete
Victor Costan

Serialized fields + sqlite :binary columns = exception

Reported by Victor Costan | September 26th, 2008 @ 06:43 AM | in 3.x

I have a serialized field. In my migration, I use the type :binary for the column. This works fine on mysql, but raises the following on sqlite3:

NoMethodError: private method `gsub' called for #<Facebooker::User:0x1fe8fac>
/Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/connection_adapters/sqlite_adapter.rb:58:in `binary_to_string'
/Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/connection_adapters/abstract/schema_definitions.rb:69:in `type_cast'
/Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/dirty.rb:157:in `field_changed?'
/Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/dirty.rb:131:in `write_attribute'
/Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/attribute_methods.rb:211:in `facebooker_user='

Diagnostic attempt: The problem is that type_cast should not be called in the first place. The method's comment says that its arg should be a string, which won't be the case if the field is serialized. This issue is masked if the column's type is string, because type_cast simply returns its arg. binary_to_string also returns its arg by default, but the sqlite adapter overrides that behavior. I think the solution is having a different field_changed? for serialized fields, or

Sadly, I don't have enough bandwidth to look further into this. I'll switch my field to a string in my deployment. I hope someone else does, though.

Thanks!

Victor

Comments and changes to this ticket

  • Victor Costan

    Victor Costan September 26th, 2008 @ 08:13 AM

    More debugging:

    I first tried to fix the error in dirty.rb, by replacing field_changed? with the following:

      def field_changed?(attr, old, value)
        if column = column_for_attribute(attr)
          if column.type == :integer && column.null && (old.nil? || old == 0)
            # For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
            # Hence we don't record it as a change if the value changes from nil to ''.
            # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
            # be typecast back to 0 (''.to_i => 0)
            value = nil if value.blank?
          else
            if self.class.serialized_attributes.has_key? attr
              # nothing to do for serialized attributes
              value = value
            else
              value = column.type_cast(value)
            end
          end
        end
    
        old != value
      end
    
    

    This fixed writing, but reading still breaks with the following:

    NoMethodError: private method `gsub' called for #<Facebooker::User:0x2019544>
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/connection_adapters/sqlite_adapter.rb:58:in `binary_to_string'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/connection_adapters/abstract/schema_definitions.rb:69:in `type_cast'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/attribute_methods.rb:269:in `read_attribute'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/base.rb:2587:in `attributes_with_quotes'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/base.rb:2585:in `each'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/base.rb:2585:in `attributes_with_quotes'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/base.rb:2507:in `create_without_callbacks'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/callbacks.rb:220:in `create_without_timestamps'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/timestamp.rb:29:in `create'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/base.rb:2483:in `create_or_update_without_callbacks'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/callbacks.rb:207:in `create_or_update'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/base.rb:2217:in `save_without_validation!'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/validations.rb:921:in `save_without_dirty!'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/dirty.rb:83:in `save_without_transactions!'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/transactions.rb:110:in `save!'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/connection_adapters/abstract/database_statements.rb:66:in `transaction'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/transactions.rb:79:in `transaction'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/transactions.rb:98:in `transaction'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/transactions.rb:110:in `save!'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/transactions.rb:118:in `rollback_active_record_state!'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.1.1/lib/active_record/transactions.rb:110:in `save!'
    
    

    However, the following quick fix in the SQLite adapter (lib/active_record/connection_adapters/sqlite_adapter.rb:31) works:

        def binary_to_string(value)
          return value unless value.kind_of? String
          value.gsub(/%00|%25/n) do |b|
            case b
              when "%00" then "\0"
              when "%25" then "%"
            end
          end
        end
    
    

    This is not a proper fix, it just makes the adapter mask the error, like the other adapters. But it works(tm).

  • Pratik

    Pratik January 18th, 2009 @ 06:55 AM

    • Tag changed from 2.1.activerecord, bug to 2.1.activerecord, activerecord, bug, serialization, serialize, serialized, sqlite
    • State changed from “new” to “incomplete”
    • Assigned user set to “Pratik”

    Maybe you could submit failing tests.

    Thanks.

  • Ryan Bigg

    Ryan Bigg April 10th, 2010 @ 11:25 AM

    Or even a concise example of how to duplicate the bug would be good.

  • Victor Costan

    Victor Costan April 21st, 2010 @ 05:51 AM

    • Tag changed from 2.1.activerecord, activerecord, bug, serialization, serialize, serialized, sqlite to 2.1.activerecord, activerecord, bug, rails3, serialization, serialize, serialized, sqlite

    With the help of Boston.rb (especially Dan Pickett), I put together a failing test. The patch applies cleanly to both master and 2.3-stable.

    The test passes for MySQL and PostgreSQL. On SQLite3, the test crashes ruby-1.8.7-p249 on Rails 3, and on 2-3-stable it raises the exception mentioned in the bug report.

    Thanks.

  • Jeremy Kemper

    Jeremy Kemper May 4th, 2010 @ 06:48 PM

    • Milestone changed from 2.x to 3.x
  • Andrea Campi

    Andrea Campi October 16th, 2010 @ 11:46 PM

    • Tag changed from 2.1.activerecord, activerecord, bug, rails3, serialization, serialize, serialized, sqlite to 2-3-stable, activerecord, bug, rails3, serialization, serialize, serialized, sqlite
    • Importance changed from “” to “”
  • bingbing

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

<h2 style="font-size: 14px">Tickets have moved to Github</h2>

The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>

People watching this ticket

Attachments

Pages