activerecord-sqlserver-adapter 6.0.3 → 6.1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -3
  3. data/CHANGELOG.md +18 -77
  4. data/Gemfile +1 -2
  5. data/README.md +13 -2
  6. data/VERSION +1 -1
  7. data/activerecord-sqlserver-adapter.gemspec +1 -1
  8. data/appveyor.yml +7 -5
  9. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +0 -2
  10. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +0 -13
  11. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +7 -4
  12. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +0 -2
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +0 -2
  14. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
  15. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +27 -15
  16. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +3 -3
  17. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -1
  18. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -3
  19. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +7 -5
  20. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +27 -7
  21. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +0 -1
  22. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +2 -2
  23. data/lib/active_record/connection_adapters/sqlserver/utils.rb +1 -1
  24. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +83 -66
  25. data/lib/active_record/connection_adapters/sqlserver_column.rb +17 -0
  26. data/lib/active_record/sqlserver_base.rb +9 -15
  27. data/lib/active_record/tasks/sqlserver_database_tasks.rb +17 -14
  28. data/lib/arel/visitors/sqlserver.rb +60 -28
  29. data/test/cases/adapter_test_sqlserver.rb +17 -15
  30. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  31. data/test/cases/coerced_tests.rb +470 -95
  32. data/test/cases/disconnected_test_sqlserver.rb +39 -0
  33. data/test/cases/execute_procedure_test_sqlserver.rb +9 -0
  34. data/test/cases/in_clause_test_sqlserver.rb +27 -0
  35. data/test/cases/migration_test_sqlserver.rb +7 -0
  36. data/test/cases/order_test_sqlserver.rb +7 -0
  37. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  38. data/test/cases/rake_test_sqlserver.rb +3 -2
  39. data/test/cases/schema_dumper_test_sqlserver.rb +9 -0
  40. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  41. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  42. data/test/schema/sqlserver_specific_schema.rb +7 -0
  43. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  44. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  45. data/test/support/sql_counter_sqlserver.rb +14 -12
  46. metadata +23 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a4ddc7c26331060001ac99b3e2ea5c7d30a97204237c48c383b356280a763ac
4
- data.tar.gz: c9e1154f442e957e1930fa7cc36addf9b8f465d579d44fcb87fb6a4ffe0426bb
3
+ metadata.gz: 257660386743662b1e8052c5073aaa07bdff955924fae3da4ba83c41229deb85
4
+ data.tar.gz: 288cf426206b713aeca8b0647f57059a4a781d00cab800340d8f5c0bad0acbdc
5
5
  SHA512:
6
- metadata.gz: fc3e2cbdcd6ef7d2f334a4af20a66e9c1f1a4ec28548dc664e879a94e87aa487a0d68c6468182b3a026f3e05aa0c735ce16c774b8b404cb8f7e140188d8c06f0
7
- data.tar.gz: 5f759150e7c204c5de007a320897b71bfe80913ee78d162dcfa891ab840102089f3798a7641ccaa2fe6ef6a56250deed62877f2b7900983f705a21677985c291
6
+ metadata.gz: 22efb62e73df4116659bbac023f3694192476cc5d4d2840c469b19b0f6c37145cf989803c94c53d45aa78943b2a72df419ffbc15b452f612ab6f420fee10a19c
7
+ data.tar.gz: 70d8088ec4278d48beb73cc1319f681d6f3ee874d200b2868370425a2a0a9b2e2859171b5cc3817b3abf63cd512911655813bafe65ae5e319dc1bb889bf0f575
@@ -13,9 +13,7 @@ jobs:
13
13
  strategy:
14
14
  fail-fast: false
15
15
  matrix:
16
- ruby:
17
- - 2.7.5
18
- - 3.0.3
16
+ ruby: [2.5.9, 2.6.7, 2.7.3, 3.0.1]
19
17
 
20
18
  steps:
21
19
  - name: Checkout code
data/CHANGELOG.md CHANGED
@@ -1,84 +1,25 @@
1
- ## v6.0.3
1
+ ## v6.1.0.0.rc1
2
2
 
3
3
  #### Fixed
4
4
 
5
- [#1054](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1054) Conditionally apply SQL Server monkey patches to ActiveRecord so that it is safe to use this gem alongside other database adapters (e.g. PostgreSQL) in a multi-database Rails app
6
-
7
- ## v6.0.2
8
-
9
- #### Fixed
10
-
11
- - [#858](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/858) Allow table existence to be tested across database schemas.
12
-
13
- #### Changed
14
-
15
- - [#852](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/852) Updated the column name matchers to accept database and owner names
16
-
17
- #### Added
18
-
19
- - [#855](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/855) Add helpers to create/change/drop a schema.
20
- - [#857](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/857) Included WAITFOR as read query type.
21
- - [#865](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/865) Implemented optimizer hints.
22
- - [#845](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/845) Add support for lateral using CROSS/OUTER APPLY.
23
- - [#870](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/870) Added DecimalWithoutScale type
24
-
25
- ## v6.0.1
26
-
27
- #### Fixed
28
-
29
- - [#851](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/851) Updated 'column_definitions_sql' to ensure that only primary key key constraints are queried for
30
-
31
- ## v6.0.0
32
-
33
- **No Changes**
34
-
35
- ## v6.0.0.rc2
36
-
37
- #### Fixed
38
-
39
- - [#639](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/639) Primary key should be lowercase if schema forced to lowercase
40
- - [#720](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/720) quoted_date doesn't work for Type::DateTime
41
-
42
- #### Changed
43
-
44
- - [#826](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/826) Rubocop: Enable Style/StringLiterals cop
45
- - [#827](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/827) Rubocop: Enable Layout/EmptyLinesAroundClassBody cop
46
- - [#828](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/828) Rubocop: Enable Layout/EmptyLines cop
47
- - [#829](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/829) Rubocop: Enable Layout/Layout/EmptyLinesAround* cops
48
- - [#830](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/830) Rubocop: Enable Layout/IndentationWidth and Layout/TrailingWhitespace cops
49
- - [#831](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/831) Rubocop: Enable Spacing cops
50
- - [#832](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/832) Rubocop: Enable Bundler cops
51
- - [#833](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/833) Rubocop: Enable Layout/* cops
52
- - [#834](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/834) Rubocop: Enable Lint/UselessAssignment cop
53
- - [#835](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/835) Rubocop: Configure Naming cops
54
-
55
- ## v6.0.0.rc1
56
-
57
- #### Fixed
58
-
59
- - [#690](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/690) Rails 6 support
60
- - [#805](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/805) Rails 6: Fix database tasks tests for SQL Server
61
- - [#807](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/807) Rails 6: Skip binary fixtures test on Windows
62
- - [#809](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/809) Rails 6: Coerce reaper test using fork
63
- - [#810](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/810) Rails 6: Fix randomly failing tests due to schema load
64
- - [#812](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/812) Rails 6: Coerce ReloadModelsTest test on Windows
65
- - [#818](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/818) Handle false return by TinyTDS if connection fails and fixed CI
66
- - [#819](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/819) Fix Ruby 2.7 kwargs warnings
67
- - [#825](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/825) Adjust error message when connection is dead
5
+ - [#872](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/872) Use native String#start_with
6
+ - [#876](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/876) Use native String#end_with
7
+ - [#873](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/873) Various fixes to get the tests running for Rails 6.1
8
+ - [#874](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/874) Deduplicate schema cache structures
9
+ - [#875](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/875) Handle default boolean column values when deduplicating
10
+ - [#879](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/879) Added visit method for HomogeneousIn
11
+ - [#880](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/880) Handle any default column class when deduplicating
12
+ - [#861](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/861) Fix Rails 6.1 database config
13
+ - [#890](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/890) Fix removal of invalid ordering from select statements
14
+ - [#881](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/881) Dump column collation to schema.rb and allow collation changes using column_change
15
+ - [#891](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/891) Add support for if_not_exists to indexes
16
+ - [#892](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/892) Add support for if_exists on remove_column
17
+ - [#883](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/885) Fix quoting of ActiveRecord::Relation::QueryAttribute and ActiveModel::Attributes
18
+ - [#893](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/893) Add Active Record Marshal forward compatibility tests
19
+ - [#903](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/903) Raise ActiveRecord::ConnectionNotEstablished on calls to execute with a disconnected connection
68
20
 
69
21
  #### Changed
70
22
 
71
- - [#716](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/716) Translate the connection timed out error
72
- - [#763](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/763) Refactor columns introspection query to make it faster
73
- - [#783](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/783) Update test matrix
74
- - [#820](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/820) Enable frozen strings for tests
75
- - [#821](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/821) Enable frozen strings - part 1
76
- - [#822](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/822) Enable frozen strings - part 2
77
- - [#823](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/823) Enable frozen strings - final
78
- - [#824](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/824) Tidy up Gemfile
79
-
80
- #### Added
81
-
82
- - [#726](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/726) How to Develop ActiveRecord SQL Server Adapter with Pre-Installed MS SQL
23
+ - [#917](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/917) Refactored to use new_client connection pattern
83
24
 
84
- Please check [5-2-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/5-2-stable/CHANGELOG.md) for previous changes.
25
+ Please check [6-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/6-0-stable/CHANGELOG.md) for previous changes.
data/Gemfile CHANGED
@@ -7,10 +7,9 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
7
7
  gemspec
8
8
 
9
9
  gem "bcrypt"
10
- gem "pg", "~> 1.3"
10
+ gem "pg", ">= 0.18.0"
11
11
  gem "sqlite3", "~> 1.4"
12
12
  gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
13
- gem "minitest", ">= 5.15.0", "< 5.16"
14
13
 
15
14
  if ENV["RAILS_SOURCE"]
16
15
  gemspec path: ENV["RAILS_SOURCE"]
data/README.md CHANGED
@@ -7,9 +7,20 @@
7
7
 
8
8
  ## About The Adapter
9
9
 
10
- The SQL Server adapter for ActiveRecord v6.0 using SQL Server 2012 or higher.
10
+ The SQL Server adapter for ActiveRecord using SQL Server 2012 or higher.
11
11
 
12
- Interested in older versions? We follow a rational versioning policy that tracks Rails. That means that our 5.2.x version of the adapter is only for the latest 5.2 version of Rails. If you need the adapter for SQL Server 2008 or 2005, you 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 version. We also have stable branches for each major/minor release of ActiveRecord.
12
+ Interested in older versions? We follow a rational versioning policy that tracks Rails. That means that our 6.x version of the adapter is only for the latest 6.x version of Rails. If you need the adapter for SQL Server 2008 or 2005, you 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 version. We also have stable branches for each major/minor release of ActiveRecord.
13
+
14
+ | Adapter Version | Rails Version | Support |
15
+ | ------------- | --- | --- |
16
+ | `6.1.0.0.rc1` | `6.1.x` | [wip](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) |
17
+ | `6.0.2` | `6.0.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-0-stable) |
18
+ | `5.2.1` | `5.2.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/5-2-stable) |
19
+ | `5.1.6` | `5.1.x` | [ended](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/5-1-stable) |
20
+ | `4.2.18` | `4.2.x` | [ended](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/4-2-stable) |
21
+ | `4.1.8` | `4.1.x` | [ended](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/4-1-stable) |
22
+
23
+ For older versions, please check their stable branches.
13
24
 
14
25
  #### Native Data Type Support
15
26
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 6.0.3
1
+ 6.1.0.0.rc1
@@ -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", "~> 6.0.0"
30
+ spec.add_dependency "activerecord", "~> 6.1.0"
31
31
  spec.add_dependency "tiny_tds"
32
32
  end
data/appveyor.yml CHANGED
@@ -5,10 +5,10 @@ build: off
5
5
  matrix:
6
6
  fast_finish: true
7
7
  allow_failures:
8
- - ruby_version: "27-x64"
8
+ - ruby_version: "25"
9
+ - ruby_version: "26"
9
10
  - ruby_version: "27"
10
- - ruby_version: "30"
11
- - ruby_version: "30-x64"
11
+ - ruby_version: "27-x64"
12
12
  services:
13
13
  - mssql2014
14
14
 
@@ -38,7 +38,9 @@ environment:
38
38
  CI_AZURE_PASS:
39
39
  secure: cSQp8sk4urJYvq0utpsK+r7J+snJ2wpcdp8RdXJfB+w=
40
40
  matrix:
41
+ - ruby_version: "25-x64"
42
+ - ruby_version: "25"
43
+ - ruby_version: "26-x64"
44
+ - ruby_version: "26"
41
45
  - ruby_version: "27-x64"
42
46
  - ruby_version: "27"
43
- - ruby_version: "30"
44
- - ruby_version: "30-x64"
@@ -10,8 +10,6 @@ module ActiveRecord
10
10
  private
11
11
 
12
12
  def attributes_for_update(attribute_names)
13
- return super unless self.class.connection.adapter_name == "SQLServer"
14
-
15
13
  super.reject do |name|
16
14
  column = self.class.columns_hash[name]
17
15
  column && column.respond_to?(:is_identity?) && column.is_identity?
@@ -10,8 +10,6 @@ module ActiveRecord
10
10
  module Calculations
11
11
  # Same as original except we don't perform PostgreSQL hack that removes ordering.
12
12
  def calculate(operation, column_name)
13
- return super unless klass.connection.adapter_name == "SQLServer"
14
-
15
13
  if has_include?(column_name)
16
14
  relation = apply_join_dependency
17
15
 
@@ -31,19 +29,8 @@ module ActiveRecord
31
29
  private
32
30
 
33
31
  def build_count_subquery(relation, column_name, distinct)
34
- return super unless klass.connection.adapter_name == "SQLServer"
35
-
36
32
  super(relation.unscope(:order), column_name, distinct)
37
33
  end
38
-
39
- def type_cast_calculated_value(value, type, operation = nil)
40
- case operation
41
- when "count" then value.to_i
42
- when "sum" then type.deserialize(value || 0)
43
- when "average" then value&.respond_to?(:to_d) ? value.to_d : value
44
- else type.deserialize(value)
45
- end
46
- end
47
34
  end
48
35
  end
49
36
  end
@@ -9,8 +9,6 @@ module ActiveRecord
9
9
  SQLSERVER_STATEMENT_REGEXP = /N'(.+)', N'(.+)', (.+)/
10
10
 
11
11
  def exec_explain(queries)
12
- return super unless connection.adapter_name == "SQLServer"
13
-
14
12
  unprepared_queries = queries.map do |(sql, binds)|
15
13
  [unprepare_sqlserver_statement(sql, binds), binds]
16
14
  end
@@ -23,13 +21,18 @@ module ActiveRecord
23
21
  # which uses sp_executesql to just the first argument, then unquote it. Likewise our
24
22
  # `sp_executesql` method should substitude the @n args with the quoted values.
25
23
  def unprepare_sqlserver_statement(sql, binds)
26
- return sql unless sql.starts_with?(SQLSERVER_STATEMENT_PREFIX)
24
+ return sql unless sql.start_with?(SQLSERVER_STATEMENT_PREFIX)
27
25
 
28
26
  executesql = sql.from(SQLSERVER_STATEMENT_PREFIX.length)
29
27
  executesql = executesql.match(SQLSERVER_STATEMENT_REGEXP).to_a[1]
30
28
 
31
29
  binds.each_with_index do |bind, index|
32
- value = connection.quote(bind)
30
+
31
+ value = if bind.is_a?(::ActiveModel::Attribute) then
32
+ connection.quote(bind.value_for_database)
33
+ else
34
+ connection.quote(bind)
35
+ end
33
36
  executesql = executesql.sub("@#{index}", value)
34
37
  end
35
38
 
@@ -12,8 +12,6 @@ module ActiveRecord
12
12
 
13
13
  # Same as original except we order by values in distinct select if present.
14
14
  def construct_relation_for_exists(conditions)
15
- return super unless klass.connection.adapter_name == "SQLServer"
16
-
17
15
  conditions = sanitize_forbidden_attributes(conditions)
18
16
 
19
17
  if distinct_value && offset_value
@@ -10,8 +10,6 @@ module ActiveRecord
10
10
  private
11
11
 
12
12
  def records_for(ids)
13
- return super unless klass.connection.adapter_name == "SQLServer"
14
-
15
13
  ids.each_slice(in_clause_length).flat_map do |slice|
16
14
  scope.where(association_key_name => slice).load do |record|
17
15
  # Processing only the first owner
@@ -37,10 +37,6 @@ module ActiveRecord
37
37
  end
38
38
  deprecate :columns_per_multicolumn_index
39
39
 
40
- def in_clause_length
41
- 10_000
42
- end
43
-
44
40
  def sql_query_length
45
41
  65_536 * 4_096
46
42
  end
@@ -17,6 +17,7 @@ module ActiveRecord
17
17
  end
18
18
 
19
19
  materialize_transactions
20
+ mark_transaction_written_if_write(sql)
20
21
 
21
22
  if id_insert_table_name = query_requires_identity_insert?(sql)
22
23
  with_identity_insert_enabled(id_insert_table_name) { do_execute(sql, name) }
@@ -31,6 +32,7 @@ module ActiveRecord
31
32
  end
32
33
 
33
34
  materialize_transactions
35
+ mark_transaction_written_if_write(sql)
34
36
 
35
37
  sp_executesql(sql, name, binds, prepare: prepare)
36
38
  end
@@ -54,7 +56,7 @@ module ActiveRecord
54
56
  end
55
57
 
56
58
  def begin_db_transaction
57
- do_execute "BEGIN TRANSACTION"
59
+ do_execute "BEGIN TRANSACTION", "TRANSACTION"
58
60
  end
59
61
 
60
62
  def transaction_isolation_levels
@@ -67,25 +69,25 @@ module ActiveRecord
67
69
  end
68
70
 
69
71
  def set_transaction_isolation_level(isolation_level)
70
- do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
72
+ do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}", "TRANSACTION"
71
73
  end
72
74
 
73
75
  def commit_db_transaction
74
- do_execute "COMMIT TRANSACTION"
76
+ do_execute "COMMIT TRANSACTION", "TRANSACTION"
75
77
  end
76
78
 
77
79
  def exec_rollback_db_transaction
78
- do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
80
+ do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION", "TRANSACTION"
79
81
  end
80
82
 
81
83
  include Savepoints
82
84
 
83
85
  def create_savepoint(name = current_savepoint_name)
84
- do_execute "SAVE TRANSACTION #{name}"
86
+ do_execute "SAVE TRANSACTION #{name}", "TRANSACTION"
85
87
  end
86
88
 
87
89
  def exec_rollback_to_savepoint(name = current_savepoint_name)
88
- do_execute "ROLLBACK TRANSACTION #{name}"
90
+ do_execute "ROLLBACK TRANSACTION #{name}", "TRANSACTION"
89
91
  end
90
92
 
91
93
  def release_savepoint(name = current_savepoint_name)
@@ -165,7 +167,7 @@ module ActiveRecord
165
167
  log(sql, name) do
166
168
  case @connection_options[:mode]
167
169
  when :dblib
168
- result = @connection.execute(sql)
170
+ result = ensure_established_connection! { dblib_execute(sql) }
169
171
  options = { as: :hash, cache_rows: true, timezone: ActiveRecord::Base.default_timezone || :utc }
170
172
  result.each(options) do |row|
171
173
  r = row.with_indifferent_access
@@ -291,6 +293,7 @@ module ActiveRecord
291
293
 
292
294
  def do_execute(sql, name = "SQL")
293
295
  materialize_transactions
296
+ mark_transaction_written_if_write(sql)
294
297
 
295
298
  log(sql, name) { raw_connection_do(sql) }
296
299
  end
@@ -354,13 +357,7 @@ module ActiveRecord
354
357
  def raw_connection_do(sql)
355
358
  case @connection_options[:mode]
356
359
  when :dblib
357
- result = @connection.execute(sql)
358
-
359
- # TinyTDS returns false instead of raising an exception if connection fails.
360
- # Getting around this by raising an exception ourselves while this PR
361
- # https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
362
- raise TinyTds::Error, "failed to execute statement" if result.is_a?(FalseClass)
363
-
360
+ result = ensure_established_connection! { dblib_execute(sql) }
364
361
  result.do
365
362
  end
366
363
  ensure
@@ -425,7 +422,7 @@ module ActiveRecord
425
422
  def raw_connection_run(sql)
426
423
  case @connection_options[:mode]
427
424
  when :dblib
428
- @connection.execute(sql)
425
+ ensure_established_connection! { dblib_execute(sql) }
429
426
  end
430
427
  end
431
428
 
@@ -459,6 +456,21 @@ module ActiveRecord
459
456
  end
460
457
  handle
461
458
  end
459
+
460
+ def dblib_execute(sql)
461
+ @connection.execute(sql).tap do |result|
462
+ # TinyTDS returns false instead of raising an exception if connection fails.
463
+ # Getting around this by raising an exception ourselves while this PR
464
+ # https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
465
+ raise TinyTds::Error, "failed to execute statement" if result.is_a?(FalseClass)
466
+ end
467
+ end
468
+
469
+ def ensure_established_connection!
470
+ raise TinyTds::Error, 'SQL Server client is not connected' unless @connection
471
+
472
+ yield
473
+ end
462
474
  end
463
475
  end
464
476
  end
@@ -10,14 +10,14 @@ module ActiveRecord
10
10
 
11
11
  def fetch_type_metadata(sql_type, sqlserver_options = {})
12
12
  cast_type = lookup_cast_type(sql_type)
13
- SQLServer::SqlTypeMetadata.new(
13
+ simple_type = SqlTypeMetadata.new(
14
14
  sql_type: sql_type,
15
15
  type: cast_type.type,
16
16
  limit: cast_type.limit,
17
17
  precision: cast_type.precision,
18
- scale: cast_type.scale,
19
- sqlserver_options: sqlserver_options
18
+ scale: cast_type.scale
20
19
  )
20
+ SQLServer::TypeMetadata.new(simple_type, sqlserver_options: sqlserver_options)
21
21
  end
22
22
 
23
23
  def quote_string(s)
@@ -3,9 +3,13 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLServer
6
- class SchemaCreation < AbstractAdapter::SchemaCreation
6
+ class SchemaCreation < SchemaCreation
7
7
  private
8
8
 
9
+ def supports_index_using?
10
+ false
11
+ end
12
+
9
13
  def visit_TableDefinition(o)
10
14
  if_not_exists = o.if_not_exists
11
15
 
@@ -29,11 +33,28 @@ module ActiveRecord
29
33
  sql
30
34
  end
31
35
 
36
+ def visit_CreateIndexDefinition(o)
37
+ if_not_exists = o.if_not_exists
38
+
39
+ o.if_not_exists = false
40
+
41
+ sql = super
42
+
43
+ if if_not_exists
44
+ sql = "IF NOT EXISTS (SELECT name FROM sysindexes WHERE name = '#{o.index.name}') #{sql}"
45
+ end
46
+
47
+ sql
48
+ end
49
+
32
50
  def add_column_options!(sql, options)
33
51
  sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
34
52
  if options[:null] == false
35
53
  sql << " NOT NULL"
36
54
  end
55
+ if options[:collation].present?
56
+ sql << " COLLATE #{options[:collation]}"
57
+ end
37
58
  if options[:is_identity] == true
38
59
  sql << " IDENTITY(1,1)"
39
60
  end
@@ -15,7 +15,7 @@ module ActiveRecord
15
15
  private
16
16
 
17
17
  def explicit_primary_key_default?(column)
18
- column.is_primary? && !column.is_identity?
18
+ column.type == :integer && !column.is_identity?
19
19
  end
20
20
 
21
21
  def schema_limit(column)
@@ -27,11 +27,17 @@ module ActiveRecord
27
27
  def schema_collation(column)
28
28
  return unless column.collation
29
29
 
30
- column.collation if column.collation != @connection.collation
30
+ # use inspect to ensure collation is dumped as string. Without this it's dumped as
31
+ # a constant ('collation: SQL_Latin1_General_CP1_CI_AS')
32
+ collation = column.collation.inspect
33
+ # use inspect to ensure string comparison
34
+ default_collation = @connection.collation.inspect
35
+
36
+ collation if collation != default_collation
31
37
  end
32
38
 
33
39
  def default_primary_key?(column)
34
- super && column.is_primary? && column.is_identity?
40
+ super && column.is_identity?
35
41
  end
36
42
  end
37
43
  end
@@ -27,7 +27,7 @@ module ActiveRecord
27
27
  end
28
28
  end
29
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)}"
30
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}", "SCHEMA"
31
31
  else
32
32
  super
33
33
  end
@@ -51,7 +51,7 @@ module ActiveRecord
51
51
  index[:index_keys].split(",").each do |column|
52
52
  column.strip!
53
53
 
54
- if column.ends_with?("(-)")
54
+ if column.end_with?("(-)")
55
55
  column.gsub! "(-)", ""
56
56
  orders[column] = :desc
57
57
  end
@@ -130,8 +130,9 @@ module ActiveRecord
130
130
  rename_table_indexes(table_name, new_name)
131
131
  end
132
132
 
133
- def remove_column(table_name, column_name, type = nil, options = {})
133
+ def remove_column(table_name, column_name, type = nil, **options)
134
134
  raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_name.is_a? Array
135
+ return if options[:if_exists] == true && !column_exists?(table_name, column_name)
135
136
 
136
137
  remove_check_constraints(table_name, column_name)
137
138
  remove_default_constraint(table_name, column_name)
@@ -156,6 +157,7 @@ module ActiveRecord
156
157
  end
157
158
  sql_commands << "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(options[:default], column_object)} WHERE #{quote_column_name(column_name)} IS NULL" if !options[:null].nil? && options[:null] == false && !options[:default].nil?
158
159
  alter_command = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, limit: options[:limit], precision: options[:precision], scale: options[:scale])}"
160
+ alter_command += " COLLATE #{options[:collation]}" if options[:collation].present?
159
161
  alter_command += " NOT NULL" if !options[:null].nil? && options[:null] == false
160
162
  sql_commands << alter_command
161
163
  if without_constraints
@@ -190,7 +192,7 @@ module ActiveRecord
190
192
  end
191
193
 
192
194
  def rename_index(table_name, old_name, new_name)
193
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" if new_name.length > allowed_index_name_length
195
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters" if new_name.length > index_name_length
194
196
 
195
197
  identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{old_name}")
196
198
  execute_procedure :sp_rename, identifier.quoted, new_name, "INDEX"
@@ -330,7 +332,7 @@ module ActiveRecord
330
332
  def initialize_native_database_types
331
333
  {
332
334
  primary_key: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY",
333
- primary_key_nonclustered: "int NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED",
335
+ primary_key_nonclustered: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED",
334
336
  integer: { name: "int", limit: 4 },
335
337
  bigint: { name: "bigint" },
336
338
  boolean: { name: "bit" },
@@ -3,16 +3,36 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLServer
6
- class SqlTypeMetadata < ActiveRecord::ConnectionAdapters::SqlTypeMetadata
7
- def initialize(**kwargs)
8
- @sqlserver_options = kwargs.extract!(:sqlserver_options)
9
- super(**kwargs)
6
+ class TypeMetadata < DelegateClass(SqlTypeMetadata)
7
+ undef to_yaml if method_defined?(:to_yaml)
8
+
9
+ include Deduplicable
10
+
11
+ attr_reader :sqlserver_options
12
+
13
+ def initialize(type_metadata, sqlserver_options: nil)
14
+ super(type_metadata)
15
+ @sqlserver_options = sqlserver_options
16
+ end
17
+
18
+ def ==(other)
19
+ other.is_a?(TypeMetadata) &&
20
+ __getobj__ == other.__getobj__ &&
21
+ sqlserver_options == other.sqlserver_options
22
+ end
23
+ alias eql? ==
24
+
25
+ def hash
26
+ TypeMetadata.hash ^
27
+ __getobj__.hash ^
28
+ sqlserver_options.hash
10
29
  end
11
30
 
12
- protected
31
+ private
13
32
 
14
- def attributes_for_hash
15
- super + [@sqlserver_options]
33
+ def deduplicated
34
+ __setobj__(__getobj__.deduplicate)
35
+ super
16
36
  end
17
37
  end
18
38
  end
@@ -9,7 +9,6 @@ module ActiveRecord
9
9
  options[:is_identity] = true unless options.key?(:default)
10
10
  elsif type == :uuid
11
11
  options[:default] = options.fetch(:default, "NEWID()")
12
- options[:primary_key] = true
13
12
  end
14
13
  super
15
14
  end
@@ -31,9 +31,9 @@ module ActiveRecord
31
31
  module SQLServerRealTransaction
32
32
  attr_reader :starting_isolation_level
33
33
 
34
- def initialize(connection, options, **args)
34
+ def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
35
35
  @connection = connection
36
- @starting_isolation_level = current_isolation_level if options[:isolation]
36
+ @starting_isolation_level = current_isolation_level if isolation
37
37
  super
38
38
  end
39
39