activerecord-sqlserver-adapter 7.2.5 → 8.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0c48184b9fac12f23a4db11b01d1ed3d670fdd48f8c9705c690c830b622f11e
4
- data.tar.gz: 9e88d43c701da66870643b33684c9a2b2d58db063e419789d0b7faf6c5cf2e15
3
+ metadata.gz: 926f65915d98bb3c271370883fa89891fc575b5c6801cd8c235386c078d34bf5
4
+ data.tar.gz: 0a932a9d5edf0d3932e3f0ba44328ed77a033a1223f05699e8f4caa79116ac56
5
5
  SHA512:
6
- metadata.gz: 40567edb70c619f8b9ae007c41b9b1c05157862df8bb275494b1158648c87cad3971bc1d4080431696c623d48c42356cf826349b3cce36a762d9e7d68a0d3102
7
- data.tar.gz: 162c65ce3cb0755f464798e0e55c651e72528f9e060c4dfd1d30ff8386fb54cbbe2f99c404f712a95c1567ec4892cc9833badcc73f8b4ce7469fdef8bdd2b039
6
+ metadata.gz: ee45540a8d2ae57dc69a35048f4ccfbf01bcfc91e177e57405b19ccb8a3279eef7b752c22dee20c4171c8bb4e2cb0fcaadb99cd6c25d139fd2ef53577a5cb23a
7
+ data.tar.gz: 9184a7041e5de445103bcd47832352614897865f0ef49e83067b553786d089be73eb73c34dfaf3f102e02804ee7d8d40803d5582430ebfeecbe95f568b0b7cb7
@@ -1,6 +1,12 @@
1
1
  name: CI
2
2
 
3
- on: [push, pull_request]
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+ schedule:
9
+ - cron: '0 4 * * 1'
4
10
 
5
11
  jobs:
6
12
  test:
@@ -14,9 +20,8 @@ jobs:
14
20
  fail-fast: false
15
21
  matrix:
16
22
  ruby:
17
- - 3.1.6
18
- - 3.2.4
19
- - 3.3.2
23
+ - 3.3.4
24
+ - 3.2.5
20
25
 
21
26
  steps:
22
27
  - name: Checkout code
data/CHANGELOG.md CHANGED
@@ -1,46 +1,12 @@
1
- ## v7.2.5
1
+ ## v8.0.0
2
2
 
3
- #### Fixed
4
-
5
- - [#1308](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1308) Fix retrieval of temporary table's column information.
6
-
7
- ## v7.2.4
8
-
9
- #### Fixed
10
-
11
- - [#1270](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1270) Fix parsing of raw table name from SQL with extra parentheses
12
-
13
- ## v7.2.3
14
-
15
- #### Fixed
16
-
17
- - [#1262](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1262) Fix distinct alias when multiple databases used.
18
-
19
- ## v7.2.2
20
-
21
- #### Fixed
22
-
23
- - [#1244](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1244) Allow INSERT statements with SELECT notation
24
- - [#1247](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1247) Fix queries with date and date-time placeholder conditions
25
- - [#1249](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1249) Binary basic columns should be limitable
26
- - [#1255](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1255) Fixed the ordering of optimizer hints in the generated SQL
3
+ #### Changed
27
4
 
28
- ## v7.2.1
5
+ - [#1216](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1216) Refactor adapter interface to match abstract adapter
6
+ - [#1225](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1225) Drop support to Ruby 3.1
29
7
 
30
8
  #### Fixed
31
9
 
32
- - [#1231](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1231) Enable identity insert on view's base table
33
-
34
- ## v7.2.0
35
-
36
- #### Added
37
-
38
- - [#1178](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1178) Support encrypting binary columns
39
-
40
- #### Changed
41
-
42
- - [#1153](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1153) Only support Ruby v3.1+
43
- - [#1196](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1196) Use default inspect for database adapter
44
-
10
+ - [#1215](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1215) Fix mismatched foreign key errors
45
11
 
46
- Please check [7-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/7-1-stable/CHANGELOG.md) for previous changes.
12
+ Please check [7-2-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/7-2-stable/CHANGELOG.md) for previous changes.
data/Dockerfile.ci CHANGED
@@ -9,6 +9,6 @@ WORKDIR $WORKDIR
9
9
 
10
10
  COPY . $WORKDIR
11
11
 
12
- RUN RAILS_BRANCH=7-2-stable bundle install --jobs `expr $(cat /proc/cpuinfo | grep -c "cpu cores") - 1` --retry 3
12
+ RUN bundle install --jobs `expr $(cat /proc/cpuinfo | grep -c "cpu cores") - 1` --retry 3
13
13
 
14
14
  CMD ["sh"]
data/Gemfile CHANGED
@@ -18,6 +18,8 @@ if ENV["RAILS_SOURCE"]
18
18
  gemspec path: ENV["RAILS_SOURCE"]
19
19
  elsif ENV["RAILS_BRANCH"]
20
20
  gem "rails", github: "rails/rails", branch: ENV["RAILS_BRANCH"]
21
+ elsif ENV["RAILS_COMMIT"]
22
+ gem "rails", github: "rails/rails", ref: ENV["RAILS_COMMIT"]
21
23
  else
22
24
  # Need to get rails source because the gem doesn't include tests
23
25
  version = ENV["RAILS_VERSION"] || begin
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # ActiveRecord SQL Server Adapter. For SQL Server 2012 And Higher.
1
+ # ActiveRecord SQL Server Adapter
2
2
 
3
3
  * [![CI](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/actions/workflows/ci.yml/badge.svg)](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/actions/workflows/ci.yml) - CI
4
4
  * [![Gem Version](http://img.shields.io/gem/v/activerecord-sqlserver-adapter.svg)](https://rubygems.org/gems/activerecord-sqlserver-adapter) - Gem Version
@@ -8,15 +8,18 @@
8
8
 
9
9
  The SQL Server adapter for ActiveRecord using SQL Server 2012 or higher.
10
10
 
11
- Interested in older versions? We follow a rational versioning policy that tracks Rails. That means that our 7.x version
12
- of the adapter is only for the latest 7.x version of Rails. If you need the adapter for SQL Server 2008 or 2005, you
13
- are still in the right spot. Just install the latest 3.2.x to 4.1.x version of the adapter that matches your Rails
14
- version. We also have stable branches for each major/minor release of ActiveRecord. For older versions, please check
15
- their stable branches.
11
+ We follow a rational versioning policy that tracks Rails. That means that our 7.x version of the adapter is only
12
+ for the latest 7.x version of Rails. We also have stable branches for each major/minor release of ActiveRecord.
13
+
14
+ We support the versions of the adapter that are in the Rails [Bug Fixes](https://rubyonrails.org/maintenance)
15
+ maintenance group.
16
+
17
+ See [Rubygems](https://rubygems.org/gems/activerecord-sqlserver-adapter/versions) for the latest version of the adapter for each Rails release.
16
18
 
17
19
  | Adapter Version | Rails Version | Support | Branch |
18
20
  |-----------------|---------------|----------------|-------------------------------------------------------------------------------------------------|
19
- | `7.2.x` | `7.2.x` | Active | [main](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) |
21
+ | `8.0.0` | `8.0.x` | Active | [main](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) |
22
+ | `7.2.x` | `7.2.x` | Active | [7-2-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-2-stable) |
20
23
  | `7.1.x` | `7.1.x` | Active | [7-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-1-stable) |
21
24
  | `7.0.x` | `7.0.x` | Ended | [7-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/7-0-stable) |
22
25
  | `6.1.x` | `6.1.x` | Ended | [6-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-1-stable) |
@@ -26,7 +29,6 @@ their stable branches.
26
29
  | `4.2.x` | `4.2.x` | Ended | [4-2-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/4-2-stable) |
27
30
  | `4.1.x` | `4.1.x` | Ended | [4-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/4-1-stable) |
28
31
 
29
- See [Rubygems](https://rubygems.org/gems/activerecord-sqlserver-adapter/versions) for the latest version of the adapter for each Rails release.
30
32
 
31
33
  #### Native Data Type Support
32
34
 
@@ -103,16 +105,14 @@ configuration then implement the `configure_connection` method in an initializer
103
105
  example we are setting the `TEXTSIZE` to 64 megabytes.
104
106
 
105
107
  ```ruby
106
- module ActiveRecord
107
- module ConnectionAdapters
108
- class SQLServerAdapter < AbstractAdapter
109
- def configure_connection
110
- super
111
- @raw_connection.execute("SET TEXTSIZE #{64.megabytes}").do
112
- end
108
+ ActiveRecord::ConnectionAdapters::SQLServerAdapter.prepend(
109
+ Module.new do
110
+ def configure_connection
111
+ super
112
+ @raw_connection.execute("SET TEXTSIZE #{64.megabytes}").do
113
113
  end
114
114
  end
115
- end
115
+ )
116
116
  ```
117
117
 
118
118
  #### Configure Application Name
@@ -262,7 +262,6 @@ Many many people have contributed. If you do not see your name here and it shoul
262
262
 
263
263
  You can see an up-to-date list of contributors here: http://github.com/rails-sqlserver/activerecord-sqlserver-adapter/contributors
264
264
 
265
-
266
265
  ## License
267
266
 
268
267
  ActiveRecord SQL Server Adapter is released under the [MIT License](https://opensource.org/licenses/MIT).
data/VERSION CHANGED
@@ -1 +1 @@
1
- 7.2.5
1
+ 8.0.0
@@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
7
7
  spec.platform = Gem::Platform::RUBY
8
8
  spec.version = version
9
9
 
10
- spec.required_ruby_version = ">= 3.1.0"
10
+ spec.required_ruby_version = ">= 3.2.0"
11
11
 
12
12
  spec.license = "MIT"
13
13
  spec.authors = ["Ken Collins", "Anna Carey", "Will Bond", "Murray Steele", "Shawn Balestracci", "Joe Rafaniello", "Tom Ward", "Aidan Haran"]
@@ -27,6 +27,6 @@ Gem::Specification.new do |spec|
27
27
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
28
28
  spec.require_paths = ["lib"]
29
29
 
30
- spec.add_dependency "activerecord", "~> 7.2.0"
30
+ spec.add_dependency "activerecord", "~> 8.0.0"
31
31
  spec.add_dependency "tiny_tds"
32
32
  end
@@ -5,7 +5,6 @@ services:
5
5
  ci:
6
6
  environment:
7
7
  - ACTIVERECORD_UNITTEST_HOST=sqlserver
8
- - RAILS_BRANCH=7-2-stable
9
8
  build:
10
9
  context: .
11
10
  dockerfile: Dockerfile.ci
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
  private
12
12
 
13
13
  def construct_relation_for_exists(conditions)
14
- klass.with_connection do |connection|
14
+ model.with_connection do |connection|
15
15
  if connection.sqlserver?
16
16
  _construct_relation_for_exists(conditions)
17
17
  else
@@ -13,50 +13,42 @@ module ActiveRecord
13
13
  !READ_QUERY.match?(sql.b)
14
14
  end
15
15
 
16
- def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
17
- log(sql, name, async: async) do |notification_payload|
18
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
19
- result = if id_insert_table_name = query_requires_identity_insert?(sql)
20
- with_identity_insert_enabled(id_insert_table_name, conn) { internal_raw_execute(sql, conn, perform_do: true) }
21
- else
22
- internal_raw_execute(sql, conn, perform_do: true)
23
- end
24
- verified!
25
- notification_payload[:row_count] = result
26
- result
27
- end
16
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch:)
17
+ result = if id_insert_table_name = query_requires_identity_insert?(sql)
18
+ # If the table name is a view, we need to get the base table name for enabling identity insert.
19
+ id_insert_table_name = view_table_name(id_insert_table_name) if view_exists?(id_insert_table_name)
20
+
21
+ with_identity_insert_enabled(id_insert_table_name, raw_connection) do
22
+ internal_exec_sql_query(sql, raw_connection)
23
+ end
24
+ else
25
+ internal_exec_sql_query(sql, raw_connection)
26
+ end
27
+
28
+ verified!
29
+ notification_payload[:row_count] = result.count
30
+ result
31
+ end
32
+
33
+ def cast_result(raw_result)
34
+ if raw_result.columns.empty?
35
+ ActiveRecord::Result.empty
36
+ else
37
+ ActiveRecord::Result.new(raw_result.columns, raw_result.rows)
28
38
  end
29
39
  end
30
40
 
31
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false)
32
- sql = transform_query(sql)
33
-
34
- check_if_write_query(sql)
35
- mark_transaction_written_if_write(sql)
41
+ def affected_rows(raw_result)
42
+ raw_result.first['AffectedRows']
43
+ end
36
44
 
37
- unless without_prepared_statement?(binds)
45
+ def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false)
46
+ unless binds.nil? || binds.empty?
38
47
  types, params = sp_executesql_types_and_parameters(binds)
39
48
  sql = sp_executesql_sql(sql, types, params, name)
40
49
  end
41
50
 
42
- log(sql, name, binds, async: async) do |notification_payload|
43
- with_raw_connection do |conn|
44
- result = if id_insert_table_name = query_requires_identity_insert?(sql)
45
- # If the table name is a view, we need to get the base table name for enabling identity insert.
46
- id_insert_table_name = view_table_name(id_insert_table_name) if view_exists?(id_insert_table_name)
47
-
48
- with_identity_insert_enabled(id_insert_table_name, conn) do
49
- internal_exec_sql_query(sql, conn)
50
- end
51
- else
52
- internal_exec_sql_query(sql, conn)
53
- end
54
-
55
- verified!
56
- notification_payload[:row_count] = result.count
57
- result
58
- end
59
- end
51
+ super
60
52
  end
61
53
 
62
54
  def internal_exec_sql_query(sql, conn)
@@ -66,14 +58,14 @@ module ActiveRecord
66
58
  finish_statement_handle(handle)
67
59
  end
68
60
 
69
- def exec_delete(sql, name, binds)
61
+ def exec_delete(sql, name = nil, binds = [])
70
62
  sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
71
- super(sql, name, binds).rows.first.first
63
+ super(sql, name, binds)
72
64
  end
73
65
 
74
- def exec_update(sql, name, binds)
66
+ def exec_update(sql, name = nil, binds = [])
75
67
  sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
76
- super(sql, name, binds).rows.first.first
68
+ super(sql, name, binds)
77
69
  end
78
70
 
79
71
  def begin_db_transaction
@@ -156,7 +148,7 @@ module ActiveRecord
156
148
  returning_sql = if returning.is_a?(String)
157
149
  returning
158
150
  else
159
- returning.map { |column| "INSERTED.#{quote_column_name(column)}" }.join(", ")
151
+ Array(returning).map { |column| "INSERTED.#{quote_column_name(column)}" }.join(", ")
160
152
  end
161
153
  sql << " OUTPUT #{returning_sql}"
162
154
  end
@@ -332,10 +324,13 @@ module ActiveRecord
332
324
 
333
325
  def sp_executesql_sql_type(attr)
334
326
  if attr.respond_to?(:type)
335
- return attr.type.sqlserver_type if attr.type.respond_to?(:sqlserver_type)
327
+ type = attr.type.is_a?(ActiveRecord::Normalization::NormalizedValueType) ? attr.type.cast_type : attr.type
328
+ type = type.subtype if type.serialized?
329
+
330
+ return type.sqlserver_type if type.respond_to?(:sqlserver_type)
336
331
 
337
- if attr.type.is_a?(ActiveRecord::Encryption::EncryptedAttributeType) && attr.type.instance_variable_get(:@cast_type).respond_to?(:sqlserver_type)
338
- return attr.type.instance_variable_get(:@cast_type).sqlserver_type
332
+ if type.is_a?(ActiveRecord::Encryption::EncryptedAttributeType) && type.instance_variable_get(:@cast_type).respond_to?(:sqlserver_type)
333
+ return type.instance_variable_get(:@cast_type).sqlserver_type
339
334
  end
340
335
  end
341
336
 
@@ -375,6 +370,7 @@ module ActiveRecord
375
370
  sql = "EXEC sp_executesql #{quote(sql)}"
376
371
  sql += ", #{types}, #{params}" unless params.empty?
377
372
  end
373
+
378
374
  sql.freeze
379
375
  end
380
376
 
@@ -452,10 +448,9 @@ module ActiveRecord
452
448
  # TinyTDS returns false instead of raising an exception if connection fails.
453
449
  # Getting around this by raising an exception ourselves while PR
454
450
  # https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
455
- def internal_raw_execute(sql, conn, perform_do: false)
456
- result = conn.execute(sql).tap do |_result|
457
- raise TinyTds::Error, "failed to execute statement" if _result.is_a?(FalseClass)
458
- end
451
+ def internal_raw_execute(sql, raw_connection, perform_do: false)
452
+ result = raw_connection.execute(sql)
453
+ raise TinyTds::Error, "failed to execute statement" if result.is_a?(FalseClass)
459
454
 
460
455
  perform_do ? result.do : result
461
456
  end
@@ -14,22 +14,24 @@ module ActiveRecord
14
14
  res
15
15
  end
16
16
 
17
- def drop_table(table_name, **options)
18
- # Mimic CASCADE option as best we can.
19
- if options[:force] == :cascade
20
- execute_procedure(:sp_fkeys, pktable_name: table_name).each do |fkdata|
21
- fktable = fkdata["FKTABLE_NAME"]
22
- fkcolmn = fkdata["FKCOLUMN_NAME"]
23
- pktable = fkdata["PKTABLE_NAME"]
24
- pkcolmn = fkdata["PKCOLUMN_NAME"]
25
- remove_foreign_key fktable, name: fkdata["FK_NAME"]
26
- execute "DELETE FROM #{quote_table_name(fktable)} WHERE #{quote_column_name(fkcolmn)} IN ( SELECT #{quote_column_name(pkcolmn)} FROM #{quote_table_name(pktable)} )"
17
+ def drop_table(*table_names, **options)
18
+ table_names.each do |table_name|
19
+ # Mimic CASCADE option as best we can.
20
+ if options[:force] == :cascade
21
+ execute_procedure(:sp_fkeys, pktable_name: table_name).each do |fkdata|
22
+ fktable = fkdata["FKTABLE_NAME"]
23
+ fkcolmn = fkdata["FKCOLUMN_NAME"]
24
+ pktable = fkdata["PKTABLE_NAME"]
25
+ pkcolmn = fkdata["PKCOLUMN_NAME"]
26
+ remove_foreign_key fktable, name: fkdata["FK_NAME"]
27
+ execute "DELETE FROM #{quote_table_name(fktable)} WHERE #{quote_column_name(fkcolmn)} IN ( SELECT #{quote_column_name(pkcolmn)} FROM #{quote_table_name(pktable)} )"
28
+ end
29
+ end
30
+ if options[:if_exists] && version_year < 2016
31
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}", "SCHEMA"
32
+ else
33
+ super
27
34
  end
28
- end
29
- if options[:if_exists] && version_year < 2016
30
- execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}", "SCHEMA"
31
- else
32
- super
33
35
  end
34
36
  end
35
37
 
@@ -37,18 +39,16 @@ module ActiveRecord
37
39
  data = select("EXEC sp_helpindex #{quote(table_name)}", "SCHEMA") rescue []
38
40
 
39
41
  data.reduce([]) do |indexes, index|
40
- index = index.with_indifferent_access
41
-
42
- if index[:index_description].match?(/primary key/)
42
+ if index['index_description'].match?(/primary key/)
43
43
  indexes
44
44
  else
45
- name = index[:index_name]
46
- unique = index[:index_description].match?(/unique/)
45
+ name = index['index_name']
46
+ unique = index['index_description'].match?(/unique/)
47
47
  where = select_value("SELECT [filter_definition] FROM sys.indexes WHERE name = #{quote(name)}", "SCHEMA")
48
48
  orders = {}
49
49
  columns = []
50
50
 
51
- index[:index_keys].split(",").each do |column|
51
+ index['index_keys'].split(",").each do |column|
52
52
  column.strip!
53
53
 
54
54
  if column.end_with?("(-)")
@@ -347,16 +347,12 @@ module ActiveRecord
347
347
 
348
348
  def columns_for_distinct(columns, orders)
349
349
  order_columns = orders.reject(&:blank?).map { |s|
350
- s = visitor.compile(s) unless s.is_a?(String)
350
+ s = s.to_sql unless s.is_a?(String)
351
351
  s.gsub(/\s+(?:ASC|DESC)\b/i, "")
352
352
  .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
353
- }
354
- .reject(&:blank?)
355
- .reject { |s| columns.include?(s) }
353
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
356
354
 
357
- order_columns_aliased = order_columns.map.with_index { |column, i| "#{column} AS alias_#{i}" }
358
-
359
- (order_columns_aliased << super).join(", ")
355
+ (order_columns << super).join(", ")
360
356
  end
361
357
 
362
358
  def update_table_definition(table_name, base)
@@ -484,16 +480,15 @@ module ActiveRecord
484
480
  end
485
481
 
486
482
  def column_definitions(table_name)
487
- identifier = database_prefix_identifier(table_name)
488
- database = identifier.fully_qualified_database_quoted
489
- view_exists = view_exists?(table_name)
490
- view_tblnm = view_table_name(table_name) if view_exists
483
+ identifier = database_prefix_identifier(table_name)
484
+ database = identifier.fully_qualified_database_quoted
485
+ view_exists = view_exists?(table_name)
491
486
 
492
487
  if view_exists
493
488
  sql = <<~SQL
494
489
  SELECT LOWER(c.COLUMN_NAME) AS [name], c.COLUMN_DEFAULT AS [default]
495
490
  FROM #{database}.INFORMATION_SCHEMA.COLUMNS c
496
- WHERE c.TABLE_NAME = #{quote(view_tblnm)}
491
+ WHERE c.TABLE_NAME = #{quote(view_table_name(table_name))}
497
492
  SQL
498
493
  results = internal_exec_query(sql, "SCHEMA")
499
494
  default_functions = results.each.with_object({}) { |row, out| out[row["name"]] = row["default"] }.compact
@@ -502,91 +497,103 @@ module ActiveRecord
502
497
  sql = column_definitions_sql(database, identifier)
503
498
 
504
499
  binds = []
505
- nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
500
+ nv128 = SQLServer::Type::UnicodeVarchar.new(limit: 128)
506
501
  binds << Relation::QueryAttribute.new("TABLE_NAME", identifier.object, nv128)
507
502
  binds << Relation::QueryAttribute.new("TABLE_SCHEMA", identifier.schema, nv128) unless identifier.schema.blank?
503
+
508
504
  results = internal_exec_query(sql, "SCHEMA", binds)
505
+ raise ActiveRecord::StatementInvalid, "Table '#{table_name}' doesn't exist" if results.empty?
509
506
 
510
507
  columns = results.map do |ci|
511
- ci = ci.symbolize_keys
512
- ci[:_type] = ci[:type]
513
- ci[:table_name] = view_tblnm || table_name
514
- ci[:type] = case ci[:type]
515
- when /^bit|image|text|ntext|datetime$/
516
- ci[:type]
517
- when /^datetime2|datetimeoffset$/i
518
- "#{ci[:type]}(#{ci[:datetime_precision]})"
519
- when /^time$/i
520
- "#{ci[:type]}(#{ci[:datetime_precision]})"
521
- when /^numeric|decimal$/i
522
- "#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
523
- when /^float|real$/i
524
- "#{ci[:type]}"
525
- when /^char|nchar|varchar|nvarchar|binary|varbinary|bigint|int|smallint$/
526
- ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
527
- else
528
- ci[:type]
529
- end
530
- ci[:default_value],
531
- ci[:default_function] = begin
532
- default = ci[:default_value]
533
- if default.nil? && view_exists
534
- view_column = views_real_column_name(table_name, ci[:name]).downcase
535
- default = default_functions[view_column] if view_column.present?
536
- end
537
- case default
538
- when nil
539
- [nil, nil]
540
- when /\A\((\w+\(\))\)\Z/
541
- default_function = Regexp.last_match[1]
542
- [nil, default_function]
543
- when /\A\(N'(.*)'\)\Z/m
544
- string_literal = SQLServer::Utils.unquote_string(Regexp.last_match[1])
545
- [string_literal, nil]
546
- when /CREATE DEFAULT/mi
547
- [nil, nil]
548
- else
549
- type = case ci[:type]
550
- when /smallint|int|bigint/ then ci[:_type]
551
- else ci[:type]
552
- end
553
- value = default.match(/\A\((.*)\)\Z/m)[1]
554
- value = select_value("SELECT CAST(#{value} AS #{type}) AS value", "SCHEMA")
555
- [value, nil]
556
- end
508
+ col = {
509
+ name: ci["name"],
510
+ numeric_scale: ci["numeric_scale"],
511
+ numeric_precision: ci["numeric_precision"],
512
+ datetime_precision: ci["datetime_precision"],
513
+ collation: ci["collation"],
514
+ ordinal_position: ci["ordinal_position"],
515
+ length: ci["length"]
516
+ }
517
+
518
+ col[:table_name] = view_exists ? view_table_name(table_name) : table_name
519
+ col[:type] = column_type(ci: ci)
520
+ col[:default_value], col[:default_function] = default_value_and_function(default: ci['default_value'],
521
+ name: ci['name'],
522
+ type: col[:type],
523
+ original_type: ci['type'],
524
+ view_exists: view_exists,
525
+ table_name: table_name,
526
+ default_functions: default_functions)
527
+
528
+ col[:null] = ci['is_nullable'].to_i == 1
529
+ col[:is_primary] = ci['is_primary'].to_i == 1
530
+
531
+ if [true, false].include?(ci['is_identity'])
532
+ col[:is_identity] = ci['is_identity']
533
+ else
534
+ col[:is_identity] = ci['is_identity'].to_i == 1
557
535
  end
558
- ci[:null] = ci[:is_nullable].to_i == 1
559
- ci.delete(:is_nullable)
560
- ci[:is_primary] = ci[:is_primary].to_i == 1
561
- ci[:is_identity] = ci[:is_identity].to_i == 1 unless [TrueClass, FalseClass].include?(ci[:is_identity].class)
562
- ci
563
- end
564
536
 
565
- # Since Rails 7, it's expected that all adapter raise error when table doesn't exists.
566
- # I'm not aware of the possibility of tables without columns on SQL Server (postgres have those).
567
- # Raise error if the method return an empty array
568
- columns.tap do |result|
569
- raise ActiveRecord::StatementInvalid, "Table '#{table_name}' doesn't exist" if result.empty?
537
+ col
570
538
  end
539
+
540
+ columns
571
541
  end
572
542
 
573
- def column_definitions_sql(database, identifier)
574
- schema_name = "schema_name()"
543
+ def default_value_and_function(default:, name:, type:, original_type:, view_exists:, table_name:, default_functions:)
544
+ if default.nil? && view_exists
545
+ view_column = views_real_column_name(table_name, name).downcase
546
+ default = default_functions[view_column] if view_column.present?
547
+ end
575
548
 
576
- if prepared_statements
577
- object_name = "@0"
578
- schema_name = "@1" if identifier.schema.present?
549
+ case default
550
+ when nil
551
+ [nil, nil]
552
+ when /\A\((\w+\(\))\)\Z/
553
+ default_function = Regexp.last_match[1]
554
+ [nil, default_function]
555
+ when /\A\(N'(.*)'\)\Z/m
556
+ string_literal = SQLServer::Utils.unquote_string(Regexp.last_match[1])
557
+ [string_literal, nil]
558
+ when /CREATE DEFAULT/mi
559
+ [nil, nil]
579
560
  else
580
- object_name = quote(identifier.object)
581
- schema_name = quote(identifier.schema) if identifier.schema.present?
561
+ type = case type
562
+ when /smallint|int|bigint/ then original_type
563
+ else type
564
+ end
565
+ value = default.match(/\A\((.*)\)\Z/m)[1]
566
+ value = select_value("SELECT CAST(#{value} AS #{type}) AS value", "SCHEMA")
567
+ [value, nil]
582
568
  end
569
+ end
583
570
 
584
- object_id_arg = identifier.schema.present? ? "CONCAT(#{schema_name},'.',#{object_name})" : object_name
585
-
586
- if identifier.temporary_table?
587
- database = "TEMPDB"
588
- object_id_arg = "CONCAT('#{database}','..',#{object_name})"
571
+ def column_type(ci:)
572
+ case ci['type']
573
+ when /^bit|image|text|ntext|datetime$/
574
+ ci['type']
575
+ when /^datetime2|datetimeoffset$/i
576
+ "#{ci['type']}(#{ci['datetime_precision']})"
577
+ when /^time$/i
578
+ "#{ci['type']}(#{ci['datetime_precision']})"
579
+ when /^numeric|decimal$/i
580
+ "#{ci['type']}(#{ci['numeric_precision']},#{ci['numeric_scale']})"
581
+ when /^float|real$/i
582
+ "#{ci['type']}"
583
+ when /^char|nchar|varchar|nvarchar|binary|varbinary|bigint|int|smallint$/
584
+ ci['length'].to_i == -1 ? "#{ci['type']}(max)" : "#{ci['type']}(#{ci['length']})"
585
+ else
586
+ ci['type']
589
587
  end
588
+ end
589
+
590
+ def column_definitions_sql(database, identifier)
591
+ object_name = prepared_statements ? "@0" : quote(identifier.object)
592
+ schema_name = if identifier.schema.blank?
593
+ "schema_name()"
594
+ else
595
+ prepared_statements ? "@1" : quote(identifier.schema)
596
+ end
590
597
 
591
598
  %{
592
599
  SELECT
@@ -641,7 +648,7 @@ module ActiveRecord
641
648
  AND k.unique_index_id = ic.index_id
642
649
  AND c.column_id = ic.column_id
643
650
  WHERE
644
- o.Object_ID = Object_ID(#{object_id_arg})
651
+ o.name = #{object_name}
645
652
  AND s.name = #{schema_name}
646
653
  ORDER BY
647
654
  c.column_id
@@ -663,7 +670,7 @@ module ActiveRecord
663
670
  end
664
671
 
665
672
  def remove_default_constraint(table_name, column_name)
666
- # If there are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
673
+ # If their are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
667
674
  execute_procedure(:sp_helpconstraint, table_name, "nomsg").flatten.select do |row|
668
675
  row["constraint_type"] == "DEFAULT on column #{column_name}"
669
676
  end.each do |row|
@@ -700,7 +707,7 @@ module ActiveRecord
700
707
  elsif s.match?(/^\s*UPDATE\s+.*/i)
701
708
  s.match(/UPDATE\s+([^\(\s]+)\s*/i)[1]
702
709
  else
703
- s.match(/FROM[\s|\(]+((\[[^\(\]]+\])|[^\(\s]+)\s*/i)[1]
710
+ s.match(/FROM\s+((\[[^\(\]]+\])|[^\(\s]+)\s*/i)[1]
704
711
  end.strip
705
712
  end
706
713
 
@@ -716,25 +723,26 @@ module ActiveRecord
716
723
 
717
724
  def view_table_name(table_name)
718
725
  view_info = view_information(table_name)
719
- view_info ? get_table_name(view_info["VIEW_DEFINITION"]) : table_name
726
+ view_info.present? ? get_table_name(view_info["VIEW_DEFINITION"]) : table_name
720
727
  end
721
728
 
722
729
  def view_information(table_name)
723
730
  @view_information ||= {}
731
+
724
732
  @view_information[table_name] ||= begin
725
733
  identifier = SQLServer::Utils.extract_identifiers(table_name)
726
734
  information_query_table = identifier.database.present? ? "[#{identifier.database}].[INFORMATION_SCHEMA].[VIEWS]" : "[INFORMATION_SCHEMA].[VIEWS]"
727
- view_info = select_one "SELECT * FROM #{information_query_table} WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", "SCHEMA"
728
-
729
- if view_info
730
- view_info = view_info.with_indifferent_access
731
- if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
732
- view_info[:VIEW_DEFINITION] = begin
733
- select_values("EXEC sp_helptext #{identifier.object_quoted}", "SCHEMA").join
734
- rescue
735
- warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
736
- nil
737
- end
735
+
736
+ view_info = select_one("SELECT * FROM #{information_query_table} WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", "SCHEMA").to_h
737
+
738
+ if view_info.present?
739
+ if view_info['VIEW_DEFINITION'].blank? || view_info['VIEW_DEFINITION'].length == 4000
740
+ view_info['VIEW_DEFINITION'] = begin
741
+ select_values("EXEC sp_helptext #{identifier.object_quoted}", "SCHEMA").join
742
+ rescue
743
+ warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
744
+ nil
745
+ end
738
746
  end
739
747
  end
740
748
 
@@ -743,8 +751,8 @@ module ActiveRecord
743
751
  end
744
752
 
745
753
  def views_real_column_name(table_name, column_name)
746
- view_definition = view_information(table_name)[:VIEW_DEFINITION]
747
- return column_name unless view_definition
754
+ view_definition = view_information(table_name)['VIEW_DEFINITION']
755
+ return column_name if view_definition.blank?
748
756
 
749
757
  # Remove "CREATE VIEW ... AS SELECT ..." and then match the column name.
750
758
  match_data = view_definition.sub(/CREATE\s+VIEW.*AS\s+SELECT\s/, '').match(/([\w-]*)\s+AS\s+#{column_name}\W/im)
@@ -16,6 +16,7 @@ module ActiveRecord
16
16
  sql = to_sql(arel)
17
17
  result = with_showplan_on { internal_exec_query(sql, "EXPLAIN", binds) }
18
18
  printer = showplan_printer.new(result)
19
+
19
20
  printer.pp
20
21
  end
21
22
 
@@ -36,9 +36,10 @@ module ActiveRecord
36
36
 
37
37
  def cast_value(value)
38
38
  value = super
39
- return if value.blank?
40
39
 
41
- value = value.change year: 2000, month: 01, day: 01
40
+ return value unless value.is_a?(::Time)
41
+
42
+ value = value.change(year: 2000, month: 01, day: 01)
42
43
  apply_seconds_precision(value)
43
44
  end
44
45
 
@@ -81,10 +81,6 @@ module ActiveRecord
81
81
  parts.hash
82
82
  end
83
83
 
84
- def temporary_table?
85
- object.start_with?("#")
86
- end
87
-
88
84
  protected
89
85
 
90
86
  def parse_raw_name
@@ -4,9 +4,7 @@ require "tiny_tds"
4
4
  require "base64"
5
5
  require "active_record"
6
6
  require "arel_sqlserver"
7
- require "active_record/connection_adapters/abstract_adapter"
8
7
  require "active_record/connection_adapters/sqlserver/core_ext/active_record"
9
- require "active_record/connection_adapters/sqlserver/core_ext/calculations"
10
8
  require "active_record/connection_adapters/sqlserver/core_ext/explain"
11
9
  require "active_record/connection_adapters/sqlserver/core_ext/explain_subscriber"
12
10
  require "active_record/connection_adapters/sqlserver/core_ext/attribute_methods"
@@ -431,36 +429,28 @@ module ActiveRecord
431
429
  TYPE_MAP
432
430
  end
433
431
 
434
- def translate_exception(e, message:, sql:, binds:)
432
+ def translate_exception(exception, message:, sql:, binds:)
435
433
  case message
436
434
  when /(SQL Server client is not connected)|(failed to execute statement)/i
437
- ConnectionNotEstablished.new(message)
435
+ ConnectionNotEstablished.new(message, connection_pool: @pool)
438
436
  when /(cannot insert duplicate key .* with unique index) | (violation of (unique|primary) key constraint)/i
439
- RecordNotUnique.new(message, sql: sql, binds: binds)
437
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
440
438
  when /(conflicted with the foreign key constraint) | (The DELETE statement conflicted with the REFERENCE constraint)/i
441
- InvalidForeignKey.new(message, sql: sql, binds: binds)
439
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
442
440
  when /has been chosen as the deadlock victim/i
443
- DeadlockVictim.new(message, sql: sql, binds: binds)
441
+ DeadlockVictim.new(message, sql: sql, binds: binds, connection_pool: @pool)
444
442
  when /database .* does not exist/i
445
- NoDatabaseError.new(message)
443
+ NoDatabaseError.new(message, connection_pool: @pool)
446
444
  when /data would be truncated/
447
- ValueTooLong.new(message, sql: sql, binds: binds)
445
+ ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
448
446
  when /connection timed out/
449
- StatementTimeout.new(message, sql: sql, binds: binds)
447
+ StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
450
448
  when /Column '(.*)' is not the same data type as referencing column '(.*)' in foreign key/
451
- pk_id, fk_id = SQLServer::Utils.extract_identifiers($1), SQLServer::Utils.extract_identifiers($2)
452
- MismatchedForeignKey.new(
453
- self,
454
- message: message,
455
- table: fk_id.schema,
456
- foreign_key: fk_id.object,
457
- target_table: pk_id.schema,
458
- primary_key: pk_id.object
459
- )
449
+ MismatchedForeignKey.new(message: message, connection_pool: @pool)
460
450
  when /Cannot insert the value NULL into column.*does not allow nulls/
461
- NotNullViolation.new(message, sql: sql, binds: binds)
451
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
462
452
  when /Arithmetic overflow error/
463
- RangeError.new(message, sql: sql, binds: binds)
453
+ RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
464
454
  else
465
455
  super
466
456
  end
@@ -464,13 +464,13 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
464
464
  assert SSTestStringDefaultsView.lease_connection.data_source_exists?(SSTestStringDefaultsView.table_name)
465
465
  end
466
466
 
467
- # That have more than 4000 chars for their defintion
467
+ # That have more than 4000 chars for their definition
468
468
 
469
- it "cope with null returned for the defintion" do
469
+ it "cope with null returned for the definition" do
470
470
  assert_nothing_raised() { SSTestStringDefaultsBigView.columns }
471
471
  end
472
472
 
473
- it "using alternate view defintion still be able to find real default" do
473
+ it "using alternate view definition still be able to find real default" do
474
474
  assert_equal "null", SSTestStringDefaultsBigView.new.pretend_null,
475
475
  SSTestStringDefaultsBigView.columns_hash["pretend_null"].inspect
476
476
  end
@@ -542,7 +542,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
542
542
  @conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
543
543
 
544
544
  ActiveRecord::Base.while_preventing_writes do
545
- assert_equal 1, @conn.execute("SELECT * FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
545
+ assert_equal 1, @conn.execute("SELECT * FROM [subscribers] WHERE [subscribers].[nick] = 'aido'").count
546
546
  end
547
547
  end
548
548
  end
@@ -581,6 +581,28 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
581
581
  end
582
582
  end
583
583
 
584
+ describe "mismatched foreign keys error" do
585
+ def setup
586
+ @conn = ActiveRecord::Base.lease_connection
587
+ end
588
+
589
+ it 'raises an error when the foreign key is mismatched' do
590
+ error = assert_raises(ActiveRecord::MismatchedForeignKey) do
591
+ @conn.add_reference :engines, :old_car
592
+ @conn.add_foreign_key :engines, :old_cars
593
+ end
594
+
595
+ assert_match(
596
+ %r/Column 'old_cars\.id' is not the same data type as referencing column 'engines\.old_car_id' in foreign key '.*'/,
597
+ error.message
598
+ )
599
+ assert_not_nil error.cause
600
+ assert_equal @conn.pool, error.connection_pool
601
+ ensure
602
+ @conn.execute("ALTER TABLE engines DROP COLUMN old_car_id") rescue nil
603
+ end
604
+ end
605
+
584
606
  describe "placeholder conditions" do
585
607
  it 'using time placeholder' do
586
608
  assert_equal Task.where("starting < ?", Time.now).count, 1
@@ -594,13 +616,4 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
594
616
  assert_equal Task.where("starting < ?", DateTime.current).count, 1
595
617
  end
596
618
  end
597
-
598
- describe "distinct select query" do
599
- it "generated SQL does not contain unnecessary alias projection" do
600
- sqls = capture_sql do
601
- Post.includes(:comments).joins(:comments).first
602
- end
603
- assert_no_match(/AS alias_0/, sqls.first)
604
- end
605
- end
606
619
  end
@@ -376,18 +376,6 @@ module ActiveRecord
376
376
  end
377
377
 
378
378
  class CalculationsTest < ActiveRecord::TestCase
379
- # SELECT columns must be in the GROUP clause.
380
- coerce_tests! :test_should_count_with_group_by_qualified_name_on_loaded
381
- def test_should_count_with_group_by_qualified_name_on_loaded_coerced
382
- accounts = Account.group("accounts.id").select("accounts.id")
383
- expected = { 1 => 1, 2 => 1, 3 => 1, 4 => 1, 5 => 1, 6 => 1 }
384
- assert_not_predicate accounts, :loaded?
385
- assert_equal expected, accounts.count
386
- accounts.load
387
- assert_predicate accounts, :loaded?
388
- assert_equal expected, accounts.count(:id)
389
- end
390
-
391
379
  # Fix randomly failing test. The loading of the model's schema was affecting the test.
392
380
  coerce_tests! :test_offset_is_kept
393
381
  def test_offset_is_kept_coerced
@@ -532,6 +520,9 @@ class CalculationsTest < ActiveRecord::TestCase
532
520
 
533
521
  # SELECT columns must be in the GROUP clause. Since since `ids` only selects the primary key you cannot perform this query in SQL Server.
534
522
  coerce_tests! :test_ids_with_includes_and_non_primary_key_order
523
+
524
+ # To limit the results in SQL Server we use `FETCH NEXT @0 ROWS ONLY` instead of `LIMIT @0`. To use `FETCH NEXT` an order must be provided.
525
+ coerce_tests! :test_no_order_by_when_counting_all
535
526
  end
536
527
 
537
528
  module ActiveRecord
@@ -1349,6 +1340,20 @@ module ActiveRecord
1349
1340
  def test_registering_new_handlers_for_association_coerced
1350
1341
  assert_match %r{#{Regexp.escape(topic_title)} ~ N'rails'}i, Reply.joins(:topic).where(topics: { title: /rails/ }).to_sql
1351
1342
  end
1343
+
1344
+ # Same as original test except string has `N` prefix to indicate unicode string.
1345
+ coerce_tests! :test_registering_new_handlers_for_joins
1346
+ def test_registering_new_handlers_for_joins_coerced
1347
+ Reply.belongs_to :regexp_topic, -> { where(title: /rails/) }, class_name: "Topic", foreign_key: "parent_id"
1348
+
1349
+ assert_match %r{#{Regexp.escape(quote_table_name("regexp_topic.title"))} ~ N'rails'}i, Reply.joins(:regexp_topic).references(Arel.sql("regexp_topic")).to_sql
1350
+ end
1351
+
1352
+ private
1353
+
1354
+ def topic_title
1355
+ Topic.lease_connection.quote_table_name("topics.title")
1356
+ end
1352
1357
  end
1353
1358
  end
1354
1359
 
@@ -2180,17 +2185,6 @@ class EnumTest < ActiveRecord::TestCase
2180
2185
  Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2181
2186
  end
2182
2187
 
2183
- # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2184
- coerce_tests! %r{declare multiple enums at a time}
2185
- test "declare multiple enums at a time coerced" do
2186
- Book.lease_connection.remove_index(:books, column: [:author_id, :name])
2187
-
2188
- send(:'original_declare multiple enums at a time')
2189
- ensure
2190
- Book.where(author_id: nil, name: nil).delete_all
2191
- Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2192
- end
2193
-
2194
2188
  # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2195
2189
  coerce_tests! %r{serializable\? with large number label}
2196
2190
  test "serializable? with large number label coerced" do
@@ -2262,14 +2256,6 @@ class LogSubscriberTest < ActiveRecord::TestCase
2262
2256
  def test_verbose_query_logs_coerced
2263
2257
  original_test_verbose_query_logs
2264
2258
  end
2265
-
2266
- # Bindings logged slightly differently.
2267
- coerce_tests! :test_where_in_binds_logging_include_attribute_names
2268
- def test_where_in_binds_logging_include_attribute_names_coerced
2269
- Developer.where(id: [1, 2, 3, 4, 5]).load
2270
- wait
2271
- assert_match(%{@0 = 1, @1 = 2, @2 = 3, @3 = 4, @4 = 5 [["id", nil], ["id", nil], ["id", nil], ["id", nil], ["id", nil]]}, @logger.logged(:debug).last)
2272
- end
2273
2259
  end
2274
2260
 
2275
2261
  class ReloadModelsTest < ActiveRecord::TestCase
@@ -2431,7 +2417,9 @@ class QueryLogsTest < ActiveRecord::TestCase
2431
2417
  # SQL requires double single-quotes.
2432
2418
  coerce_tests! :test_sql_commenter_format
2433
2419
  def test_sql_commenter_format_coerced
2434
- ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2420
+ ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
2421
+ ActiveRecord::QueryLogs.tags = [:application]
2422
+
2435
2423
  assert_queries_match(%r{/\*application=''active_record''\*/}) do
2436
2424
  Dashboard.first
2437
2425
  end
@@ -2440,7 +2428,7 @@ class QueryLogsTest < ActiveRecord::TestCase
2440
2428
  # SQL requires double single-quotes.
2441
2429
  coerce_tests! :test_sqlcommenter_format_value
2442
2430
  def test_sqlcommenter_format_value_coerced
2443
- ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2431
+ ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
2444
2432
 
2445
2433
  ActiveRecord::QueryLogs.tags = [
2446
2434
  :application,
@@ -2455,7 +2443,7 @@ class QueryLogsTest < ActiveRecord::TestCase
2455
2443
  # SQL requires double single-quotes.
2456
2444
  coerce_tests! :test_sqlcommenter_format_value_string_coercible
2457
2445
  def test_sqlcommenter_format_value_string_coercible_coerced
2458
- ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2446
+ ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
2459
2447
 
2460
2448
  ActiveRecord::QueryLogs.tags = [
2461
2449
  :application,
@@ -2470,7 +2458,7 @@ class QueryLogsTest < ActiveRecord::TestCase
2470
2458
  # SQL requires double single-quotes.
2471
2459
  coerce_tests! :test_sqlcommenter_format_allows_string_keys
2472
2460
  def test_sqlcommenter_format_allows_string_keys_coerced
2473
- ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2461
+ ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
2474
2462
 
2475
2463
  ActiveRecord::QueryLogs.tags = [
2476
2464
  :application,
@@ -2505,6 +2493,17 @@ class InsertAllTest < ActiveRecord::TestCase
2505
2493
  result = Book.insert_all! [{ name: "Rework", author_id: 1 }], returning: Arel.sql("UPPER(INSERTED.name) as name")
2506
2494
  assert_equal %w[ REWORK ], result.pluck("name")
2507
2495
  end
2496
+
2497
+ # Need to remove index as SQL Server considers NULLs on a unique-index to be equal unlike PostgreSQL/MySQL/SQLite.
2498
+ coerce_tests! :test_insert_with_type_casting_and_serialize_is_consistent
2499
+ def test_insert_with_type_casting_and_serialize_is_consistent_coerced
2500
+ connection.remove_index(:books, column: [:author_id, :name])
2501
+
2502
+ original_test_insert_with_type_casting_and_serialize_is_consistent
2503
+ ensure
2504
+ Book.where(author_id: nil, name: '["Array"]').delete_all
2505
+ Book.lease_connection.add_index(:books, [:author_id, :name], unique: true)
2506
+ end
2508
2507
  end
2509
2508
 
2510
2509
  module ActiveRecord
@@ -2667,6 +2666,28 @@ module ActiveRecord
2667
2666
  end
2668
2667
  end
2669
2668
 
2669
+ module ActiveRecord
2670
+ module ConnectionAdapters
2671
+ class RegistrationIsolatedTest < ActiveRecord::TestCase
2672
+ # SQL Server was not included in the list of available adapters in the error message.
2673
+ coerce_tests! %r{resolve raises if the adapter is using the pre 7.2 adapter registration API}
2674
+ def resolve_raises_if_the_adapter_is_using_the_pre_7_2_adapter_registration_API
2675
+ exception = assert_raises(ActiveRecord::AdapterNotFound) do
2676
+ ActiveRecord::ConnectionAdapters.resolve("fake_legacy")
2677
+ end
2678
+
2679
+ assert_equal(
2680
+ "Database configuration specifies nonexistent 'ridiculous' adapter. Available adapters are: abstract, fake, mysql2, postgresql, sqlite3, sqlserver, trilogy. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile if it's not in the list of available adapters.",
2681
+ exception.message
2682
+ )
2683
+ ensure
2684
+ ActiveRecord::ConnectionAdapters.instance_variable_get(:@adapters).delete("fake_legacy")
2685
+ end
2686
+ end
2687
+ end
2688
+ end
2689
+
2690
+
2670
2691
  module ActiveRecord
2671
2692
  class TableMetadataTest < ActiveSupport::TestCase
2672
2693
  # Adapter returns an object that is subclass of what is expected in the original test.
@@ -36,7 +36,6 @@ class OptimizerHitsTestSQLServer < ActiveRecord::TestCase
36
36
  end
37
37
  end
38
38
 
39
-
40
39
  it "support order" do
41
40
  assert_queries_match(%r{\ASELECT .+ FROM .+ ORDER .+ OPTION .+\z}) do
42
41
  companies = Company.optimizer_hints("LABEL='FindCompanies'")
@@ -101,11 +101,5 @@ class SchemaTestSQLServer < ActiveRecord::TestCase
101
101
  assert_equal "[with].[select notation]", connection.send(:get_raw_table_name, "INSERT INTO [with].[select notation] SELECT * FROM [table_name]")
102
102
  end
103
103
  end
104
-
105
- describe 'CREATE VIEW statements' do
106
- it do
107
- assert_equal "test_table_as", connection.send(:get_raw_table_name, "CREATE VIEW test_views ( test_table_a_id, test_table_b_id ) AS SELECT test_table_as.id as test_table_a_id, test_table_bs.id as test_table_b_id FROM (test_table_as with(nolock) LEFT JOIN test_table_bs with(nolock) ON (test_table_as.id = test_table_bs.test_table_a_id))")
108
- end
109
- end
110
104
  end
111
105
  end
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: 7.2.5
4
+ version: 8.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ken Collins
@@ -15,7 +15,7 @@ authors:
15
15
  autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
- date: 2025-03-10 00:00:00.000000000 Z
18
+ date: 2024-11-11 00:00:00.000000000 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: activerecord
@@ -23,14 +23,14 @@ dependencies:
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 7.2.0
26
+ version: 8.0.0
27
27
  type: :runtime
28
28
  prerelease: false
29
29
  version_requirements: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 7.2.0
33
+ version: 8.0.0
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: tiny_tds
36
36
  requirement: !ruby/object:Gem::Requirement
@@ -79,7 +79,6 @@ files:
79
79
  - lib/active_record/connection_adapters/sqlserver/core_ext/abstract_adapter.rb
80
80
  - lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb
81
81
  - lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb
82
- - lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb
83
82
  - lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb
84
83
  - lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb
85
84
  - lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb
@@ -177,7 +176,6 @@ files:
177
176
  - test/cases/schema_test_sqlserver.rb
178
177
  - test/cases/showplan_test_sqlserver.rb
179
178
  - test/cases/specific_schema_test_sqlserver.rb
180
- - test/cases/temporary_table_test_sqlserver.rb
181
179
  - test/cases/transaction_test_sqlserver.rb
182
180
  - test/cases/trigger_test_sqlserver.rb
183
181
  - test/cases/utils_test_sqlserver.rb
@@ -240,8 +238,8 @@ licenses:
240
238
  - MIT
241
239
  metadata:
242
240
  bug_tracker_uri: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues
243
- changelog_uri: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/v7.2.5/CHANGELOG.md
244
- source_code_uri: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/v7.2.5
241
+ changelog_uri: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/v8.0.0/CHANGELOG.md
242
+ source_code_uri: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/v8.0.0
245
243
  post_install_message:
246
244
  rdoc_options: []
247
245
  require_paths:
@@ -250,7 +248,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
250
248
  requirements:
251
249
  - - ">="
252
250
  - !ruby/object:Gem::Version
253
- version: 3.1.0
251
+ version: 3.2.0
254
252
  required_rubygems_version: !ruby/object:Gem::Requirement
255
253
  requirements:
256
254
  - - ">="
@@ -296,7 +294,6 @@ test_files:
296
294
  - test/cases/schema_test_sqlserver.rb
297
295
  - test/cases/showplan_test_sqlserver.rb
298
296
  - test/cases/specific_schema_test_sqlserver.rb
299
- - test/cases/temporary_table_test_sqlserver.rb
300
297
  - test/cases/transaction_test_sqlserver.rb
301
298
  - test/cases/trigger_test_sqlserver.rb
302
299
  - test/cases/utils_test_sqlserver.rb
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_record/relation"
4
- require "active_record/version"
5
-
6
- module ActiveRecord
7
- module ConnectionAdapters
8
- module SQLServer
9
- module CoreExt
10
- module Calculations
11
-
12
- private
13
-
14
- def build_count_subquery(relation, column_name, distinct)
15
- klass.with_connection do |connection|
16
- relation = relation.unscope(:order) if connection.sqlserver?
17
- super(relation, column_name, distinct)
18
- end
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end
25
-
26
- ActiveSupport.on_load(:active_record) do
27
- mod = ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::Calculations
28
- ActiveRecord::Relation.include(mod)
29
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "cases/helper_sqlserver"
4
-
5
- class TemporaryTableSQLServer < ActiveRecord::TestCase
6
- def test_insert_into_temporary_table
7
- ActiveRecord::Base.with_connection do |conn|
8
- conn.exec_query("CREATE TABLE #temp_users (id INT IDENTITY(1,1), name NVARCHAR(100))")
9
-
10
- result = conn.exec_query("SELECT * FROM #temp_users")
11
- assert_equal 0, result.count
12
-
13
- conn.exec_query("INSERT INTO #temp_users (name) VALUES ('John'), ('Doe')")
14
-
15
- result = conn.exec_query("SELECT * FROM #temp_users")
16
- assert_equal 2, result.count
17
- end
18
- end
19
- end