Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
张重言
rails
提交
1944a7e7
R
rails
项目概览
张重言
/
rails
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
R
rails
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
1944a7e7
编写于
12月 04, 2017
作者:
F
fatkodima
提交者:
Jeremy Daer
6月 02, 2020
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Add basic support for check constraints to database migrations
上级
aaf20e3c
变更
20
隐藏空白更改
内联
并排
Showing
20 changed file
with
459 addition
and
12 deletion
+459
-12
activerecord/CHANGELOG.md
activerecord/CHANGELOG.md
+11
-0
activerecord/lib/active_record/connection_adapters.rb
activerecord/lib/active_record/connection_adapters.rb
+1
-0
activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
...ve_record/connection_adapters/abstract/schema_creation.rb
+25
-1
activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
...record/connection_adapters/abstract/schema_definitions.rb
+48
-3
activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
..._record/connection_adapters/abstract/schema_statements.rb
+70
-0
activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
...lib/active_record/connection_adapters/abstract_adapter.rb
+5
-0
activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
...tive_record/connection_adapters/abstract_mysql_adapter.rb
+32
-0
activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
...ctive_record/connection_adapters/mysql/schema_creation.rb
+4
-0
activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
...ecord/connection_adapters/postgresql/schema_statements.rb
+21
-0
activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
...b/active_record/connection_adapters/postgresql_adapter.rb
+4
-0
activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
...e_record/connection_adapters/sqlite3/schema_statements.rb
+29
-0
activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
.../lib/active_record/connection_adapters/sqlite3_adapter.rb
+14
-1
activerecord/lib/active_record/migration/command_recorder.rb
activerecord/lib/active_record/migration/command_recorder.rb
+10
-1
activerecord/lib/active_record/schema_dumper.rb
activerecord/lib/active_record/schema_dumper.rb
+25
-0
activerecord/test/cases/migration/change_table_test.rb
activerecord/test/cases/migration/change_table_test.rb
+14
-0
activerecord/test/cases/migration/check_constraint_test.rb
activerecord/test/cases/migration/check_constraint_test.rb
+118
-0
activerecord/test/cases/migration/command_recorder_test.rb
activerecord/test/cases/migration/command_recorder_test.rb
+11
-0
activerecord/test/cases/schema_dumper_test.rb
activerecord/test/cases/schema_dumper_test.rb
+11
-0
activerecord/test/schema/schema.rb
activerecord/test/schema/schema.rb
+5
-1
guides/source/active_record_migrations.md
guides/source/active_record_migrations.md
+1
-5
未找到文件。
activerecord/CHANGELOG.md
浏览文件 @
1944a7e7
*
Add basic support for CHECK constraints to database migrations.
Usage:
```ruby
add_check_constraint :products, "price > 0", name: "price_check"
remove_check_constraint :products, name: "price_check"
```
*fatkodima*
*
Add
`ActiveRecord::Base.strict_loading_by_default`
and
`ActiveRecord::Base.strict_loading_by_default=`
to enable/disable strict_loading mode by default for a model. The configuration's value is
inheritable by subclasses, but they can override that value and it will not impact parent class.
...
...
activerecord/lib/active_record/connection_adapters.rb
浏览文件 @
1944a7e7
...
...
@@ -17,6 +17,7 @@ module ConnectionAdapters
autoload
:ColumnDefinition
autoload
:ChangeColumnDefinition
autoload
:ForeignKeyDefinition
autoload
:CheckConstraintDefinition
autoload
:TableDefinition
autoload
:Table
autoload
:AlterTable
...
...
activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
浏览文件 @
1944a7e7
...
...
@@ -15,7 +15,8 @@ def accept(o)
delegate
:quote_column_name
,
:quote_table_name
,
:quote_default_expression
,
:type_to_sql
,
:options_include_default?
,
:supports_indexes_in_create?
,
:supports_foreign_keys?
,
:foreign_key_options
,
:quoted_columns_for_index
,
:supports_partial_index?
,
to: :@conn
,
private:
true
:quoted_columns_for_index
,
:supports_partial_index?
,
:supports_check_constraints?
,
:check_constraint_options
,
to: :@conn
,
private:
true
private
def
visit_AlterTable
(
o
)
...
...
@@ -23,6 +24,8 @@ def visit_AlterTable(o)
sql
<<
o
.
adds
.
map
{
|
col
|
accept
col
}.
join
(
" "
)
sql
<<
o
.
foreign_key_adds
.
map
{
|
fk
|
visit_AddForeignKey
fk
}.
join
(
" "
)
sql
<<
o
.
foreign_key_drops
.
map
{
|
fk
|
visit_DropForeignKey
fk
}.
join
(
" "
)
sql
<<
o
.
check_constraint_adds
.
map
{
|
con
|
visit_AddCheckConstraint
con
}.
join
(
" "
)
sql
<<
o
.
check_constraint_drops
.
map
{
|
con
|
visit_DropCheckConstraint
con
}.
join
(
" "
)
end
def
visit_ColumnDefinition
(
o
)
...
...
@@ -52,6 +55,10 @@ def visit_TableDefinition(o)
statements
.
concat
(
o
.
foreign_keys
.
map
{
|
to_table
,
options
|
foreign_key_in_create
(
o
.
name
,
to_table
,
options
)
})
end
if
supports_check_constraints?
statements
.
concat
(
o
.
check_constraints
.
map
{
|
expression
,
options
|
check_constraint_in_create
(
o
.
name
,
expression
,
options
)
})
end
create_sql
<<
"(
#{
statements
.
join
(
', '
)
}
)"
if
statements
.
present?
add_table_options!
(
create_sql
,
o
)
create_sql
<<
" AS
#{
to_sql
(
o
.
as
)
}
"
if
o
.
as
...
...
@@ -98,6 +105,18 @@ def visit_CreateIndexDefinition(o)
sql
.
join
(
" "
)
end
def
visit_CheckConstraintDefinition
(
o
)
"CONSTRAINT
#{
o
.
name
}
CHECK (
#{
o
.
expression
}
)"
end
def
visit_AddCheckConstraint
(
o
)
"ADD
#{
accept
(
o
)
}
"
end
def
visit_DropCheckConstraint
(
name
)
"DROP CONSTRAINT
#{
quote_column_name
(
name
)
}
"
end
def
quoted_columns
(
o
)
String
===
o
.
columns
?
o
.
columns
:
quoted_columns_for_index
(
o
.
columns
,
o
.
column_options
)
end
...
...
@@ -148,6 +167,11 @@ def foreign_key_in_create(from_table, to_table, options)
accept
ForeignKeyDefinition
.
new
(
from_table
,
to_table
,
options
)
end
def
check_constraint_in_create
(
table_name
,
expression
,
options
)
options
=
check_constraint_options
(
table_name
,
expression
,
options
)
accept
CheckConstraintDefinition
.
new
(
table_name
,
expression
,
options
)
end
def
action_sql
(
action
,
dependency
)
case
dependency
when
:nullify
then
"ON
#{
action
}
SET NULL"
...
...
activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
浏览文件 @
1944a7e7
...
...
@@ -127,6 +127,16 @@ def default_primary_key
end
end
CheckConstraintDefinition
=
Struct
.
new
(
:table_name
,
:expression
,
:options
)
do
def
name
options
[
:name
]
end
def
export_name_on_schema_dump?
!
ActiveRecord
::
SchemaDumper
.
chk_ignore_pattern
.
match?
(
name
)
if
name
end
end
class
ReferenceDefinition
# :nodoc:
def
initialize
(
name
,
...
...
@@ -267,7 +277,7 @@ def #{column_type}(*names, **options)
class
TableDefinition
include
ColumnMethods
attr_reader
:name
,
:temporary
,
:if_not_exists
,
:options
,
:as
,
:comment
,
:indexes
,
:foreign_keys
attr_reader
:name
,
:temporary
,
:if_not_exists
,
:options
,
:as
,
:comment
,
:indexes
,
:foreign_keys
,
:check_constraints
def
initialize
(
conn
,
...
...
@@ -284,6 +294,7 @@ def initialize(
@indexes
=
[]
@foreign_keys
=
[]
@primary_keys
=
nil
@check_constraints
=
[]
@temporary
=
temporary
@if_not_exists
=
if_not_exists
@options
=
options
...
...
@@ -412,6 +423,10 @@ def foreign_key(table_name, **options) # :nodoc:
foreign_keys
<<
[
table_name
,
options
]
end
def
check_constraint
(
expression
,
**
options
)
check_constraints
<<
[
expression
,
options
]
end
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
# <tt>:updated_at</tt> to the table. See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps]
#
...
...
@@ -471,14 +486,16 @@ def integer_like_primary_key_type(type, options)
class
AlterTable
# :nodoc:
attr_reader
:adds
attr_reader
:foreign_key_adds
attr_reader
:
foreign_key
_drops
attr_reader
:foreign_key_adds
,
:foreign_key_drops
attr_reader
:
check_constraint_adds
,
:check_constraint
_drops
def
initialize
(
td
)
@td
=
td
@adds
=
[]
@foreign_key_adds
=
[]
@foreign_key_drops
=
[]
@check_constraint_adds
=
[]
@check_constraint_drops
=
[]
end
def
name
;
@td
.
name
;
end
...
...
@@ -491,6 +508,14 @@ def drop_foreign_key(name)
@foreign_key_drops
<<
name
end
def
add_check_constraint
(
expression
,
options
)
@check_constraint_adds
<<
CheckConstraintDefinition
.
new
(
name
,
expression
,
options
)
end
def
drop_check_constraint
(
constraint_name
)
@check_constraint_drops
<<
constraint_name
end
def
add_column
(
name
,
type
,
**
options
)
name
=
name
.
to_s
type
=
type
.
to_sym
...
...
@@ -515,6 +540,7 @@ def add_column(name, type, **options)
# t.rename
# t.references
# t.belongs_to
# t.check_constraint
# t.string
# t.text
# t.integer
...
...
@@ -536,6 +562,7 @@ def add_column(name, type, **options)
# t.remove_references
# t.remove_belongs_to
# t.remove_index
# t.remove_check_constraint
# t.remove_timestamps
# end
#
...
...
@@ -737,6 +764,24 @@ def remove_foreign_key(*args, **options)
def
foreign_key_exists?
(
*
args
,
**
options
)
@base
.
foreign_key_exists?
(
name
,
*
args
,
**
options
)
end
# Adds a check constraint.
#
# t.check_constraint("price > 0", name: "price_check")
#
# See {connection.add_check_constraint}[rdoc-ref:SchemaStatements#add_check_constraint]
def
check_constraint
(
*
args
)
@base
.
add_check_constraint
(
name
,
*
args
)
end
# Removes the given check constraint from the table.
#
# t.remove_check_constraint(name: "price_check")
#
# See {connection.remove_check_constraint}[rdoc-ref:SchemaStatements#remove_check_constraint]
def
remove_check_constraint
(
*
args
)
@base
.
remove_check_constraint
(
name
,
*
args
)
end
end
end
end
activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
浏览文件 @
1944a7e7
...
...
@@ -1128,6 +1128,55 @@ def foreign_key_options(from_table, to_table, options) # :nodoc:
options
end
# Returns an array of check constraints for the given table.
# The check constraints are represented as CheckConstraintDefinition objects.
def
check_constraints
(
table_name
)
raise
NotImplementedError
end
# Adds a new check constraint to the table. +expression+ is a String
# representation of verifiable boolean condition.
#
# add_check_constraint :products, "price > 0", name: "price_check"
#
# generates:
#
# ALTER TABLE "products" ADD CONSTRAINT price_check CHECK (price > 0)
#
def
add_check_constraint
(
table_name
,
expression
,
**
options
)
return
unless
supports_check_constraints?
options
=
check_constraint_options
(
table_name
,
expression
,
options
)
at
=
create_alter_table
(
table_name
)
at
.
add_check_constraint
(
expression
,
options
)
execute
schema_creation
.
accept
(
at
)
end
def
check_constraint_options
(
table_name
,
expression
,
options
)
# :nodoc:
options
=
options
.
dup
options
[
:name
]
||=
check_constraint_name
(
table_name
,
expression:
expression
,
**
options
)
options
end
# Removes the given check constraint from the table.
#
# remove_check_constraint :products, name: "price_check"
#
# The +expression+ parameter will be ignored if present. It can be helpful
# to provide this in a migration's +change+ method so it can be reverted.
# In that case, +expression+ will be used by #add_check_constraint.
def
remove_check_constraint
(
table_name
,
expression
=
nil
,
**
options
)
return
unless
supports_check_constraints?
chk_name_to_delete
=
check_constraint_for!
(
table_name
,
expression:
expression
,
**
options
).
name
at
=
create_alter_table
(
table_name
)
at
.
drop_check_constraint
(
chk_name_to_delete
)
execute
schema_creation
.
accept
(
at
)
end
def
dump_schema_information
# :nodoc:
versions
=
schema_migration
.
all_versions
insert_versions_sql
(
versions
)
if
versions
.
any?
...
...
@@ -1457,6 +1506,27 @@ def extract_foreign_key_action(specifier)
end
end
def
check_constraint_name
(
table_name
,
**
options
)
options
.
fetch
(
:name
)
do
expression
=
options
.
fetch
(
:expression
)
identifier
=
"
#{
table_name
}
_
#{
expression
}
_chk"
hashed_identifier
=
Digest
::
SHA256
.
hexdigest
(
identifier
).
first
(
10
)
"chk_rails_
#{
hashed_identifier
}
"
end
end
def
check_constraint_for
(
table_name
,
**
options
)
return
unless
supports_check_constraints?
chk_name
=
check_constraint_name
(
table_name
,
**
options
)
check_constraints
(
table_name
).
detect
{
|
chk
|
chk
.
name
==
chk_name
}
end
def
check_constraint_for!
(
table_name
,
expression:
nil
,
**
options
)
check_constraint_for
(
table_name
,
expression:
expression
,
**
options
)
||
raise
(
ArgumentError
,
"Table '
#{
table_name
}
' has no check constraint for
#{
expression
||
options
}
"
)
end
def
validate_index_length!
(
table_name
,
new_name
,
internal
=
false
)
if
new_name
.
length
>
index_name_length
raise
ArgumentError
,
"Index name '
#{
new_name
}
' on table '
#{
table_name
}
' is too long; the limit is
#{
index_name_length
}
characters"
...
...
activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
浏览文件 @
1944a7e7
...
...
@@ -338,6 +338,11 @@ def supports_foreign_keys_in_create?
end
deprecate
:supports_foreign_keys_in_create?
# Does this adapter support creating check constraints?
def
supports_check_constraints?
false
end
# Does this adapter support views?
def
supports_views?
false
...
...
activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
浏览文件 @
1944a7e7
...
...
@@ -92,6 +92,14 @@ def supports_foreign_keys?
true
end
def
supports_check_constraints?
if
mariadb?
database_version
>=
"10.2.1"
else
database_version
>=
"8.0.16"
end
end
def
supports_views?
true
end
...
...
@@ -415,6 +423,30 @@ def foreign_keys(table_name)
end
end
def
check_constraints
(
table_name
)
scope
=
quoted_scope
(
table_name
)
chk_info
=
exec_query
(
<<~
SQL
,
"SCHEMA"
)
SELECT cc.constraint_name AS 'name',
cc.check_clause AS 'expression'
FROM information_schema.check_constraints cc
JOIN information_schema.table_constraints tc
USING (constraint_schema, constraint_name)
WHERE tc.table_schema =
#{
scope
[
:schema
]
}
AND tc.table_name =
#{
scope
[
:name
]
}
AND cc.constraint_schema =
#{
scope
[
:schema
]
}
SQL
chk_info
.
map
do
|
row
|
options
=
{
name:
row
[
"name"
]
}
expression
=
row
[
"expression"
]
expression
=
expression
[
1
..-
2
]
unless
mariadb?
# remove parentheses added by mysql
CheckConstraintDefinition
.
new
(
table_name
,
expression
,
options
)
end
end
def
table_options
(
table_name
)
# :nodoc:
create_table_info
=
create_table_info
(
table_name
)
...
...
activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
浏览文件 @
1944a7e7
...
...
@@ -11,6 +11,10 @@ def visit_DropForeignKey(name)
"DROP FOREIGN KEY
#{
name
}
"
end
def
visit_DropCheckConstraint
(
name
)
"DROP
#{
mariadb?
?
'CONSTRAINT'
:
'CHECK'
}
#{
name
}
"
end
def
visit_AddColumnDefinition
(
o
)
add_column_position!
(
super
,
column_options
(
o
.
column
))
end
...
...
activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
浏览文件 @
1944a7e7
...
...
@@ -519,6 +519,27 @@ def foreign_table_exists?(table_name)
query_values
(
data_source_sql
(
table_name
,
type:
"FOREIGN TABLE"
),
"SCHEMA"
).
any?
if
table_name
.
present?
end
def
check_constraints
(
table_name
)
# :nodoc:
scope
=
quoted_scope
(
table_name
)
check_info
=
exec_query
(
<<-
SQL
,
"SCHEMA"
)
SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef
FROM pg_constraint c
JOIN pg_class t ON c.conrelid = t.oid
WHERE c.contype = 'c'
AND t.relname =
#{
scope
[
:name
]
}
SQL
check_info
.
map
do
|
row
|
options
=
{
name:
row
[
"conname"
]
}
expression
=
row
[
"constraintdef"
][
/CHECK \({2}(.+)\){2}/
,
1
]
CheckConstraintDefinition
.
new
(
table_name
,
expression
,
options
)
end
end
# Maps logical Rails types to PostgreSQL-specific data types.
def
type_to_sql
(
type
,
limit:
nil
,
precision:
nil
,
scale:
nil
,
array:
nil
,
**
)
# :nodoc:
sql
=
\
...
...
activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
浏览文件 @
1944a7e7
...
...
@@ -166,6 +166,10 @@ def supports_foreign_keys?
true
end
def
supports_check_constraints?
true
end
def
supports_validate_constraints?
true
end
...
...
activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
浏览文件 @
1944a7e7
...
...
@@ -78,6 +78,35 @@ def remove_foreign_key(from_table, to_table = nil, **options)
alter_table
(
from_table
,
foreign_keys
)
end
def
check_constraints
(
table_name
)
table_sql
=
query_value
(
<<-
SQL
,
"SCHEMA"
)
SELECT sql
FROM sqlite_master
WHERE name =
#{
quote_table_name
(
table_name
)
}
AND type = 'table'
UNION ALL
SELECT sql
FROM sqlite_temp_master
WHERE name =
#{
quote_table_name
(
table_name
)
}
AND type = 'table'
SQL
table_sql
.
scan
(
/CONSTRAINT\s+(?<name>\w+)\s+CHECK\s+\((?<expression>(:?[^()]|\(\g<expression>\))+)\)/i
).
map
do
|
name
,
expression
|
CheckConstraintDefinition
.
new
(
table_name
,
expression
,
name:
name
)
end
end
def
add_check_constraint
(
table_name
,
expression
,
**
options
)
alter_table
(
table_name
)
do
|
definition
|
definition
.
check_constraint
(
expression
,
**
options
)
end
end
def
remove_check_constraint
(
table_name
,
expression
=
nil
,
**
options
)
check_constraints
=
check_constraints
(
table_name
)
chk_name_to_delete
=
check_constraint_for!
(
table_name
,
expression:
expression
,
**
options
).
name
check_constraints
.
delete_if
{
|
chk
|
chk
.
name
==
chk_name_to_delete
}
alter_table
(
table_name
,
foreign_keys
(
table_name
),
check_constraints
)
end
def
create_schema_dumper
(
options
)
SQLite3
::
SchemaDumper
.
create
(
self
,
options
)
end
...
...
activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
浏览文件 @
1944a7e7
...
...
@@ -136,6 +136,10 @@ def supports_foreign_keys?
true
end
def
supports_check_constraints?
true
end
def
supports_views?
true
end
...
...
@@ -361,7 +365,12 @@ def invalid_alter_table_type?(type, options)
options
[
:null
]
==
false
&&
options
[
:default
].
nil?
end
def
alter_table
(
table_name
,
foreign_keys
=
foreign_keys
(
table_name
),
**
options
)
def
alter_table
(
table_name
,
foreign_keys
=
foreign_keys
(
table_name
),
check_constraints
=
check_constraints
(
table_name
),
**
options
)
altered_table_name
=
"a
#{
table_name
}
"
caller
=
lambda
do
|
definition
|
...
...
@@ -374,6 +383,10 @@ def alter_table(table_name, foreign_keys = foreign_keys(table_name), **options)
definition
.
foreign_key
(
to_table
,
**
fk
.
options
)
end
check_constraints
.
each
do
|
chk
|
definition
.
check_constraint
(
chk
.
expression
,
**
chk
.
options
)
end
yield
definition
if
block_given?
end
...
...
activerecord/lib/active_record/migration/command_recorder.rb
浏览文件 @
1944a7e7
...
...
@@ -8,6 +8,7 @@ class Migration
#
# * add_column
# * add_foreign_key
# * add_check_constraint
# * add_index
# * add_reference
# * add_timestamps
...
...
@@ -25,6 +26,7 @@ class Migration
# * remove_column (must supply a type)
# * remove_columns (must specify at least one column name or more)
# * remove_foreign_key (must supply a second table)
# * remove_check_constraint
# * remove_index
# * remove_reference
# * remove_timestamps
...
...
@@ -39,7 +41,8 @@ class CommandRecorder
:drop_join_table
,
:drop_table
,
:execute_block
,
:enable_extension
,
:disable_extension
,
:change_column
,
:execute
,
:remove_columns
,
:change_column_null
,
:add_foreign_key
,
:remove_foreign_key
,
:change_column_comment
,
:change_table_comment
:change_column_comment
,
:change_table_comment
,
:add_check_constraint
,
:remove_check_constraint
]
include
JoinTable
...
...
@@ -136,6 +139,7 @@ module StraightReversions # :nodoc:
add_timestamps: :remove_timestamps
,
add_reference: :remove_reference
,
add_foreign_key: :remove_foreign_key
,
add_check_constraint: :remove_check_constraint
,
enable_extension: :disable_extension
}.
each
do
|
cmd
,
inv
|
[[
inv
,
cmd
],
[
cmd
,
inv
]].
uniq
.
each
do
|
method
,
inverse
|
...
...
@@ -265,6 +269,11 @@ def invert_change_table_comment(args)
[
:change_table_comment
,
[
table
,
from:
options
[
:to
],
to:
options
[
:from
]]]
end
def
invert_remove_check_constraint
(
args
)
raise
ActiveRecord
::
IrreversibleMigration
,
"remove_check_constraint is only reversible if given an expression."
if
args
.
size
<
2
super
end
def
respond_to_missing?
(
method
,
_
)
super
||
delegate
.
respond_to?
(
method
)
end
...
...
activerecord/lib/active_record/schema_dumper.rb
浏览文件 @
1944a7e7
...
...
@@ -23,6 +23,12 @@ class SchemaDumper #:nodoc:
# should not be dumped to db/schema.rb.
cattr_accessor
:fk_ignore_pattern
,
default:
/^fk_rails_[0-9a-f]{10}$/
##
# :singleton-method:
# Specify a custom regular expression matching check constraints which name
# should not be dumped to db/schema.rb.
cattr_accessor
:chk_ignore_pattern
,
default:
/^chk_rails_[0-9a-f]{10}$/
class
<<
self
def
dump
(
connection
=
ActiveRecord
::
Base
.
connection
,
stream
=
STDOUT
,
config
=
ActiveRecord
::
Base
)
connection
.
create_schema_dumper
(
generate_options
(
config
)).
dump
(
stream
)
...
...
@@ -159,6 +165,7 @@ def table(table, stream)
end
indexes_in_create
(
table
,
tbl
)
check_constraints_in_create
(
table
,
tbl
)
if
@connection
.
supports_check_constraints?
tbl
.
puts
" end"
tbl
.
puts
...
...
@@ -212,6 +219,24 @@ def index_parts(index)
index_parts
end
def
check_constraints_in_create
(
table
,
stream
)
if
(
check_constraints
=
@connection
.
check_constraints
(
table
)).
any?
add_check_constraint_statements
=
check_constraints
.
map
do
|
check_constraint
|
parts
=
[
"t.check_constraint
#{
check_constraint
.
expression
.
inspect
}
"
]
if
check_constraint
.
export_name_on_schema_dump?
parts
<<
"name:
#{
check_constraint
.
name
.
inspect
}
"
end
"
#{
parts
.
join
(
', '
)
}
"
end
stream
.
puts
add_check_constraint_statements
.
sort
.
join
(
"
\n
"
)
end
end
def
foreign_keys
(
table
,
stream
)
if
(
foreign_keys
=
@connection
.
foreign_keys
(
table
)).
any?
add_foreign_key_statements
=
foreign_keys
.
map
do
|
foreign_key
|
...
...
activerecord/test/cases/migration/change_table_test.rb
浏览文件 @
1944a7e7
...
...
@@ -345,6 +345,20 @@ def test_table_name_set
assert_equal
:delete_me
,
t
.
name
end
end
def
test_check_constraint_creates_check_constraint
with_change_table
do
|
t
|
@connection
.
expect
:add_check_constraint
,
nil
,
[
:delete_me
,
"price > discounted_price"
,
name:
"price_check"
]
t
.
check_constraint
"price > discounted_price"
,
name:
"price_check"
end
end
def
test_remove_check_constraint_removes_check_constraint
with_change_table
do
|
t
|
@connection
.
expect
:remove_check_constraint
,
nil
,
[
:delete_me
,
name:
"price_check"
]
t
.
remove_check_constraint
name:
"price_check"
end
end
end
end
end
activerecord/test/cases/migration/check_constraint_test.rb
0 → 100644
浏览文件 @
1944a7e7
# frozen_string_literal: true
require
"cases/helper"
require
"support/schema_dumping_helper"
if
ActiveRecord
::
Base
.
connection
.
supports_check_constraints?
module
ActiveRecord
class
Migration
class
CheckConstraintTest
<
ActiveRecord
::
TestCase
include
SchemaDumpingHelper
class
Trade
<
ActiveRecord
::
Base
end
setup
do
@connection
=
ActiveRecord
::
Base
.
connection
@connection
.
create_table
"trades"
,
force:
true
do
|
t
|
t
.
integer
:price
t
.
integer
:quantity
end
end
teardown
do
@connection
.
drop_table
"trades"
,
if_exists:
true
end
def
test_check_constraints
check_constraints
=
@connection
.
check_constraints
(
"products"
)
assert_equal
1
,
check_constraints
.
size
constraint
=
check_constraints
.
first
assert_equal
"products"
,
constraint
.
table_name
assert_equal
"products_price_check"
,
constraint
.
name
if
current_adapter?
(
:Mysql2Adapter
)
assert_equal
"`price` > `discounted_price`"
,
constraint
.
expression
else
assert_equal
"price > discounted_price"
,
constraint
.
expression
end
end
def
test_add_check_constraint
@connection
.
add_check_constraint
:trades
,
"quantity > 0"
check_constraints
=
@connection
.
check_constraints
(
"trades"
)
assert_equal
1
,
check_constraints
.
size
constraint
=
check_constraints
.
first
assert_equal
"trades"
,
constraint
.
table_name
assert_equal
"chk_rails_2189e9f96c"
,
constraint
.
name
if
current_adapter?
(
:Mysql2Adapter
)
assert_equal
"`quantity` > 0"
,
constraint
.
expression
else
assert_equal
"quantity > 0"
,
constraint
.
expression
end
end
def
test_added_check_constraint_ensures_valid_values
@connection
.
add_check_constraint
:trades
,
"quantity > 0"
,
name:
"quantity_check"
assert_raises
(
ActiveRecord
::
StatementInvalid
)
do
Trade
.
create
(
quantity:
-
1
)
end
end
def
test_remove_check_constraint
@connection
.
add_check_constraint
:trades
,
"price > 0"
,
name:
"price_check"
@connection
.
add_check_constraint
:trades
,
"quantity > 0"
,
name:
"quantity_check"
assert_equal
2
,
@connection
.
check_constraints
(
"trades"
).
size
@connection
.
remove_check_constraint
:trades
,
name:
"quantity_check"
assert_equal
1
,
@connection
.
check_constraints
(
"trades"
).
size
constraint
=
@connection
.
check_constraints
(
"trades"
).
first
assert_equal
"trades"
,
constraint
.
table_name
assert_equal
"price_check"
,
constraint
.
name
if
current_adapter?
(
:Mysql2Adapter
)
assert_equal
"`price` > 0"
,
constraint
.
expression
else
assert_equal
"price > 0"
,
constraint
.
expression
end
end
def
test_remove_non_existing_check_constraint
assert_raises
(
ArgumentError
)
do
@connection
.
remove_check_constraint
:trades
,
name:
"nonexistent"
end
end
end
end
end
else
module
ActiveRecord
class
Migration
class
NoForeignKeySupportTest
<
ActiveRecord
::
TestCase
setup
do
@connection
=
ActiveRecord
::
Base
.
connection
end
def
test_add_check_constraint_should_be_noop
@connection
.
add_check_constraint
:products
,
"discounted_price > 0"
,
name:
"discounted_price_check"
end
def
test_remove_check_constraint_should_be_noop
@connection
.
remove_check_constraint
:products
,
name:
"price_check"
end
def
test_check_constraints_should_raise_not_implemented
assert_raises
(
NotImplementedError
)
do
@connection
.
check_constraints
(
"products"
)
end
end
end
end
end
end
activerecord/test/cases/migration/command_recorder_test.rb
浏览文件 @
1944a7e7
...
...
@@ -421,6 +421,17 @@ def test_invert_transaction_with_irreversible_inside_is_irreversible
end
end
end
def
test_invert_remove_check_constraint
enable
=
@recorder
.
inverse_of
:remove_check_constraint
,
[
:dogs
,
"speed > 0"
,
name:
"speed_check"
]
assert_equal
[
:add_check_constraint
,
[
:dogs
,
"speed > 0"
,
name:
"speed_check"
],
nil
],
enable
end
def
test_invert_remove_check_constraint_without_expression
assert_raises
(
ActiveRecord
::
IrreversibleMigration
)
do
@recorder
.
inverse_of
:remove_check_constraint
,
[
:dogs
]
end
end
end
end
end
activerecord/test/cases/schema_dumper_test.rb
浏览文件 @
1944a7e7
...
...
@@ -200,6 +200,17 @@ def test_schema_dumps_index_length
end
end
if
ActiveRecord
::
Base
.
connection
.
supports_check_constraints?
def
test_schema_dumps_check_constraints
constraint_definition
=
dump_table_schema
(
"products"
).
split
(
/\n/
).
grep
(
/t.check_constraint.*products_price_check/
).
first
.
strip
if
current_adapter?
(
:Mysql2Adapter
)
assert_equal
't.check_constraint "`price` > `discounted_price`", name: "products_price_check"'
,
constraint_definition
else
assert_equal
't.check_constraint "price > discounted_price", name: "products_price_check"'
,
constraint_definition
end
end
end
def
test_schema_dump_should_honor_nonstandard_primary_keys
output
=
standard_dump
match
=
output
.
match
(
%r{create_table "movies"(.*)do}
)
...
...
activerecord/test/schema/schema.rb
浏览文件 @
1944a7e7
...
...
@@ -767,9 +767,13 @@
create_table
:products
,
force:
true
do
|
t
|
t
.
references
:collection
t
.
references
:type
t
.
string
:name
t
.
string
:name
t
.
decimal
:price
t
.
decimal
:discounted_price
end
add_check_constraint
:products
,
"price > discounted_price"
,
name:
"products_price_check"
create_table
:product_types
,
force:
true
do
|
t
|
t
.
string
:name
end
...
...
guides/source/active_record_migrations.md
浏览文件 @
1944a7e7
...
...
@@ -725,10 +725,6 @@ of `create_table` and `reversible`, replacing `create_table`
by
`drop_table`
, and finally replacing
`up`
by
`down`
and vice-versa.
This is all taken care of by
`revert`
.
NOTE: If you want to add check constraints like in the examples above,
you will have to use
`structure.sql`
as dump method. See
[
Schema Dumping and You
](
#schema-dumping-and-you
)
.
Running Migrations
------------------
...
...
@@ -970,7 +966,7 @@ database and expressing its structure using `create_table`, `add_index`, and so
on.
`db/schema.rb`
cannot express everything your database may support such as
triggers, sequences, stored procedures,
check constraints,
etc. While migrations
triggers, sequences, stored procedures, etc. While migrations
may use
`execute`
to create database constructs that are not supported by the
Ruby migration DSL, these constructs may not be able to be reconstituted by the
schema dumper. If you are using features like these, you should set the schema
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录