diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d3100aca..3bbd72aca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ #### Added - [#855](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/855) Add helpers to create/change/drop a schema. -- [#857](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/857) Included WAITFOR as read query type. +- [#857](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/857) Included WAITFOR as read query type. +- [#864](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/864) Implemented optimizer hints. ## v6.0.1 diff --git a/lib/active_record/connection_adapters/sqlserver_adapter.rb b/lib/active_record/connection_adapters/sqlserver_adapter.rb index 867a66001..9f5099346 100644 --- a/lib/active_record/connection_adapters/sqlserver_adapter.rb +++ b/lib/active_record/connection_adapters/sqlserver_adapter.rb @@ -176,6 +176,10 @@ def supports_insert_conflict_target? false end + def supports_optimizer_hints? + true + end + def disable_referential_integrity tables = tables_with_referential_integrity tables.each { |t| do_execute "ALTER TABLE #{quote_table_name(t)} NOCHECK CONSTRAINT ALL" } diff --git a/lib/arel/visitors/sqlserver.rb b/lib/arel/visitors/sqlserver.rb index 680ee8be5..3cb3b0338 100644 --- a/lib/arel/visitors/sqlserver.rb +++ b/lib/arel/visitors/sqlserver.rb @@ -144,6 +144,20 @@ def collect_in_clause(left, right, collector) super end + def visit_Arel_Nodes_SelectCore(o, collector) + collector = super + maybe_visit o.optimizer_hints, collector + end + + def visit_Arel_Nodes_OptimizerHints(o, collector) + hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(", ") + collector << "OPTION (#{hints})" + end + + def collect_optimizer_hints(o, collector) + collector + end + # SQLServer ToSql/Visitor (Additions) def visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, options = {} diff --git a/test/cases/optimizer_hints_test_sqlserver.rb b/test/cases/optimizer_hints_test_sqlserver.rb new file mode 100644 index 000000000..3a1782bc8 --- /dev/null +++ b/test/cases/optimizer_hints_test_sqlserver.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "cases/helper_sqlserver" +require "models/post" + +class OptimizerHintsTestSQLServer < ActiveRecord::TestCase + fixtures :posts + + def test_optimizer_hints + expected_sql = "SELECT [posts].* FROM [posts] OPTION (MAXDOP 2)" + current_sql = Post.optimizer_hints("MAXDOP 2").to_sql + assert_equal expected_sql, current_sql + end + + def test_multiple_optimizer_hints + expected_sql = "SELECT [posts].* FROM [posts] OPTION (MAXDOP 2, KEEPFIXED PLAN)" + current_sql = Post.optimizer_hints("MAXDOP 2").optimizer_hints("KEEPFIXED PLAN").to_sql + assert_equal expected_sql, current_sql + end + + def test_optimizer_hints_with_count_subquery + assert_sql(%r{.*'SELECT COUNT\(count_column\) FROM \(SELECT .*\) subquery_for_count OPTION \(MAXDOP 2\)'.*}) do + posts = Post.optimizer_hints("MAXDOP 2") + posts = posts.select(:id).where(author_id: [0, 1]).limit(5) + assert_equal 5, posts.count + end + end + + def test_optimizer_hints_with_unscope + expected_sql = "SELECT [posts].* FROM [posts]" + current_sql = Post.optimizer_hints("MAXDOP 2").unscope(:optimizer_hints).to_sql + assert_equal expected_sql, current_sql + end +end