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:
parent
45468b87f1
commit
505526335b
10 changed files with 80 additions and 5 deletions
|
@ -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.
|
||||
|
||||
*Matthew Draper*
|
||||
|
|
|
@ -1071,7 +1071,7 @@ module ActiveRecord
|
|||
raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
|
||||
end
|
||||
|
||||
elsif [:datetime, :time, :interval].include?(type) && precision ||= native[:precision]
|
||||
elsif [:datetime, :timestamp, :time, :interval].include?(type) && precision ||= native[:precision]
|
||||
if (0..6) === precision
|
||||
column_type_sql << "(#{precision})"
|
||||
else
|
||||
|
|
|
@ -46,6 +46,7 @@ module ActiveRecord
|
|||
float: { name: "float" },
|
||||
decimal: { name: "decimal" },
|
||||
datetime: { name: "datetime" },
|
||||
timestamp: { name: "timestamp" },
|
||||
time: { name: "time" },
|
||||
date: { name: "date" },
|
||||
binary: { name: "blob", limit: 65535 },
|
||||
|
@ -708,7 +709,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def extract_precision(sql_type)
|
||||
if /time/.match?(sql_type)
|
||||
if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
|
||||
super || 0
|
||||
else
|
||||
super
|
||||
|
|
|
@ -25,6 +25,14 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
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]
|
||||
sql << " CHARACTER SET #{charset}"
|
||||
end
|
||||
|
|
|
@ -75,6 +75,11 @@ module ActiveRecord
|
|||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
def aliased_types(name, fallback)
|
||||
fallback
|
||||
end
|
||||
end
|
||||
|
||||
class Table < ActiveRecord::ConnectionAdapters::Table
|
||||
|
|
|
@ -30,7 +30,10 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def schema_type(column)
|
||||
if column.sql_type == "tinyblob"
|
||||
case column.sql_type
|
||||
when /\Atimestamp\b/
|
||||
:timestamp
|
||||
when "tinyblob"
|
||||
:blob
|
||||
else
|
||||
super
|
||||
|
@ -38,7 +41,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
def schema_collation(column)
|
||||
|
|
|
@ -100,11 +100,21 @@ if current_adapter?(:Mysql2Adapter)
|
|||
include SchemaDumpingHelper
|
||||
|
||||
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")
|
||||
assert_match %r/t\.datetime\s+"modified_datetime",\s+default: -> { "CURRENT_TIMESTAMP" }/, output
|
||||
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
|
||||
|
||||
class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase
|
||||
|
|
|
@ -269,6 +269,8 @@ module ActiveRecord
|
|||
|
||||
if current_adapter?(:PostgreSQLAdapter)
|
||||
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
|
||||
assert_equal klass.connection.type_to_sql("datetime"), klass.columns_hash["foo"].sql_type
|
||||
end
|
||||
|
|
|
@ -291,6 +291,14 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
|
|||
schema = dump_table_schema "barcodes"
|
||||
assert_match %r{create_table "barcodes", primary_key: "code", id: :string, limit: 42}, schema
|
||||
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
|
||||
|
||||
class CompositePrimaryKeyTest < ActiveRecord::TestCase
|
||||
|
|
|
@ -6,6 +6,11 @@ ActiveRecord::Schema.define do
|
|||
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|
|
||||
t.binary :var_binary, limit: 255
|
||||
t.binary :var_binary_large, limit: 4095
|
||||
|
|
Loading…
Reference in a new issue