1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Correctly dump native timestamp types for MySQL

The native timestamp type in MySQL is different from datetime type.
Internal representation of the timestamp type is UNIX time, This means
that timestamp columns are affected by time zone.

```
> SET time_zone = '+00:00';
Query OK, 0 rows affected (0.00 sec)

> INSERT INTO time_with_zone(ts,dt) VALUES (NOW(),NOW());
Query OK, 1 row affected (0.02 sec)

> SELECT * FROM time_with_zone;
+---------------------+---------------------+
| ts                  | dt                  |
+---------------------+---------------------+
| 2016-02-07 22:11:44 | 2016-02-07 22:11:44 |
+---------------------+---------------------+
1 row in set (0.00 sec)

> SET time_zone = '-08:00';
Query OK, 0 rows affected (0.00 sec)

> SELECT * FROM time_with_zone;
+---------------------+---------------------+
| ts                  | dt                  |
+---------------------+---------------------+
| 2016-02-07 14:11:44 | 2016-02-07 22:11:44 |
+---------------------+---------------------+
1 row in set (0.00 sec)
```
This commit is contained in:
Ryuta Kamizono 2015-10-11 21:45:55 +09:00
parent 45468b87f1
commit 505526335b
10 changed files with 80 additions and 5 deletions

View file

@ -1,3 +1,36 @@
* Correctly dump native timestamp types for MySQL.
The native timestamp type in MySQL is different from datetime type.
Internal representation of the timestamp type is UNIX time, This means
that timestamp columns are affected by time zone.
> SET time_zone = '+00:00';
Query OK, 0 rows affected (0.00 sec)
> INSERT INTO time_with_zone(ts,dt) VALUES (NOW(),NOW());
Query OK, 1 row affected (0.02 sec)
> SELECT * FROM time_with_zone;
+---------------------+---------------------+
| ts | dt |
+---------------------+---------------------+
| 2016-02-07 22:11:44 | 2016-02-07 22:11:44 |
+---------------------+---------------------+
1 row in set (0.00 sec)
> SET time_zone = '-08:00';
Query OK, 0 rows affected (0.00 sec)
> SELECT * FROM time_with_zone;
+---------------------+---------------------+
| ts | dt |
+---------------------+---------------------+
| 2016-02-07 14:11:44 | 2016-02-07 22:11:44 |
+---------------------+---------------------+
1 row in set (0.00 sec)
*Ryuta Kamizono*
* All integer-like PKs are autoincrement unless they have an explicit default. * All integer-like PKs are autoincrement unless they have an explicit default.
*Matthew Draper* *Matthew Draper*

View file

@ -1071,7 +1071,7 @@ module ActiveRecord
raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified" raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
end end
elsif [:datetime, :time, :interval].include?(type) && precision ||= native[:precision] elsif [:datetime, :timestamp, :time, :interval].include?(type) && precision ||= native[:precision]
if (0..6) === precision if (0..6) === precision
column_type_sql << "(#{precision})" column_type_sql << "(#{precision})"
else else

View file

@ -46,6 +46,7 @@ module ActiveRecord
float: { name: "float" }, float: { name: "float" },
decimal: { name: "decimal" }, decimal: { name: "decimal" },
datetime: { name: "datetime" }, datetime: { name: "datetime" },
timestamp: { name: "timestamp" },
time: { name: "time" }, time: { name: "time" },
date: { name: "date" }, date: { name: "date" },
binary: { name: "blob", limit: 65535 }, binary: { name: "blob", limit: 65535 },
@ -708,7 +709,7 @@ module ActiveRecord
end end
def extract_precision(sql_type) def extract_precision(sql_type)
if /time/.match?(sql_type) if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
super || 0 super || 0
else else
super super

View file

@ -25,6 +25,14 @@ module ActiveRecord
end end
def add_column_options!(sql, options) def add_column_options!(sql, options)
# By default, TIMESTAMP columns are NOT NULL, cannot contain NULL values,
# and assigning NULL assigns the current timestamp. To permit a TIMESTAMP
# column to contain NULL, explicitly declare it with the NULL attribute.
# See http://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html
if /\Atimestamp\b/.match?(options[:column].sql_type) && !options[:primary_key]
sql << " NULL" unless options[:null] == false || options_include_default?(options)
end
if charset = options[:charset] if charset = options[:charset]
sql << " CHARACTER SET #{charset}" sql << " CHARACTER SET #{charset}"
end end

View file

@ -75,6 +75,11 @@ module ActiveRecord
super super
end end
private
def aliased_types(name, fallback)
fallback
end
end end
class Table < ActiveRecord::ConnectionAdapters::Table class Table < ActiveRecord::ConnectionAdapters::Table

View file

@ -30,7 +30,10 @@ module ActiveRecord
end end
def schema_type(column) def schema_type(column)
if column.sql_type == "tinyblob" case column.sql_type
when /\Atimestamp\b/
:timestamp
when "tinyblob"
:blob :blob
else else
super super
@ -38,7 +41,7 @@ module ActiveRecord
end end
def schema_precision(column) def schema_precision(column)
super unless /time/.match?(column.sql_type) && column.precision == 0 super unless /\A(?:date)?time(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0
end end
def schema_collation(column) def schema_collation(column)

View file

@ -100,11 +100,21 @@ if current_adapter?(:Mysql2Adapter)
include SchemaDumpingHelper include SchemaDumpingHelper
if ActiveRecord::Base.connection.version >= "5.6.0" if ActiveRecord::Base.connection.version >= "5.6.0"
test "schema dump includes default expression" do test "schema dump datetime includes default expression" do
output = dump_table_schema("datetime_defaults") output = dump_table_schema("datetime_defaults")
assert_match %r/t\.datetime\s+"modified_datetime",\s+default: -> { "CURRENT_TIMESTAMP" }/, output assert_match %r/t\.datetime\s+"modified_datetime",\s+default: -> { "CURRENT_TIMESTAMP" }/, output
end end
end end
test "schema dump timestamp includes default expression" do
output = dump_table_schema("timestamp_defaults")
assert_match %r/t\.timestamp\s+"modified_timestamp",\s+default: -> { "CURRENT_TIMESTAMP" }/, output
end
test "schema dump timestamp without default expression" do
output = dump_table_schema("timestamp_defaults")
assert_match %r/t\.timestamp\s+"nullable_timestamp"$/, output
end
end end
class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase

View file

@ -269,6 +269,8 @@ module ActiveRecord
if current_adapter?(:PostgreSQLAdapter) if current_adapter?(:PostgreSQLAdapter)
assert_equal "timestamp without time zone", klass.columns_hash["foo"].sql_type assert_equal "timestamp without time zone", klass.columns_hash["foo"].sql_type
elsif current_adapter?(:Mysql2Adapter)
assert_equal "timestamp", klass.columns_hash["foo"].sql_type
else else
assert_equal klass.connection.type_to_sql("datetime"), klass.columns_hash["foo"].sql_type assert_equal klass.connection.type_to_sql("datetime"), klass.columns_hash["foo"].sql_type
end end

View file

@ -291,6 +291,14 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
schema = dump_table_schema "barcodes" schema = dump_table_schema "barcodes"
assert_match %r{create_table "barcodes", primary_key: "code", id: :string, limit: 42}, schema assert_match %r{create_table "barcodes", primary_key: "code", id: :string, limit: 42}, schema
end end
if current_adapter?(:Mysql2Adapter) && subsecond_precision_supported?
test "schema typed primary key column" do
@connection.create_table(:scheduled_logs, id: :timestamp, precision: 6, force: true)
schema = dump_table_schema("scheduled_logs")
assert_match %r/create_table "scheduled_logs", id: :timestamp, precision: 6/, schema
end
end
end end
class CompositePrimaryKeyTest < ActiveRecord::TestCase class CompositePrimaryKeyTest < ActiveRecord::TestCase

View file

@ -6,6 +6,11 @@ ActiveRecord::Schema.define do
end end
end end
create_table :timestamp_defaults, force: true do |t|
t.timestamp :nullable_timestamp
t.timestamp :modified_timestamp, default: -> { "CURRENT_TIMESTAMP" }
end
create_table :binary_fields, force: true do |t| create_table :binary_fields, force: true do |t|
t.binary :var_binary, limit: 255 t.binary :var_binary, limit: 255
t.binary :var_binary_large, limit: 4095 t.binary :var_binary_large, limit: 4095