PostgreSQL: handle timestamp with time zone columns correctly in schema.rb#41395
Conversation
…chema.rb` Found this issue while working on rails#41084 Currently if you have a `timestamp with time zone` column, and you run `rake db:schema:dump`, it will be output as a `t.datetime`. This is wrong because when you run `rake db:schema:load`, the column will be recreated as a `timestamp without time zone`. The fix is to add a new column type, `t.timestamptz`. This behaves exactly the same as before, the only change is what native type it is converted to during schema loads.
| output = perform_schema_dump | ||
| assert output.include?('t.datetime "default_format"') | ||
| assert output.include?('t.datetime "without_time_zone"') | ||
| assert output.include?('t.timestamptz "with_time_zone"') |
There was a problem hiding this comment.
without the changes in this PR, this test would fail. output.include?('t.datetime "with_time_zone"') would be true. the problem then is when you recreate the table (eg. in a test environment), the column is created as timestamp, not timestamptz.
| register_class_with_precision m, "time", Type::Time | ||
| register_class_with_precision m, "timestamp", OID::DateTime | ||
| register_class_with_precision m, "timestamp", OID::Timestamp | ||
| register_class_with_precision m, "timestamptz", OID::TimestampWithTimeZone |
There was a problem hiding this comment.
removing line 535 and adding this is the fix
|
Thank you! |
…one` In rails#21126 it was suggested to make "timestamp with time zone" the default type for datetime columns in PostgreSQL. This is in line with PostgreSQL [best practices](https://wiki.postgresql.org/wiki/Don't_Do_This#Don.27t_use_timestamp_.28without_time_zone.29). This PR lays some groundwork for that. This PR adds a configuration option, `ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type`. The default is `:timestamp` which preserves current Rails behavior of using "timestamp without time zone" when you do `t.datetime` in a migration. If you change it to `:timestamptz`, you'll get "timestamp with time zone" columns instead. If you change this setting in an existing app, you should immediately call `bin/rails db:migrate` to ensure your `schema.rb` file remains correct. If you do so, then existing columns will not be impacted, so for example if you have an app with a mixture of both types of columns, and you change the config, schema dumps will continue to output the correct types. This PR also adds two new types that can be used in migrations: `t.timestamp` and `t.timestamptz`. ```ruby ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamp # default value is :timestamp create_table("foo1") do |t| t.datetime :default_format # "timestamp without time zone" t.timestamp :without_time_zone # "timestamp without time zone" t.timestamptz :with_time_zone # "timestamp with time zone" end ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz create_table("foo2") do |t| t.datetime :default_format # "timestamp with time zone" <-- note how this has changed! t.timestamp :without_time_zone # "timestamp without time zone" t.timestamptz :with_time_zone # "timestamp with time zone" end ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:my_custom_type] = { name: "custom_datetime_format_i_invented" } ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :my_custom_type create_table("foo3") do |t| t.datetime :default_format # "custom_datetime_format_i_invented" t.timestamp :without_time_zone # "timestamp without time zone" t.timestamptz :with_time_zone # "timestamp with time zone" end ``` **Notes** - This PR doesn't change the default `datetime` format. The default is still "timestamp without time zone". A future PR could do that, but there was enough code here just getting the config option right. - See also rails#41395 which set some groundwork (and added some tests) for this. - This reverts some of rails#15184. rails#15184 alluded to issues in XML serialization, but I couldn't find any related tests that this broke.
…one` In rails#21126 it was suggested to make "timestamp with time zone" the default type for datetime columns in PostgreSQL. This is in line with PostgreSQL [best practices](https://wiki.postgresql.org/wiki/Don't_Do_This#Don.27t_use_timestamp_.28without_time_zone.29). This PR lays some groundwork for that. This PR adds a configuration option, `ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type`. The default is `:timestamp` which preserves current Rails behavior of using "timestamp without time zone" when you do `t.datetime` in a migration. If you change it to `:timestamptz`, you'll get "timestamp with time zone" columns instead. If you change this setting in an existing app, you should immediately call `bin/rails db:migrate` to ensure your `schema.rb` file remains correct. If you do so, then existing columns will not be impacted, so for example if you have an app with a mixture of both types of columns, and you change the config, schema dumps will continue to output the correct types. This PR also adds two new types that can be used in migrations: `t.timestamp` and `t.timestamptz`. ```ruby ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamp # default value is :timestamp create_table("foo1") do |t| t.datetime :default_format # "timestamp without time zone" t.timestamp :without_time_zone # "timestamp without time zone" t.timestamptz :with_time_zone # "timestamp with time zone" end ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz create_table("foo2") do |t| t.datetime :default_format # "timestamp with time zone" <-- note how this has changed! t.timestamp :without_time_zone # "timestamp without time zone" t.timestamptz :with_time_zone # "timestamp with time zone" end ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:my_custom_type] = { name: "custom_datetime_format_i_invented" } ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :my_custom_type create_table("foo3") do |t| t.datetime :default_format # "custom_datetime_format_i_invented" t.timestamp :without_time_zone # "timestamp without time zone" t.timestamptz :with_time_zone # "timestamp with time zone" end ``` **Notes** - This PR doesn't change the default `datetime` format. The default is still "timestamp without time zone". A future PR could do that, but there was enough code here just getting the config option right. - See also rails#41395 which set some groundwork (and added some tests) for this. - This reverts some of rails#15184. rails#15184 alluded to issues in XML serialization, but I couldn't find any related tests that this broke.
|
I'm reading through all of the different issues related to the When using Given # config/initializers/database.rb
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptzWhen running a migration: change_column :roles, :created_at, :timestamptz
change_column :roles, :updated_at, :timestamptzThe schema.rb still has create_table "roles", force: :cascade do |t|
- t.datetime "created_at", precision: 6, null: false
- t.datetime "updated_at", precision: 6, null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: falseHowever, the columns in the database are now changed to Is it expected that the column type in |
|
Yes, that is correct. Since your The idea is that unless you explicitly ask for a |
|
Thank you for confirming, and for all of the work you did on this! |
|
There's a potential issue with this PR - #44601 |
rails#41395 added support for the `timestamptz` type on the Postgres adapter. As we found [here](rails#41084 (comment)) this causes issues because in some scenarios the new type is not considered a time zone aware attribute, meaning values of this type in the DB are presented as a `Time`, not an `ActiveSupport::TimeWithZone`. This PR fixes that by ensuring that `timestamptz` is always a time zone aware type, for Postgres users.
rails#41395 added support for the `timestamptz` type on the Postgres adapter. As we found [here](rails#41084 (comment)) this causes issues because in some scenarios the new type is not considered a time zone aware attribute, meaning values of this type in the DB are presented as a `Time`, not an `ActiveSupport::TimeWithZone`. This PR fixes that by ensuring that `timestamptz` is always a time zone aware type, for Postgres users.
Found this issue while working on #41084
Currently if you have a
timestamp with time zonecolumn, and you runrake db:schema:dump, it will be output as at.datetime. This is wrong because when you runrake db:schema:load, the column will be recreated as atimestamp without time zone.The fix is to add a new column type,
t.timestamptz. This behaves exactly the same as before, the only change is what native type it is converted to during schema loads.This means that if you run
rake db:migratewith this code, you will see changes to yourschema.rbif the database you were dumping from includestimestamp with time zonecolumns.