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:
commit
de2259791c
11 changed files with 100 additions and 44 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue