5

I currently have the following db schema:

create_table :user_actions do |t|
  t.integer  :emitter_id
  t.string   :emitter_type
  t.integer  :target_id
  t.string   :target_type
  t.json     :payload
  t.timestamps
end

And I would like to migrate the payload field from json to hstore.

Doing the following:

change_column :user_actions, :payload, :hstore

Result in the following error message:

PG::DatatypeMismatch: ERROR:  column "payload" cannot be cast automatically to type hstore
HINT:  Specify a USING expression to perform the conversion.

Not sure how to use the USING hint and what's the best way to do this migration without loosing any data ?

Pierre-Louis Gottfrois
  • 17,561
  • 8
  • 47
  • 71
  • Does [this example](http://www.postgresql.org/docs/9.3/static/hstore.html#AEN149208) help? (Section F.16.6. Compatibility) – DrColossos Jun 17 '14 at 13:12
  • this is probably doable @DrColossos example shows the postgres using clause. I don't know much about ruby/activerecord, I did find this thread: https://github.com/rails/rails/issues/11062 which indicates that 1) you can't get to a using clause through activerecord and 2) the work around is going straight to sql. – Greg Jun 17 '14 at 13:20
  • Thx, actually went for a simple solution, rename column and do a simple migration `o.new_field = o.payload` where `new_field` is a `hstore` – Pierre-Louis Gottfrois Jun 17 '14 at 15:41

3 Answers3

12

HINT: Specify a USING expression to perform the conversion

Actually the format is:

change_column :user_actions, :payload, '[type_to_which_you_want_to_change] USING CAST(data AS [type_to_which_you_want_to_change])'

So in your case:

change_column :user_actions, :payload, 'hstore USING CAST(payload AS hstore)'

Reference:

https://stackoverflow.com/a/25032193/1222852

Community
  • 1
  • 1
Taimoor Changaiz
  • 10,250
  • 4
  • 49
  • 53
3

Taimoor Changaiz's answer is partially incorrect since postgresql cannot cast JSON to HSTORE without using a custom function. Assuming your JSON is not nested, here's what will work:

  def self.up
    execute <<-SQL
        CREATE OR REPLACE FUNCTION my_json_to_hstore(json)
              RETURNS hstore
              IMMUTABLE
              STRICT
              LANGUAGE sql
            AS $func$
              SELECT hstore(array_agg(key), array_agg(value))
              FROM   json_each_text($1)
            $func$;
        SQL
    change_column :user_actions, :payload, 'hstore USING my_json_to_hstore(payload)'
    end

  def self.down
    change_column :user_actions, :payload, 'json USING CAST(payload AS json)'
    execute "DROP FUNCTION my_json_to_hstore(json)"
  end

Credit to pozs for his custom postgresql function here: Casting JSON to HSTORE in Postgres 9.3+?

Community
  • 1
  • 1
l85m
  • 808
  • 1
  • 10
  • 19
2

Went for a simple but working solution. Create a new column named parameters and do a simple migration script:

def up
  add_column :user_actions, :parameters, :hstore

  UserAction.find_each do |o|
    o.parameters = o.payload
    o.save!
  end

  remove_column :user_actions, :payload
end

def down
  add_column :user_actions, :payload, :json

  UserAction.find_each do |o|
    o.payload = o.parameters
    o.save!
  end

  remove_column :user_actions, :parameters
end
Pierre-Louis Gottfrois
  • 17,561
  • 8
  • 47
  • 71