Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Postgres: Default for column (string) cannot be cast automatically to type enum

I have a status column in a table that I want to be an enum. Originally I created that field as an integer, thinking that I would use the built in Rails enum functionality. Turns out that requires at least Rails 4.1, but I am using 4.0 and the process of upgrading is going to take some time.

But thinking about how this all works, I realized that I can have either an ActiveRecord enum or a postgres enum, not both. I thought that in the long term having a more explicit postgres enum would be best. So, I wrote a migration to convert the status column from an integer to an enum.

execute "CREATE TYPE status_options AS ENUM ('pending', 'declined', 'approved');"
change_column :site_applications, :status, "status_options USING status::status_options"

But, I get this error:

PG::CannotCoerce: ERROR:  cannot cast type integer to status_options
ALTER TABLE "site_applications" ALTER COLUMN "status" TYPE status_options USING status::status_options

Everything that I have seen so far in my searchings tells me that should have worked, but it doesn't. I thought maybe the problem is that I just can't go from integer to enum. So be it. My solution was to first convert the column to a string and then try to convert it to enum.

change_column :site_applications, :status, :string
execute "CREATE TYPE status_options AS ENUM ('pending', 'declined', 'approved');"
change_column :site_applications, :status, "status_options USING status::status_options"

And that gives me the following error:

PG::DatatypeMismatch: ERROR:  default for column "status" cannot be cast automatically to type status_options
ALTER TABLE "site_applications" ALTER COLUMN "status" TYPE status_options USING status::status_options

That led me to believe that this had something to do with the default value, so I tried specifying the default in the change_column declaration:

change_column :site_applications, :status, :string, default: "pending"

That successfully changes the column to a string with a default of "pending", but change_column fails with the same "default for column" error.

I realize that I could simply drop the column all together and then recreate it exactly how I want, but at this point it's a matter of posterity. Why the heck can't I convert a column from integer or string to enum? Anyone?

UPDATE WITH ACCEPTED ANSWER

Based on Gary's answer down there, this is the migration that worked.

def up
  execute "ALTER TABLE site_applications ALTER status DROP DEFAULT;"
  execute "CREATE TYPE status_options AS ENUM ('pending', 'declined', 'approved');"
  change_column :site_applications, :status, "status_options USING status::status_options", default: "pending"
end

def down
  change_column :site_applications, :status, :string, default: "pending"
  execute "DROP TYPE status_options;"
end
like image 554
Eli Duke Avatar asked Jul 22 '15 15:07

Eli Duke


1 Answers

You need to remove the default value from the column prior to the change as the default is set to a value that is valid for the old column type but incompatible with the new type.

alter table schema.site_applications alter status drop default

Then you can change the column type. Finally once the new column type is applied, you can add a new default against the table.

alter table schema.site_applications alter status set default 'pending'::status_options
like image 85
Gary Avatar answered Sep 20 '22 17:09

Gary