activerecord-sqlserver-adapter 8.0.9 → 8.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/VERSION +1 -1
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +5 -9
- data/test/cases/coerced_tests.rb +32 -183
- data/test/cases/optimizer_hints_test_sqlserver.rb +1 -1
- data/test/cases/showplan_test_sqlserver.rb +2 -2
- data/test/cases/specific_schema_test_sqlserver.rb +6 -6
- data/test/support/query_assertions.rb +22 -0
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e6c3e4f4ea64ada810516060935068217cc32d43b160f33fec283e6a81889168
|
|
4
|
+
data.tar.gz: efa5b8818e45ca35a610a850ed44253f4682f83267a46822b23604fd78655e94
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9e8f1d1a400b6a624d22ae77f6b8d70a31e967790cdd30be86d476f94af7e45ea913d5278a02fddebcd4ee30749c0277c4624e61b6f4ec521403a966f5dc2f96
|
|
7
|
+
data.tar.gz: 78ff679e5aea744e81da238916812c364b7fef69e6b7e4cc2bbb4a59f96d256cd62f70ca886504fe815723aa12d2e91efef88793c92cbe6c494f58dc78f3c041
|
data/CHANGELOG.md
CHANGED
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
8.0.
|
|
1
|
+
8.0.10
|
|
@@ -14,6 +14,11 @@ module ActiveRecord
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch:)
|
|
17
|
+
unless binds.nil? || binds.empty?
|
|
18
|
+
types, params = sp_executesql_types_and_parameters(binds)
|
|
19
|
+
sql = sp_executesql_sql(sql, types, params, notification_payload[:name])
|
|
20
|
+
end
|
|
21
|
+
|
|
17
22
|
result = if id_insert_table_name = query_requires_identity_insert?(sql)
|
|
18
23
|
with_identity_insert_enabled(id_insert_table_name, raw_connection) do
|
|
19
24
|
internal_exec_sql_query(sql, raw_connection)
|
|
@@ -40,15 +45,6 @@ module ActiveRecord
|
|
|
40
45
|
raw_result.first[column_name]
|
|
41
46
|
end
|
|
42
47
|
|
|
43
|
-
def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false)
|
|
44
|
-
unless binds.nil? || binds.empty?
|
|
45
|
-
types, params = sp_executesql_types_and_parameters(binds)
|
|
46
|
-
sql = sp_executesql_sql(sql, types, params, name)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
super
|
|
50
|
-
end
|
|
51
|
-
|
|
52
48
|
def internal_exec_sql_query(sql, conn)
|
|
53
49
|
handle = internal_raw_execute(sql, conn)
|
|
54
50
|
handle_to_names_and_values(handle, ar_result: true)
|
data/test/cases/coerced_tests.rb
CHANGED
|
@@ -248,7 +248,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
|
|
|
248
248
|
def test_belongs_to_coerced
|
|
249
249
|
client = Client.find(3)
|
|
250
250
|
first_firm = companies(:first_firm)
|
|
251
|
-
|
|
251
|
+
assert_queries_and_values_match(/FETCH NEXT @3 ROWS ONLY/, ['Firm', 'Agency', 1, 1]) do
|
|
252
252
|
assert_equal first_firm, client.firm
|
|
253
253
|
assert_equal first_firm.name, client.firm.name
|
|
254
254
|
end
|
|
@@ -257,21 +257,6 @@ end
|
|
|
257
257
|
|
|
258
258
|
module ActiveRecord
|
|
259
259
|
class BindParameterTest < ActiveRecord::TestCase
|
|
260
|
-
# Same as original coerced test except log is found using `EXEC sp_executesql` wrapper.
|
|
261
|
-
coerce_tests! :test_binds_are_logged
|
|
262
|
-
def test_binds_are_logged_coerced
|
|
263
|
-
sub = Arel::Nodes::BindParam.new(1)
|
|
264
|
-
binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
|
|
265
|
-
sql = "select * from topics where id = #{sub.to_sql}"
|
|
266
|
-
|
|
267
|
-
@connection.exec_query(sql, "SQL", binds)
|
|
268
|
-
|
|
269
|
-
logged_sql = "EXEC sp_executesql N'#{sql}', N'#{sub.to_sql} int', #{sub.to_sql} = 1"
|
|
270
|
-
message = @subscriber.calls.find { |args| args[4][:sql] == logged_sql }
|
|
271
|
-
|
|
272
|
-
assert_equal binds, message[4][:binds]
|
|
273
|
-
end
|
|
274
|
-
|
|
275
260
|
# SQL Server adapter does not use a statement cache as query plans are already reused using `EXEC sp_executesql`.
|
|
276
261
|
coerce_tests! :test_statement_cache
|
|
277
262
|
coerce_tests! :test_statement_cache_with_query_cache
|
|
@@ -279,55 +264,6 @@ module ActiveRecord
|
|
|
279
264
|
coerce_tests! :test_statement_cache_with_find_by
|
|
280
265
|
coerce_tests! :test_statement_cache_with_in_clause
|
|
281
266
|
coerce_tests! :test_statement_cache_with_sql_string_literal
|
|
282
|
-
|
|
283
|
-
# Same as original coerced test except prepared statements include `EXEC sp_executesql` wrapper.
|
|
284
|
-
coerce_tests! :test_bind_params_to_sql_with_prepared_statements, :test_bind_params_to_sql_with_unprepared_statements
|
|
285
|
-
def test_bind_params_to_sql_with_prepared_statements_coerced
|
|
286
|
-
assert_bind_params_to_sql_coerced(prepared: true)
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
def test_bind_params_to_sql_with_unprepared_statements_coerced
|
|
290
|
-
@connection.unprepared_statement do
|
|
291
|
-
assert_bind_params_to_sql_coerced(prepared: false)
|
|
292
|
-
end
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
private
|
|
296
|
-
|
|
297
|
-
def assert_bind_params_to_sql_coerced(prepared:)
|
|
298
|
-
table = Author.quoted_table_name
|
|
299
|
-
pk = "#{table}.#{Author.quoted_primary_key}"
|
|
300
|
-
|
|
301
|
-
# prepared_statements: true
|
|
302
|
-
#
|
|
303
|
-
# EXEC sp_executesql N'SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (@0, @1, @2) OR [authors].[id] IS NULL)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3
|
|
304
|
-
#
|
|
305
|
-
# prepared_statements: false
|
|
306
|
-
#
|
|
307
|
-
# SELECT [authors].* FROM [authors] WHERE ([authors].[id] IN (1, 2, 3) OR [authors].[id] IS NULL)
|
|
308
|
-
#
|
|
309
|
-
sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE (#{pk} IN (#{bind_params(1..3)}) OR #{pk} IS NULL)"
|
|
310
|
-
sql_prepared = "EXEC sp_executesql N'SELECT #{table}.* FROM #{table} WHERE (#{pk} IN (#{bind_params(1..3)}) OR #{pk} IS NULL)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3"
|
|
311
|
-
|
|
312
|
-
authors = Author.where(id: [1, 2, 3, nil])
|
|
313
|
-
assert_equal sql_unprepared, @connection.to_sql(authors.arel)
|
|
314
|
-
assert_queries_match(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
|
|
315
|
-
|
|
316
|
-
# prepared_statements: true
|
|
317
|
-
#
|
|
318
|
-
# EXEC sp_executesql N'SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (@0, @1, @2)', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3
|
|
319
|
-
#
|
|
320
|
-
# prepared_statements: false
|
|
321
|
-
#
|
|
322
|
-
# SELECT [authors].* FROM [authors] WHERE [authors].[id] IN (1, 2, 3)
|
|
323
|
-
#
|
|
324
|
-
sql_unprepared = "SELECT #{table}.* FROM #{table} WHERE #{pk} IN (#{bind_params(1..3)})"
|
|
325
|
-
sql_prepared = "EXEC sp_executesql N'SELECT #{table}.* FROM #{table} WHERE #{pk} IN (#{bind_params(1..3)})', N'@0 bigint, @1 bigint, @2 bigint', @0 = 1, @1 = 2, @2 = 3"
|
|
326
|
-
|
|
327
|
-
authors = Author.where(id: [1, 2, 3, 9223372036854775808])
|
|
328
|
-
assert_equal sql_unprepared, @connection.to_sql(authors.arel)
|
|
329
|
-
assert_queries_match(prepared ? sql_prepared : sql_unprepared) { assert_equal 3, authors.length }
|
|
330
|
-
end
|
|
331
267
|
end
|
|
332
268
|
end
|
|
333
269
|
|
|
@@ -391,7 +327,7 @@ class CalculationsTest < ActiveRecord::TestCase
|
|
|
391
327
|
assert_predicate accounts, :loaded?
|
|
392
328
|
assert_equal expected, accounts.count(:id)
|
|
393
329
|
end
|
|
394
|
-
|
|
330
|
+
|
|
395
331
|
# Fix randomly failing test. The loading of the model's schema was affecting the test.
|
|
396
332
|
coerce_tests! :test_offset_is_kept
|
|
397
333
|
def test_offset_is_kept_coerced
|
|
@@ -512,7 +448,7 @@ class CalculationsTest < ActiveRecord::TestCase
|
|
|
512
448
|
def test_limit_is_kept_coerced
|
|
513
449
|
queries = capture_sql { Account.limit(1).count }
|
|
514
450
|
assert_equal 1, queries.length
|
|
515
|
-
assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY
|
|
451
|
+
assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/, queries.first)
|
|
516
452
|
end
|
|
517
453
|
|
|
518
454
|
# Match SQL Server limit implementation
|
|
@@ -520,7 +456,7 @@ class CalculationsTest < ActiveRecord::TestCase
|
|
|
520
456
|
def test_limit_with_offset_is_kept_coerced
|
|
521
457
|
queries = capture_sql { Account.limit(1).offset(1).count }
|
|
522
458
|
assert_equal 1, queries.length
|
|
523
|
-
assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY
|
|
459
|
+
assert_match(/ORDER BY \[accounts\]\.\[id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY/, queries.first)
|
|
524
460
|
end
|
|
525
461
|
|
|
526
462
|
# SQL Server needs an alias for the calculated column
|
|
@@ -987,9 +923,9 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
987
923
|
# Assert SQL Server limit implementation
|
|
988
924
|
coerce_tests! :test_take_and_first_and_last_with_integer_should_use_sql_limit
|
|
989
925
|
def test_take_and_first_and_last_with_integer_should_use_sql_limit_coerced
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
926
|
+
assert_queries_and_values_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/, [3]) { Topic.take(3).entries }
|
|
927
|
+
assert_queries_and_values_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/, [2]) { Topic.first(2).entries }
|
|
928
|
+
assert_queries_and_values_match(/OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/, [5]) { Topic.last(5).entries }
|
|
993
929
|
end
|
|
994
930
|
|
|
995
931
|
# This fails only when run in the full test suite task. Just taking it out of the mix.
|
|
@@ -1020,7 +956,7 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
1020
956
|
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1021
957
|
coerce_tests! :test_include_on_unloaded_relation_with_match
|
|
1022
958
|
def test_include_on_unloaded_relation_with_match_coerced
|
|
1023
|
-
assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY
|
|
959
|
+
assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY/) do
|
|
1024
960
|
assert_equal true, Customer.where(name: "David").include?(customers(:david))
|
|
1025
961
|
end
|
|
1026
962
|
end
|
|
@@ -1028,7 +964,7 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
1028
964
|
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1029
965
|
coerce_tests! :test_include_on_unloaded_relation_without_match
|
|
1030
966
|
def test_include_on_unloaded_relation_without_match_coerced
|
|
1031
|
-
assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY
|
|
967
|
+
assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY/) do
|
|
1032
968
|
assert_equal false, Customer.where(name: "David").include?(customers(:mary))
|
|
1033
969
|
end
|
|
1034
970
|
end
|
|
@@ -1036,7 +972,7 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
1036
972
|
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1037
973
|
coerce_tests! :test_member_on_unloaded_relation_with_match
|
|
1038
974
|
def test_member_on_unloaded_relation_with_match_coerced
|
|
1039
|
-
assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY
|
|
975
|
+
assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY/) do
|
|
1040
976
|
assert_equal true, Customer.where(name: "David").member?(customers(:david))
|
|
1041
977
|
end
|
|
1042
978
|
end
|
|
@@ -1044,7 +980,7 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
1044
980
|
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1045
981
|
coerce_tests! :test_member_on_unloaded_relation_without_match
|
|
1046
982
|
def test_member_on_unloaded_relation_without_match_coerced
|
|
1047
|
-
assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY
|
|
983
|
+
assert_queries_match(/1 AS one.*FETCH NEXT @2 ROWS ONLY/) do
|
|
1048
984
|
assert_equal false, Customer.where(name: "David").member?(customers(:mary))
|
|
1049
985
|
end
|
|
1050
986
|
end
|
|
@@ -1059,7 +995,7 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
1059
995
|
assert_equal topics(:third), Topic.last
|
|
1060
996
|
|
|
1061
997
|
c = Topic.lease_connection
|
|
1062
|
-
|
|
998
|
+
assert_queries_and_values_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.title"))} DESC, #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, [1]) {
|
|
1063
999
|
Topic.last
|
|
1064
1000
|
}
|
|
1065
1001
|
ensure
|
|
@@ -1073,7 +1009,7 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
1073
1009
|
Topic.implicit_order_column = "id"
|
|
1074
1010
|
|
|
1075
1011
|
c = Topic.lease_connection
|
|
1076
|
-
|
|
1012
|
+
assert_queries_and_values_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, [1]) {
|
|
1077
1013
|
Topic.last
|
|
1078
1014
|
}
|
|
1079
1015
|
ensure
|
|
@@ -1088,7 +1024,7 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
1088
1024
|
|
|
1089
1025
|
c = NonPrimaryKey.lease_connection
|
|
1090
1026
|
|
|
1091
|
-
|
|
1027
|
+
assert_queries_and_values_match(/ORDER BY #{Regexp.escape(c.quote_table_name("non_primary_keys.created_at"))} DESC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY/i, [1]) {
|
|
1092
1028
|
NonPrimaryKey.last
|
|
1093
1029
|
}
|
|
1094
1030
|
ensure
|
|
@@ -1098,7 +1034,7 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
1098
1034
|
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1099
1035
|
coerce_tests! :test_member_on_unloaded_relation_with_composite_primary_key
|
|
1100
1036
|
def test_member_on_unloaded_relation_with_composite_primary_key_coerced
|
|
1101
|
-
assert_queries_match(/1 AS one.* FETCH NEXT @
|
|
1037
|
+
assert_queries_match(/1 AS one.* FETCH NEXT @3 ROWS ONLY/) do
|
|
1102
1038
|
book = cpk_books(:cpk_great_author_first_book)
|
|
1103
1039
|
assert Cpk::Book.where(title: "The first book").member?(book)
|
|
1104
1040
|
end
|
|
@@ -1113,7 +1049,7 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
1113
1049
|
quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
|
|
1114
1050
|
quoted_descrption = Regexp.escape(c.quote_table_name("clothing_items.description"))
|
|
1115
1051
|
|
|
1116
|
-
assert_queries_match(/ORDER BY #{quoted_descrption} ASC, #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY
|
|
1052
|
+
assert_queries_match(/ORDER BY #{quoted_descrption} ASC, #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY/i) do
|
|
1117
1053
|
assert_kind_of ClothingItem, ClothingItem.first
|
|
1118
1054
|
end
|
|
1119
1055
|
ensure
|
|
@@ -1127,7 +1063,7 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
1127
1063
|
quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
|
|
1128
1064
|
quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
|
|
1129
1065
|
|
|
1130
|
-
assert_queries_match(/ORDER BY #{quoted_type} DESC, #{quoted_color} DESC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY
|
|
1066
|
+
assert_queries_match(/ORDER BY #{quoted_type} DESC, #{quoted_color} DESC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY/i) do
|
|
1131
1067
|
assert_kind_of ClothingItem, ClothingItem.last
|
|
1132
1068
|
end
|
|
1133
1069
|
end
|
|
@@ -1139,7 +1075,7 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
1139
1075
|
quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
|
|
1140
1076
|
quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
|
|
1141
1077
|
|
|
1142
|
-
assert_queries_match(/ORDER BY #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY
|
|
1078
|
+
assert_queries_match(/ORDER BY #{quoted_type} ASC, #{quoted_color} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY/i) do
|
|
1143
1079
|
assert_kind_of ClothingItem, ClothingItem.first
|
|
1144
1080
|
end
|
|
1145
1081
|
end
|
|
@@ -1152,7 +1088,7 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
1152
1088
|
quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
|
|
1153
1089
|
quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
|
|
1154
1090
|
|
|
1155
|
-
assert_queries_match(/ORDER BY #{quoted_color} ASC, #{quoted_type} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY
|
|
1091
|
+
assert_queries_match(/ORDER BY #{quoted_color} ASC, #{quoted_type} ASC OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY/i) do
|
|
1156
1092
|
assert_kind_of ClothingItem, ClothingItem.first
|
|
1157
1093
|
end
|
|
1158
1094
|
ensure
|
|
@@ -1162,7 +1098,7 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
1162
1098
|
# Check for `FETCH NEXT x ROWS` rather then `LIMIT`.
|
|
1163
1099
|
coerce_tests! :test_include_on_unloaded_relation_with_composite_primary_key
|
|
1164
1100
|
def test_include_on_unloaded_relation_with_composite_primary_key_coerced
|
|
1165
|
-
assert_queries_match(/1 AS one.*OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY
|
|
1101
|
+
assert_queries_match(/1 AS one.*OFFSET 0 ROWS FETCH NEXT @(\d) ROWS ONLY/) do
|
|
1166
1102
|
book = cpk_books(:cpk_great_author_first_book)
|
|
1167
1103
|
assert Cpk::Book.where(title: "The first book").include?(book)
|
|
1168
1104
|
end
|
|
@@ -1172,11 +1108,11 @@ class FinderTest < ActiveRecord::TestCase
|
|
|
1172
1108
|
coerce_tests! :test_nth_to_last_with_order_uses_limit
|
|
1173
1109
|
def test_nth_to_last_with_order_uses_limit_coerced
|
|
1174
1110
|
c = Topic.lease_connection
|
|
1175
|
-
assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET @(\d) ROWS FETCH NEXT @(\d) ROWS ONLY
|
|
1111
|
+
assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC OFFSET @(\d) ROWS FETCH NEXT @(\d) ROWS ONLY/i) do
|
|
1176
1112
|
Topic.second_to_last
|
|
1177
1113
|
end
|
|
1178
1114
|
|
|
1179
|
-
assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.updated_at"))} DESC OFFSET @(\d) ROWS FETCH NEXT @(\d) ROWS ONLY
|
|
1115
|
+
assert_queries_match(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.updated_at"))} DESC OFFSET @(\d) ROWS FETCH NEXT @(\d) ROWS ONLY/i) do
|
|
1180
1116
|
Topic.order(:updated_at).second_to_last
|
|
1181
1117
|
end
|
|
1182
1118
|
end
|
|
@@ -1227,7 +1163,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
|
|
|
1227
1163
|
def test_has_one_coerced
|
|
1228
1164
|
firm = companies(:first_firm)
|
|
1229
1165
|
first_account = Account.find(1)
|
|
1230
|
-
assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY
|
|
1166
|
+
assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY/) do
|
|
1231
1167
|
assert_equal first_account, firm.account
|
|
1232
1168
|
assert_equal first_account.credit_limit, firm.account.credit_limit
|
|
1233
1169
|
end
|
|
@@ -1239,7 +1175,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
|
|
|
1239
1175
|
coerce_tests! :test_has_one_through_executes_limited_query
|
|
1240
1176
|
def test_has_one_through_executes_limited_query_coerced
|
|
1241
1177
|
boring_club = clubs(:boring_club)
|
|
1242
|
-
assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY
|
|
1178
|
+
assert_queries_match(/FETCH NEXT @(\d) ROWS ONLY/) do
|
|
1243
1179
|
assert_equal boring_club, @member.general_club
|
|
1244
1180
|
end
|
|
1245
1181
|
end
|
|
@@ -1460,7 +1396,7 @@ class RelationTest < ActiveRecord::TestCase
|
|
|
1460
1396
|
# Find any limit via our expression.
|
|
1461
1397
|
coerce_tests! %r{relations don't load all records in #inspect}
|
|
1462
1398
|
def test_relations_dont_load_all_records_in_inspect_coerced
|
|
1463
|
-
assert_queries_match(/NEXT @0 ROWS
|
|
1399
|
+
assert_queries_match(/NEXT @0 ROWS/) do
|
|
1464
1400
|
Post.all.inspect
|
|
1465
1401
|
end
|
|
1466
1402
|
end
|
|
@@ -1568,10 +1504,10 @@ class SchemaDumperTest < ActiveRecord::TestCase
|
|
|
1568
1504
|
|
|
1569
1505
|
schema_info = ActiveRecord::Base.lease_connection.dump_schema_information
|
|
1570
1506
|
expected = <<~STR
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1507
|
+
INSERT INTO #{ActiveRecord::Base.lease_connection.quote_table_name("schema_migrations")} (version) VALUES
|
|
1508
|
+
(N'20100301010101'),
|
|
1509
|
+
(N'20100201010101'),
|
|
1510
|
+
(N'20100101010101');
|
|
1575
1511
|
STR
|
|
1576
1512
|
assert_equal expected.strip, schema_info
|
|
1577
1513
|
ensure
|
|
@@ -2148,7 +2084,7 @@ class RelationMergingTest < ActiveRecord::TestCase
|
|
|
2148
2084
|
non_mary_and_bob = Author.where.not(id: [mary, bob])
|
|
2149
2085
|
|
|
2150
2086
|
author_id = Author.lease_connection.quote_table_name("authors.id")
|
|
2151
|
-
assert_queries_match(/WHERE #{Regexp.escape(author_id)} NOT IN \((@\d), \g<1>\)
|
|
2087
|
+
assert_queries_match(/WHERE #{Regexp.escape(author_id)} NOT IN \((@\d), \g<1>\)/) do
|
|
2152
2088
|
assert_equal [david], non_mary_and_bob.merge(non_mary_and_bob)
|
|
2153
2089
|
end
|
|
2154
2090
|
|
|
@@ -2313,7 +2249,7 @@ class PreloaderTest < ActiveRecord::TestCase
|
|
|
2313
2249
|
|
|
2314
2250
|
c = Cpk::OrderAgreement.lease_connection
|
|
2315
2251
|
order_id_column = Regexp.escape(c.quote_table_name("cpk_order_agreements.order_id"))
|
|
2316
|
-
order_id_constraint = /#{order_id_column} = @0
|
|
2252
|
+
order_id_constraint = /#{order_id_column} = @0$/
|
|
2317
2253
|
expectation = /SELECT.*WHERE.* #{order_id_constraint}/
|
|
2318
2254
|
|
|
2319
2255
|
assert_match(expectation, preload_sql)
|
|
@@ -2337,7 +2273,7 @@ class PreloaderTest < ActiveRecord::TestCase
|
|
|
2337
2273
|
|
|
2338
2274
|
c = Cpk::Order.lease_connection
|
|
2339
2275
|
order_id = Regexp.escape(c.quote_table_name("cpk_orders.id"))
|
|
2340
|
-
order_constraint = /#{order_id} = @0
|
|
2276
|
+
order_constraint = /#{order_id} = @0$/
|
|
2341
2277
|
expectation = /SELECT.*WHERE.* #{order_constraint}/
|
|
2342
2278
|
|
|
2343
2279
|
assert_match(expectation, preload_sql)
|
|
@@ -2404,66 +2340,6 @@ end
|
|
|
2404
2340
|
|
|
2405
2341
|
require "models/dashboard"
|
|
2406
2342
|
class QueryLogsTest < ActiveRecord::TestCase
|
|
2407
|
-
# SQL requires double single-quotes.
|
|
2408
|
-
coerce_tests! :test_sql_commenter_format
|
|
2409
|
-
def test_sql_commenter_format_coerced
|
|
2410
|
-
ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
|
|
2411
|
-
ActiveRecord::QueryLogs.tags = [:application]
|
|
2412
|
-
|
|
2413
|
-
assert_queries_match(%r{/\*application=''active_record''\*/}) do
|
|
2414
|
-
Dashboard.first
|
|
2415
|
-
end
|
|
2416
|
-
end
|
|
2417
|
-
|
|
2418
|
-
# SQL requires double single-quotes.
|
|
2419
|
-
coerce_tests! :test_sqlcommenter_format_value
|
|
2420
|
-
def test_sqlcommenter_format_value_coerced
|
|
2421
|
-
ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
|
|
2422
|
-
|
|
2423
|
-
ActiveRecord::QueryLogs.tags = [
|
|
2424
|
-
:application,
|
|
2425
|
-
{ tracestate: "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7", custom_proc: -> { "Joe's Shack" } },
|
|
2426
|
-
]
|
|
2427
|
-
|
|
2428
|
-
assert_queries_match(%r{custom_proc=''Joe%27s%20Shack'',tracestate=''congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7''\*/}) do
|
|
2429
|
-
Dashboard.first
|
|
2430
|
-
end
|
|
2431
|
-
end
|
|
2432
|
-
|
|
2433
|
-
# SQL requires double single-quotes.
|
|
2434
|
-
coerce_tests! :test_sqlcommenter_format_value_string_coercible
|
|
2435
|
-
def test_sqlcommenter_format_value_string_coercible_coerced
|
|
2436
|
-
ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
|
|
2437
|
-
|
|
2438
|
-
ActiveRecord::QueryLogs.tags = [
|
|
2439
|
-
:application,
|
|
2440
|
-
{ custom_proc: -> { 1234 } },
|
|
2441
|
-
]
|
|
2442
|
-
|
|
2443
|
-
assert_queries_match(%r{custom_proc=''1234''\*/}) do
|
|
2444
|
-
Dashboard.first
|
|
2445
|
-
end
|
|
2446
|
-
end
|
|
2447
|
-
|
|
2448
|
-
# SQL requires double single-quotes.
|
|
2449
|
-
coerce_tests! :test_sqlcommenter_format_allows_string_keys
|
|
2450
|
-
def test_sqlcommenter_format_allows_string_keys_coerced
|
|
2451
|
-
ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
|
|
2452
|
-
|
|
2453
|
-
ActiveRecord::QueryLogs.tags = [
|
|
2454
|
-
:application,
|
|
2455
|
-
{
|
|
2456
|
-
"string" => "value",
|
|
2457
|
-
tracestate: "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7",
|
|
2458
|
-
custom_proc: -> { "Joe's Shack" }
|
|
2459
|
-
},
|
|
2460
|
-
]
|
|
2461
|
-
|
|
2462
|
-
assert_queries_match(%r{custom_proc=''Joe%27s%20Shack'',string=''value'',tracestate=''congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7''\*/}) do
|
|
2463
|
-
Dashboard.first
|
|
2464
|
-
end
|
|
2465
|
-
end
|
|
2466
|
-
|
|
2467
2343
|
# Invalid character encoding causes `ActiveRecord::StatementInvalid` error similar to Postgres.
|
|
2468
2344
|
coerce_tests! :test_invalid_encoding_query
|
|
2469
2345
|
def test_invalid_encoding_query_coerced
|
|
@@ -2712,33 +2588,6 @@ module ActiveRecord
|
|
|
2712
2588
|
end
|
|
2713
2589
|
end
|
|
2714
2590
|
|
|
2715
|
-
require "models/car"
|
|
2716
|
-
class ExplainTest < ActiveRecord::TestCase
|
|
2717
|
-
# Expected query slightly different from because of 'sp_executesql' and query parameters.
|
|
2718
|
-
coerce_tests! :test_relation_explain_with_first
|
|
2719
|
-
def test_relation_explain_with_first_coerced
|
|
2720
|
-
expected_query = capture_sql {
|
|
2721
|
-
Car.all.first
|
|
2722
|
-
}.first[/EXEC sp_executesql N'(.*?) NEXT/, 1]
|
|
2723
|
-
message = Car.all.explain.first
|
|
2724
|
-
assert_match(/^EXPLAIN/, message)
|
|
2725
|
-
assert_match(expected_query, message)
|
|
2726
|
-
end
|
|
2727
|
-
|
|
2728
|
-
# Expected query slightly different from because of 'sp_executesql' and query parameters.
|
|
2729
|
-
coerce_tests! :test_relation_explain_with_last
|
|
2730
|
-
def test_relation_explain_with_last_coerced
|
|
2731
|
-
expected_query = capture_sql {
|
|
2732
|
-
Car.all.last
|
|
2733
|
-
}.first[/EXEC sp_executesql N'(.*?) NEXT/, 1]
|
|
2734
|
-
expected_query = expected_query
|
|
2735
|
-
message = Car.all.explain.last
|
|
2736
|
-
|
|
2737
|
-
assert_match(/^EXPLAIN/, message)
|
|
2738
|
-
assert_match(expected_query, message)
|
|
2739
|
-
end
|
|
2740
|
-
end
|
|
2741
|
-
|
|
2742
2591
|
module ActiveRecord
|
|
2743
2592
|
module Assertions
|
|
2744
2593
|
class QueryAssertionsTest < ActiveSupport::TestCase
|
|
@@ -29,7 +29,7 @@ class OptimizerHitsTestSQLServer < ActiveRecord::TestCase
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
it "support subqueries" do
|
|
32
|
-
assert_queries_match(%r{
|
|
32
|
+
assert_queries_match(%r{SELECT COUNT\(count_column\) FROM \(SELECT .*\) subquery_for_count OPTION \(MAXDOP 2\)}) do
|
|
33
33
|
companies = Company.optimizer_hints("MAXDOP 2")
|
|
34
34
|
companies = companies.select(:id).where(firm_id: [0, 1]).limit(3)
|
|
35
35
|
assert_equal 3, companies.count
|
|
@@ -28,13 +28,13 @@ class ShowplanTestSQLServer < ActiveRecord::TestCase
|
|
|
28
28
|
|
|
29
29
|
it "from array condition using index" do
|
|
30
30
|
plan = Car.where(id: [1, 2]).explain.inspect
|
|
31
|
-
_(plan).must_include "SELECT [cars].* FROM [cars] WHERE [cars].[id] IN (
|
|
31
|
+
_(plan).must_include "SELECT [cars].* FROM [cars] WHERE [cars].[id] IN (@0, @1)"
|
|
32
32
|
_(plan).must_include "Clustered Index Seek", "make sure we do not showplan the sp_executesql"
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
it "from array condition" do
|
|
36
36
|
plan = Car.where(name: ["honda", "zyke"]).explain.inspect
|
|
37
|
-
_(plan).must_include " SELECT [cars].* FROM [cars] WHERE [cars].[name] IN (
|
|
37
|
+
_(plan).must_include " SELECT [cars].* FROM [cars] WHERE [cars].[name] IN (@0, @1)"
|
|
38
38
|
_(plan).must_include "Clustered Index Scan", "make sure we do not showplan the sp_executesql"
|
|
39
39
|
end
|
|
40
40
|
end
|
|
@@ -116,16 +116,16 @@ class SpecificSchemaTestSQLServer < ActiveRecord::TestCase
|
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
118
|
# Using ActiveRecord's quoted_id feature for objects.
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
assert_queries_and_values_match(/.*/, ["'T'", 1]) { SSTestDatatypeMigration.where(char_col: value.new).first }
|
|
120
|
+
assert_queries_and_values_match(/.*/, ["'T'", 1]) { SSTestDatatypeMigration.where(varchar_col: value.new).first }
|
|
121
121
|
# Using our custom char type data.
|
|
122
122
|
type = ActiveRecord::Type::SQLServer::Char
|
|
123
123
|
data = ActiveRecord::Type::SQLServer::Data
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
assert_queries_and_values_match(/.*/, ["'T'", 1]) { SSTestDatatypeMigration.where(char_col: data.new("T", type.new)).first }
|
|
125
|
+
assert_queries_and_values_match(/.*/, ["'T'", 1]) { SSTestDatatypeMigration.where(varchar_col: data.new("T", type.new)).first }
|
|
126
126
|
# Taking care of everything.
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
assert_queries_and_values_match(/.*/, ["'T'", 1]) { SSTestDatatypeMigration.where(char_col: "T").first }
|
|
128
|
+
assert_queries_and_values_match(/.*/, ["'T'", 1]) { SSTestDatatypeMigration.where(varchar_col: "T").first }
|
|
129
129
|
end
|
|
130
130
|
|
|
131
131
|
it "can update and hence properly quoted non-national char/varchar columns" do
|
|
@@ -22,6 +22,28 @@ module ARTest
|
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
def assert_queries_and_values_match(match, bound_values=[], count: nil, &block)
|
|
26
|
+
ActiveRecord::Base.lease_connection.materialize_transactions
|
|
27
|
+
|
|
28
|
+
counter = ActiveRecord::Assertions::QueryAssertions::SQLCounter.new
|
|
29
|
+
ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
|
|
30
|
+
result = _assert_nothing_raised_or_warn("assert_queries_match", &block)
|
|
31
|
+
queries = counter.log_full
|
|
32
|
+
matched_queries = queries.select do |query, values|
|
|
33
|
+
values = values.map { |v| v.respond_to?(:quoted) ? v.quoted : v }
|
|
34
|
+
match === query && bound_values === values
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if count
|
|
38
|
+
assert_equal count, matched_queries.size, "#{matched_queries.size} instead of #{count} queries were executed.#{count.log.empty? ? '' : "\nQueries:\n#{counter.log.join("\n")}"}"
|
|
39
|
+
else
|
|
40
|
+
assert_operator matched_queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{counter.log.empty? ? '' : "\nQueries:\n#{counter.log.join("\n")}"}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
result
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
25
47
|
private
|
|
26
48
|
|
|
27
49
|
# Rails tests expect a save-point to be created and released. SQL Server does not release
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-sqlserver-adapter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 8.0.
|
|
4
|
+
version: 8.0.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ken Collins
|
|
@@ -243,8 +243,8 @@ licenses:
|
|
|
243
243
|
- MIT
|
|
244
244
|
metadata:
|
|
245
245
|
bug_tracker_uri: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues
|
|
246
|
-
changelog_uri: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/v8.0.
|
|
247
|
-
source_code_uri: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/v8.0.
|
|
246
|
+
changelog_uri: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/v8.0.10/CHANGELOG.md
|
|
247
|
+
source_code_uri: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/v8.0.10
|
|
248
248
|
rdoc_options: []
|
|
249
249
|
require_paths:
|
|
250
250
|
- lib
|