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

Merge pull request #20005 from kamipo/default_expression_support

Add `:expression` option support on the schema default
This commit is contained in:
Rafael França 2016-01-16 04:18:21 -02:00
commit de2259791c
11 changed files with 100 additions and 44 deletions

View file

@ -1,3 +1,13 @@
* Add expression support on the schema default.
Example:
create_table :posts do |t|
t.datetime :published_at, default: -> { 'NOW()' }
end
*Ryuta Kamizono*
* Fix regression when loading fixture files with symbol keys.
Fixes #22584.

View file

@ -102,9 +102,13 @@ module ActiveRecord
quote_table_name("#{table}.#{attr}")
end
def quote_default_expression(value, column) #:nodoc:
value = lookup_cast_type(column.sql_type).serialize(value)
quote(value)
def quote_default_expression(value, column) # :nodoc:
if value.is_a?(Proc)
value.call
else
value = lookup_cast_type(column.sql_type).serialize(value)
quote(value)
end
end
def quoted_true

View file

@ -76,11 +76,17 @@ module ActiveRecord
def schema_default(column)
type = lookup_cast_type_from_column(column)
default = type.deserialize(column.default)
unless default.nil?
if default.nil?
schema_expression(column)
else
type.type_cast_for_schema(default)
end
end
def schema_expression(column)
"-> { #{column.default_function.inspect} }" if column.default_function
end
def schema_collation(column)
column.collation.inspect if column.collation
end

View file

@ -495,12 +495,16 @@ module ActiveRecord
end
# Returns an array of +Column+ objects for the table specified by +table_name+.
def columns(table_name)#:nodoc:
def columns(table_name) # :nodoc:
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
execute_and_free(sql, 'SCHEMA') do |result|
each_hash(result).map do |field|
type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
new_column(field[:Field], nil, type_metadata, field[:Null] == "YES", field[:Default], field[:Collation])
else
new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
end
end
end
end

View file

@ -55,10 +55,11 @@ module ActiveRecord
end
end
# Does not quote function default values for UUID columns
def quote_default_expression(value, column) #:nodoc:
if column.type == :uuid && value =~ /\(\)/
value
def quote_default_expression(value, column) # :nodoc:
if value.is_a?(Proc)
value.call
elsif column.type == :uuid && value =~ /\(\)/
value # Does not quote function default values for UUID columns
elsif column.respond_to?(:array?)
value = type_cast_from_column(column, value)
quote(value)

View file

@ -9,7 +9,7 @@ module ActiveRecord
spec[:id] = ':bigserial'
elsif column.type == :uuid
spec[:id] = ':uuid'
spec[:default] = column.default_function.inspect
spec[:default] = schema_default(column) || 'nil'
else
spec[:id] = column.type.inspect
spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
@ -41,12 +41,8 @@ module ActiveRecord
end
end
def schema_default(column)
if column.default_function
column.default_function.inspect unless column.serial?
else
super
end
def schema_expression(column)
super unless column.serial?
end
end
end

View file

@ -512,8 +512,13 @@ module ActiveRecord
def extract_value_from_default(default) # :nodoc:
case default
# Quoted types
when /\A[\(B]?'(.*)'::/m
$1.gsub("''".freeze, "'".freeze)
when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
# The default 'now'::date is CURRENT_DATE
if $1 == "now".freeze && $2 == "date".freeze
nil
else
$1.gsub("''".freeze, "'".freeze)
end
# Boolean types
when 'true'.freeze, 'false'.freeze
default
@ -535,7 +540,7 @@ module ActiveRecord
end
def has_default_function?(default_value, default) # :nodoc:
!default_value && (%r{\w+\(.*\)} === default)
!default_value && (%r{\w+\(.*\)|\(.*\)::\w+} === default)
end
def load_additional_types(type_map, oids = nil) # :nodoc:

View file

@ -197,14 +197,14 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
def test_schema_dumper_for_uuid_primary_key
schema = dump_table_schema "pg_uuids"
assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema)
assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema)
assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: -> { "uuid_generate_v1\(\)" }/, schema)
assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema)
end
def test_schema_dumper_for_uuid_primary_key_with_custom_default
schema = dump_table_schema "pg_uuids_2"
assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: "my_uuid_generator\(\)"/, schema)
assert_match(/t\.uuid "other_uuid_2", default: "my_uuid_generator\(\)"/, schema)
assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: -> { "my_uuid_generator\(\)" }/, schema)
assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema)
end
end
end

View file

@ -1,4 +1,5 @@
require "cases/helper"
require 'support/schema_dumping_helper'
require 'models/default'
require 'models/entrant'
@ -80,7 +81,32 @@ class DefaultStringsTest < ActiveRecord::TestCase
end
end
if current_adapter?(:PostgreSQLAdapter)
class PostgresqlDefaultExpressionTest < ActiveRecord::TestCase
include SchemaDumpingHelper
test "schema dump includes default expression" do
output = dump_table_schema("defaults")
assert_match %r/t\.date\s+"modified_date",\s+default: -> { "\('now'::text\)::date" }/, output
assert_match %r/t\.date\s+"modified_date_function",\s+default: -> { "now\(\)" }/, output
assert_match %r/t\.datetime\s+"modified_time",\s+default: -> { "now\(\)" }/, output
assert_match %r/t\.datetime\s+"modified_time_function",\s+default: -> { "now\(\)" }/, output
end
end
end
if current_adapter?(:Mysql2Adapter)
class MysqlDefaultExpressionTest < ActiveRecord::TestCase
include SchemaDumpingHelper
if ActiveRecord::Base.connection.version >= '5.6.0'
test "schema dump 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
end
class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase
# ActiveRecord::Base#create! (and #save and other related methods) will
# open a new transaction. When in transactional tests mode, this will

View file

@ -1,4 +1,11 @@
ActiveRecord::Schema.define do
if ActiveRecord::Base.connection.version >= '5.6.0'
create_table :datetime_defaults, force: true do |t|
t.datetime :modified_datetime, default: -> { 'CURRENT_TIMESTAMP' }
end
end
create_table :binary_fields, force: true do |t|
t.binary :var_binary, limit: 255
t.binary :var_binary_large, limit: 4095

View file

@ -11,7 +11,23 @@ ActiveRecord::Schema.define do
t.uuid :uuid_parent_id
end
%w(postgresql_times postgresql_oids defaults postgresql_timestamp_with_zones
create_table :defaults, force: true do |t|
t.date :modified_date, default: -> { 'CURRENT_DATE' }
t.date :modified_date_function, default: -> { 'now()' }
t.date :fixed_date, default: '2004-01-01'
t.datetime :modified_time, default: -> { 'CURRENT_TIMESTAMP' }
t.datetime :modified_time_function, default: -> { 'now()' }
t.datetime :fixed_time, default: '2004-01-01 00:00:00.000000-00'
t.column :char1, 'char(1)', default: 'Y'
t.string :char2, limit: 50, default: 'a varchar field'
t.text :char3, default: 'a text field'
t.bigint :bigint_default, default: -> { '0::bigint' }
t.text :multiline_default, default: '--- []
'
end
%w(postgresql_times postgresql_oids postgresql_timestamp_with_zones
postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name|
drop_table table_name, if_exists: true
end
@ -27,25 +43,6 @@ ActiveRecord::Schema.define do
execute "SELECT setval('#{seq_name}', 100)"
end
execute <<_SQL
CREATE TABLE defaults (
id serial primary key,
modified_date date default CURRENT_DATE,
modified_date_function date default now(),
fixed_date date default '2004-01-01',
modified_time timestamp default CURRENT_TIMESTAMP,
modified_time_function timestamp default now(),
fixed_time timestamp default '2004-01-01 00:00:00.000000-00',
char1 char(1) default 'Y',
char2 character varying(50) default 'a varchar field',
char3 text default 'a text field',
bigint_default bigint default 0::bigint,
multiline_default text DEFAULT '--- []
'::text
);
_SQL
execute <<_SQL
CREATE TABLE postgresql_times (
id SERIAL PRIMARY KEY,