Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
张重言
rails
提交
1db05065
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,发现更多精彩内容 >>
未验证
提交
1db05065
编写于
3月 17, 2019
作者:
R
Ryuta Kamizono
提交者:
GitHub
3月 17, 2019
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #35615 from kamipo/optimizer_hints
Support Optimizer Hints
上级
6486f80d
97347d8c
变更
20
隐藏空白更改
内联
并排
Showing
20 changed file
with
184 addition
and
6 deletion
+184
-6
activerecord/CHANGELOG.md
activerecord/CHANGELOG.md
+25
-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
+5
-0
activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
...b/active_record/connection_adapters/postgresql_adapter.rb
+12
-2
activerecord/lib/active_record/querying.rb
activerecord/lib/active_record/querying.rb
+1
-1
activerecord/lib/active_record/relation.rb
activerecord/lib/active_record/relation.rb
+1
-1
activerecord/lib/active_record/relation/query_methods.rb
activerecord/lib/active_record/relation/query_methods.rb
+24
-0
activerecord/lib/arel/nodes/select_core.rb
activerecord/lib/arel/nodes/select_core.rb
+3
-2
activerecord/lib/arel/nodes/unary.rb
activerecord/lib/arel/nodes/unary.rb
+1
-0
activerecord/lib/arel/select_manager.rb
activerecord/lib/arel/select_manager.rb
+7
-0
activerecord/lib/arel/visitors/depth_first.rb
activerecord/lib/arel/visitors/depth_first.rb
+1
-0
activerecord/lib/arel/visitors/dot.rb
activerecord/lib/arel/visitors/dot.rb
+1
-0
activerecord/lib/arel/visitors/ibm_db.rb
activerecord/lib/arel/visitors/ibm_db.rb
+12
-0
activerecord/lib/arel/visitors/informix.rb
activerecord/lib/arel/visitors/informix.rb
+5
-0
activerecord/lib/arel/visitors/mssql.rb
activerecord/lib/arel/visitors/mssql.rb
+13
-0
activerecord/lib/arel/visitors/to_sql.rb
activerecord/lib/arel/visitors/to_sql.rb
+13
-0
activerecord/test/cases/adapters/mysql2/optimizer_hints_test.rb
...record/test/cases/adapters/mysql2/optimizer_hints_test.rb
+24
-0
activerecord/test/cases/adapters/postgresql/optimizer_hints_test.rb
...rd/test/cases/adapters/postgresql/optimizer_hints_test.rb
+28
-0
activerecord/test/cases/helper.rb
activerecord/test/cases/helper.rb
+1
-0
activerecord/test/cases/invertible_migration_test.rb
activerecord/test/cases/invertible_migration_test.rb
+2
-0
未找到文件。
activerecord/CHANGELOG.md
浏览文件 @
1db05065
*
Support Optimizer Hints.
In most databases, there is a way to control the optimizer is by using optimizer hints,
which can be specified within individual statements.
Example (for MySQL):
Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
# SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
Example (for PostgreSQL with pg_hint_plan):
Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
# SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
See also:
* https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html
* https://pghintplan.osdn.jp/pg_hint_plan.html
* https://docs.oracle.com/en/database/oracle/oracle-database/12.2/tgsql/influencing-the-optimizer.html
* https://docs.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-query?view=sql-server-2017
* https://www.ibm.com/support/knowledgecenter/en/SSEPGG_11.1.0/com.ibm.db2.luw.admin.perf.doc/doc/c0070117.html
*Ryuta Kamizono*
*
Fix query attribute method on user-defined attribute to be aware of typecasted value.
For example, the following code no longer return false as casted non-empty string:
...
...
activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
浏览文件 @
1db05065
...
...
@@ -384,6 +384,11 @@ def supports_foreign_tables?
false
end
# Does this adapter support optimizer hints?
def
supports_optimizer_hints?
false
end
def
supports_lazy_transactions?
false
end
...
...
activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
浏览文件 @
1db05065
...
...
@@ -103,6 +103,11 @@ def supports_virtual_columns?
mariadb?
||
version
>=
"5.7.5"
end
# See https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html for more details.
def
supports_optimizer_hints?
!
mariadb?
&&
version
>=
"5.7.7"
end
def
supports_advisory_locks?
true
end
...
...
activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
浏览文件 @
1db05065
...
...
@@ -351,6 +351,13 @@ def supports_pgcrypto_uuid?
postgresql_version
>=
90400
end
def
supports_optimizer_hints?
unless
defined?
(
@has_pg_hint_plan
)
@has_pg_hint_plan
=
extension_available?
(
"pg_hint_plan"
)
end
@has_pg_hint_plan
end
def
supports_lazy_transactions?
true
end
...
...
@@ -381,9 +388,12 @@ def disable_extension(name)
}
end
def
extension_available?
(
name
)
query_value
(
"SELECT true FROM pg_available_extensions WHERE name =
#{
quote
(
name
)
}
"
,
"SCHEMA"
)
end
def
extension_enabled?
(
name
)
res
=
exec_query
(
"SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '
#{
name
}
' AND installed_version IS NOT NULL) as enabled"
,
"SCHEMA"
)
res
.
cast_values
.
first
query_value
(
"SELECT installed_version IS NOT NULL FROM pg_available_extensions WHERE name =
#{
quote
(
name
)
}
"
,
"SCHEMA"
)
end
def
extensions
...
...
activerecord/lib/active_record/querying.rb
浏览文件 @
1db05065
...
...
@@ -14,7 +14,7 @@ module Querying
:find_each
,
:find_in_batches
,
:in_batches
,
:select
,
:reselect
,
:order
,
:reorder
,
:group
,
:limit
,
:offset
,
:joins
,
:left_joins
,
:left_outer_joins
,
:where
,
:rewhere
,
:preload
,
:eager_load
,
:includes
,
:from
,
:lock
,
:readonly
,
:extending
,
:or
,
:having
,
:create_with
,
:distinct
,
:references
,
:none
,
:unscope
,
:merge
,
:except
,
:only
,
:having
,
:create_with
,
:distinct
,
:references
,
:none
,
:unscope
,
:
optimizer_hints
,
:
merge
,
:except
,
:only
,
:count
,
:average
,
:minimum
,
:maximum
,
:sum
,
:calculate
,
:pluck
,
:pick
,
:ids
].
freeze
# :nodoc:
...
...
activerecord/lib/active_record/relation.rb
浏览文件 @
1db05065
...
...
@@ -5,7 +5,7 @@ module ActiveRecord
class
Relation
MULTI_VALUE_METHODS
=
[
:includes
,
:eager_load
,
:preload
,
:select
,
:group
,
:order
,
:joins
,
:left_outer_joins
,
:references
,
:extending
,
:unscope
]
:extending
,
:unscope
,
:optimizer_hints
]
SINGLE_VALUE_METHODS
=
[
:limit
,
:offset
,
:lock
,
:readonly
,
:reordering
,
:reverse_order
,
:distinct
,
:create_with
,
:skip_query_cache
]
...
...
activerecord/lib/active_record/relation/query_methods.rb
浏览文件 @
1db05065
...
...
@@ -901,6 +901,29 @@ def extending!(*modules, &block) # :nodoc:
self
end
# Specify optimizer hints to be used in the SELECT statement.
#
# Example (for MySQL):
#
# Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
# # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
#
# Example (for PostgreSQL with pg_hint_plan):
#
# Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
# # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
def
optimizer_hints
(
*
args
)
check_if_method_has_arguments!
(
:optimizer_hints
,
args
)
spawn
.
optimizer_hints!
(
*
args
)
end
def
optimizer_hints!
(
*
args
)
# :nodoc:
args
.
flatten!
self
.
optimizer_hints_values
+=
args
self
end
# Reverse the existing order clause on the relation.
#
# User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
...
...
@@ -977,6 +1000,7 @@ def build_arel(aliases)
build_select
(
arel
)
arel
.
optimizer_hints
(
*
optimizer_hints_values
)
unless
optimizer_hints_values
.
empty?
arel
.
distinct
(
distinct_value
)
arel
.
from
(
build_from
)
unless
from_clause
.
empty?
arel
.
lock
(
lock_value
)
if
lock_value
...
...
activerecord/lib/arel/nodes/select_core.rb
浏览文件 @
1db05065
...
...
@@ -4,7 +4,7 @@ module Arel # :nodoc: all
module
Nodes
class
SelectCore
<
Arel
::
Nodes
::
Node
attr_accessor
:projections
,
:wheres
,
:groups
,
:windows
attr_accessor
:havings
,
:source
,
:set_quantifier
attr_accessor
:havings
,
:source
,
:set_quantifier
,
:optimizer_hints
def
initialize
super
()
...
...
@@ -42,7 +42,7 @@ def initialize_copy(other)
def
hash
[
@source
,
@set_quantifier
,
@projections
,
@source
,
@set_quantifier
,
@projections
,
@optimizer_hints
,
@wheres
,
@groups
,
@havings
,
@windows
].
hash
end
...
...
@@ -51,6 +51,7 @@ def eql?(other)
self
.
class
==
other
.
class
&&
self
.
source
==
other
.
source
&&
self
.
set_quantifier
==
other
.
set_quantifier
&&
self
.
optimizer_hints
==
other
.
optimizer_hints
&&
self
.
projections
==
other
.
projections
&&
self
.
wheres
==
other
.
wheres
&&
self
.
groups
==
other
.
groups
&&
...
...
activerecord/lib/arel/nodes/unary.rb
浏览文件 @
1db05065
...
...
@@ -35,6 +35,7 @@ def eql?(other)
Not
Offset
On
OptimizerHints
Ordering
RollUp
}
.
each
do
|
name
|
...
...
activerecord/lib/arel/select_manager.rb
浏览文件 @
1db05065
...
...
@@ -146,6 +146,13 @@ def projections=(projections)
@ctx
.
projections
=
projections
end
def
optimizer_hints
(
*
hints
)
unless
hints
.
empty?
@ctx
.
optimizer_hints
=
Arel
::
Nodes
::
OptimizerHints
.
new
(
hints
)
end
self
end
def
distinct
(
value
=
true
)
if
value
@ctx
.
set_quantifier
=
Arel
::
Nodes
::
Distinct
.
new
...
...
activerecord/lib/arel/visitors/depth_first.rb
浏览文件 @
1db05065
...
...
@@ -35,6 +35,7 @@ def unary(o)
alias
:visit_Arel_Nodes_Ascending
:unary
alias
:visit_Arel_Nodes_Descending
:unary
alias
:visit_Arel_Nodes_UnqualifiedColumn
:unary
alias
:visit_Arel_Nodes_OptimizerHints
:unary
def
function
(
o
)
visit
o
.
expressions
...
...
activerecord/lib/arel/visitors/dot.rb
浏览文件 @
1db05065
...
...
@@ -82,6 +82,7 @@ def unary(o)
alias
:visit_Arel_Nodes_Offset
:unary
alias
:visit_Arel_Nodes_On
:unary
alias
:visit_Arel_Nodes_UnqualifiedColumn
:unary
alias
:visit_Arel_Nodes_OptimizerHints
:unary
alias
:visit_Arel_Nodes_Preceding
:unary
alias
:visit_Arel_Nodes_Following
:unary
alias
:visit_Arel_Nodes_Rows
:unary
...
...
activerecord/lib/arel/visitors/ibm_db.rb
浏览文件 @
1db05065
...
...
@@ -4,6 +4,14 @@ module Arel # :nodoc: all
module
Visitors
class
IBM_DB
<
Arel
::
Visitors
::
ToSql
private
def
visit_Arel_Nodes_SelectCore
(
o
,
collector
)
collector
=
super
maybe_visit
o
.
optimizer_hints
,
collector
end
def
visit_Arel_Nodes_OptimizerHints
(
o
,
collector
)
collector
<<
"/* <OPTGUIDELINES>
#{
sanitize_as_sql_comment
(
o
).
join
}
</OPTGUIDELINES> */"
end
def
visit_Arel_Nodes_Limit
(
o
,
collector
)
collector
<<
"FETCH FIRST "
...
...
@@ -16,6 +24,10 @@ def is_distinct_from(o, collector)
collector
=
visit
[
o
.
left
,
o
.
right
,
0
,
1
],
collector
collector
<<
")"
end
def
collect_optimizer_hints
(
o
,
collector
)
collector
end
end
end
end
activerecord/lib/arel/visitors/informix.rb
浏览文件 @
1db05065
...
...
@@ -42,10 +42,15 @@ def visit_Arel_Nodes_SelectCore(o, collector)
collector
end
def
visit_Arel_Nodes_OptimizerHints
(
o
,
collector
)
collector
<<
"/*+
#{
sanitize_as_sql_comment
(
o
).
join
(
", "
)
}
*/"
end
def
visit_Arel_Nodes_Offset
(
o
,
collector
)
collector
<<
"SKIP "
visit
o
.
expr
,
collector
end
def
visit_Arel_Nodes_Limit
(
o
,
collector
)
collector
<<
"FIRST "
visit
o
.
expr
,
collector
...
...
activerecord/lib/arel/visitors/mssql.rb
浏览文件 @
1db05065
...
...
@@ -76,6 +76,15 @@ def visit_Arel_Nodes_SelectStatement(o, collector)
end
end
def
visit_Arel_Nodes_SelectCore
(
o
,
collector
)
collector
=
super
maybe_visit
o
.
optimizer_hints
,
collector
end
def
visit_Arel_Nodes_OptimizerHints
(
o
,
collector
)
collector
<<
"OPTION (
#{
sanitize_as_sql_comment
(
o
).
join
(
", "
)
}
)"
end
def
get_offset_limit_clause
(
o
)
first_row
=
o
.
offset
?
o
.
offset
.
expr
.
to_i
+
1
:
1
last_row
=
o
.
limit
?
o
.
limit
.
expr
.
to_i
-
1
+
first_row
:
nil
...
...
@@ -103,6 +112,10 @@ def visit_Arel_Nodes_DeleteStatement(o, collector)
end
end
def
collect_optimizer_hints
(
o
,
collector
)
collector
end
def
determine_order_by
(
orders
,
x
)
if
orders
.
any?
orders
...
...
activerecord/lib/arel/visitors/to_sql.rb
浏览文件 @
1db05065
...
...
@@ -219,6 +219,7 @@ def visit_Arel_Nodes_SelectOptions(o, collector)
def
visit_Arel_Nodes_SelectCore
(
o
,
collector
)
collector
<<
"SELECT"
collector
=
collect_optimizer_hints
(
o
,
collector
)
collector
=
maybe_visit
o
.
set_quantifier
,
collector
collect_nodes_for
o
.
projections
,
collector
,
SPACE
...
...
@@ -236,6 +237,10 @@ def visit_Arel_Nodes_SelectCore(o, collector)
collector
end
def
visit_Arel_Nodes_OptimizerHints
(
o
,
collector
)
collector
<<
"/*+
#{
sanitize_as_sql_comment
(
o
).
join
(
" "
)
}
*/"
end
def
collect_nodes_for
(
nodes
,
collector
,
spacer
,
connector
=
COMMA
)
unless
nodes
.
empty?
collector
<<
spacer
...
...
@@ -799,6 +804,14 @@ def quote_column_name(name)
@connection
.
quote_column_name
(
name
)
end
def
sanitize_as_sql_comment
(
o
)
o
.
expr
.
map
{
|
v
|
v
.
gsub
(
%r{ /
\*\+
?
\s
* |
\s
*
\*
/ }x
,
""
)
}
end
def
collect_optimizer_hints
(
o
,
collector
)
maybe_visit
o
.
optimizer_hints
,
collector
end
def
maybe_visit
(
thing
,
collector
)
return
collector
unless
thing
collector
<<
" "
...
...
activerecord/test/cases/adapters/mysql2/optimizer_hints_test.rb
0 → 100644
浏览文件 @
1db05065
# frozen_string_literal: true
require
"cases/helper"
require
"models/post"
if
supports_optimizer_hints?
class
Mysql2OptimzerHintsTest
<
ActiveRecord
::
Mysql2TestCase
fixtures
:posts
def
test_optimizer_hints
assert_sql
(
%r{
\A
SELECT /
\*\+
NO_RANGE_OPTIMIZATION
\(
posts index_posts_on_author_id
\)
\*
/}
)
do
posts
=
Post
.
optimizer_hints
(
"NO_RANGE_OPTIMIZATION(posts index_posts_on_author_id)"
)
posts
=
posts
.
select
(
:id
).
where
(
author_id:
[
0
,
1
])
assert_includes
posts
.
explain
,
"| index | index_posts_on_author_id | index_posts_on_author_id |"
end
assert_sql
(
%r{
\A
SELECT /
\*\+
NO_RANGE_OPTIMIZATION
\(
posts index_posts_on_author_id
\)
\*
/}
)
do
posts
=
Post
.
optimizer_hints
(
"/*+ NO_RANGE_OPTIMIZATION(posts index_posts_on_author_id) */"
)
posts
=
posts
.
select
(
:id
).
where
(
author_id:
[
0
,
1
])
assert_includes
posts
.
explain
,
"| index | index_posts_on_author_id | index_posts_on_author_id |"
end
end
end
end
activerecord/test/cases/adapters/postgresql/optimizer_hints_test.rb
0 → 100644
浏览文件 @
1db05065
# frozen_string_literal: true
require
"cases/helper"
require
"models/post"
if
supports_optimizer_hints?
class
PostgresqlOptimzerHintsTest
<
ActiveRecord
::
PostgreSQLTestCase
fixtures
:posts
def
setup
enable_extension!
(
"pg_hint_plan"
,
ActiveRecord
::
Base
.
connection
)
end
def
test_optimizer_hints
assert_sql
(
%r{
\A
SELECT /
\*\+
SeqScan
\(
posts
\)
\*
/}
)
do
posts
=
Post
.
optimizer_hints
(
"SeqScan(posts)"
)
posts
=
posts
.
select
(
:id
).
where
(
author_id:
[
0
,
1
])
assert_includes
posts
.
explain
,
"Seq Scan on posts"
end
assert_sql
(
%r{
\A
SELECT /
\*\+
SeqScan
\(
posts
\)
\*
/}
)
do
posts
=
Post
.
optimizer_hints
(
"/*+ SeqScan(posts) */"
)
posts
=
posts
.
select
(
:id
).
where
(
author_id:
[
0
,
1
])
assert_includes
posts
.
explain
,
"Seq Scan on posts"
end
end
end
end
activerecord/test/cases/helper.rb
浏览文件 @
1db05065
...
...
@@ -64,6 +64,7 @@ def supports_default_expression?
supports_insert_on_duplicate_skip?
supports_insert_on_duplicate_update?
supports_insert_conflict_target?
supports_optimizer_hints?
]
.
each
do
|
method_name
|
define_method
method_name
do
ActiveRecord
::
Base
.
connection
.
public_send
(
method_name
)
...
...
activerecord/test/cases/invertible_migration_test.rb
浏览文件 @
1db05065
...
...
@@ -308,6 +308,8 @@ def test_migrate_enable_and_disable_extension
migration2
=
DisableExtension1
.
new
migration3
=
DisableExtension2
.
new
assert_equal
true
,
Horse
.
connection
.
extension_available?
(
"hstore"
)
migration1
.
migrate
(
:up
)
migration2
.
migrate
(
:up
)
assert_equal
true
,
Horse
.
connection
.
extension_enabled?
(
"hstore"
)
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录