Skip to content

Commit 9f99467

Browse files
committed
Fix explain with array conditions
1 parent dc4ca14 commit 9f99467

File tree

2 files changed

+26
-18
lines changed

2 files changed

+26
-18
lines changed

lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb

+14-18
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ module CoreExt
55
module Explain
66

77
SQLSERVER_STATEMENT_PREFIX = 'EXEC sp_executesql '.freeze
8-
SQLSERVER_PARAM_MATCHER = /@\d+ = (.*)/
9-
SQLSERVER_NATIONAL_STRING_MATCHER = /N'(.*)'/m
8+
SQLSERVER_STATEMENT_REGEXP = /N'(.+)', N'(.+)', (.+)/
109

1110
def exec_explain(queries)
1211
unprepared_queries = queries.map do |(sql, binds)|
13-
[unprepare_sqlserver_statement(sql), binds]
12+
[unprepare_sqlserver_statement(sql, binds), binds]
1413
end
1514
super(unprepared_queries)
1615
end
@@ -19,22 +18,19 @@ def exec_explain(queries)
1918

2019
# This is somewhat hacky, but it should reliably reformat our prepared sql statment
2120
# which uses sp_executesql to just the first argument, then unquote it. Likewise our
22-
# `sp_executesql` method should substitude the @n args withe the quoted values.
23-
def unprepare_sqlserver_statement(sql)
24-
if sql.starts_with?(SQLSERVER_STATEMENT_PREFIX)
25-
executesql = sql.from(SQLSERVER_STATEMENT_PREFIX.length)
26-
args = executesql.split(', ')
27-
unprepared_sql = args.shift.strip.match(SQLSERVER_NATIONAL_STRING_MATCHER)[1]
28-
unprepared_sql = Utils.unquote_string(unprepared_sql)
29-
args = args.from(args.length / 2)
30-
args.each_with_index do |arg, index|
31-
value = arg.match(SQLSERVER_PARAM_MATCHER)[1]
32-
unprepared_sql.sub! "@#{index}", value
33-
end
34-
unprepared_sql
35-
else
36-
sql
21+
# `sp_executesql` method should substitude the @n args with the quoted values.
22+
def unprepare_sqlserver_statement(sql, binds)
23+
return sql unless sql.starts_with?(SQLSERVER_STATEMENT_PREFIX)
24+
25+
executesql = sql.from(SQLSERVER_STATEMENT_PREFIX.length)
26+
executesql = executesql.match(SQLSERVER_STATEMENT_REGEXP).to_a[1]
27+
28+
binds.each_with_index do |bind, index|
29+
value = connection.quote(bind)
30+
executesql = executesql.sub("@#{index}", value)
3731
end
32+
33+
executesql
3834
end
3935

4036
end

test/cases/showplan_test_sqlserver.rb

+12
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ class ShowplanTestSQLServer < ActiveRecord::TestCase
2626
plan.must_include "Clustered Index Scan", 'make sure we do not showplan the sp_executesql'
2727
end
2828

29+
it 'from array condition using index' do
30+
plan = Car.where(id: [1, 2]).explain
31+
plan.must_include " SELECT [cars].* FROM [cars] WHERE [cars].[id] IN (1, 2)"
32+
plan.must_include "Clustered Index Seek", 'make sure we do not showplan the sp_executesql'
33+
end
34+
35+
it 'from array condition' do
36+
plan = Car.where(name: ['honda', 'zyke']).explain
37+
plan.must_include " SELECT [cars].* FROM [cars] WHERE [cars].[name] IN (N'honda', N'zyke')"
38+
plan.must_include "Clustered Index Scan", 'make sure we do not showplan the sp_executesql'
39+
end
40+
2941
end
3042

3143
describe 'With SHOWPLAN_TEXT option' do

0 commit comments

Comments
 (0)