activerecord-sqlserver-adapter 6.1.2.0 → 7.0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +4 -1
  3. data/CHANGELOG.md +16 -41
  4. data/Gemfile +1 -0
  5. data/MIT-LICENSE +1 -1
  6. data/README.md +18 -9
  7. data/VERSION +1 -1
  8. data/activerecord-sqlserver-adapter.gemspec +2 -2
  9. data/appveyor.yml +4 -6
  10. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +7 -15
  11. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +15 -6
  12. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +2 -4
  13. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +11 -9
  14. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +27 -8
  15. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +3 -1
  16. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +1 -1
  17. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +1 -1
  18. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -1
  19. data/lib/active_record/connection_adapters/sqlserver/utils.rb +16 -1
  20. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +84 -74
  21. data/lib/arel/visitors/sqlserver.rb +2 -0
  22. data/test/cases/active_schema_test_sqlserver.rb +55 -0
  23. data/test/cases/coerced_tests.rb +301 -85
  24. data/test/cases/column_test_sqlserver.rb +58 -58
  25. data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
  26. data/test/cases/rake_test_sqlserver.rb +2 -1
  27. data/test/cases/schema_dumper_test_sqlserver.rb +1 -1
  28. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +1 -1
  29. data/test/support/coerceable_test_sqlserver.rb +4 -4
  30. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
  31. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  32. data/test/support/rake_helpers.rb +3 -1
  33. metadata +15 -11
  34. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  35. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc687a6f9cd86f8c1f7b62356b266e84b47833c9abc25d6a339e387122ccbe4b
4
- data.tar.gz: 7b1cf7d3f5caa773943a722da4f39fe69435dbd03ce451c540e855f87642e640
3
+ metadata.gz: da2be1cf7b3b5d8c8ad40cb1c79e037d5054cdf522e8e98b53fd22185af10a24
4
+ data.tar.gz: 9d1da7dc2e59761988098939f3254979df4862f26a7edcb617d7a35ff720155d
5
5
  SHA512:
6
- metadata.gz: cfa048c171f318b7b4e46662b305e04a87e923c42cd1729f5d62bcd6c3e4e8cbbdacec56b767f537becb51c64d162cf556b1b9f69637732b346c37c823833393
7
- data.tar.gz: e4da6b9fa4c35f21afba43390d790097481dbe321c5c808d3be586eaec745d98e9af8ac10b394990266e733661f65081b12e10b958a64e457609910de4379205
6
+ metadata.gz: 51b6f8a1d55f2e27dd0301ee9b65a2922c7120b20db3831a774c33acd3b736035f6a6238c26a3491c817ebe808ccd8719eabfd957766a2064c8368ad2c6cd2ee
7
+ data.tar.gz: dc7b43ee4d532e44f1106064d3757ea3dd636ecc0bc5cec64d58a5c4de3e4b6bf5ce4017ad9bf6ee8c5a4503627b345e2b83811b71318765f5c4072c5e327456
@@ -13,7 +13,10 @@ jobs:
13
13
  strategy:
14
14
  fail-fast: false
15
15
  matrix:
16
- ruby: [2.5.9, 2.6.7, 2.7.3, 3.0.1]
16
+ ruby:
17
+ - 2.7.5
18
+ - 3.0.3
19
+ - 3.1.0
17
20
 
18
21
  steps:
19
22
  - name: Checkout code
data/CHANGELOG.md CHANGED
@@ -1,55 +1,30 @@
1
- ## v6.1.2.0
1
+ ## v7.0.0.0
2
2
 
3
- [Full changelog](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/compare/v6.1.1.0...v6.1.2.0)
3
+ [Full changelog](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/compare/v7.0.0.0.rc1...v7.0.0.0)
4
4
 
5
5
  #### Fixed
6
6
 
7
- - [#940](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/940) Primary key violation should result in RecordNotUnique error
7
+ - [#1002](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1002) Fix support for index types
8
8
 
9
9
  #### Changed
10
10
 
11
- - [#941](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/941) No longer support configuring the application name by overriding the 'configure_application_name' method.
11
+ - [#1004](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1004) Dump the precision for datetime columns following the new defaults.
12
12
 
13
- ## v6.1.1.0
13
+ ## v7.0.0.0.rc1
14
14
 
15
- [Full changelog](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/compare/v6.1.0.0...v6.1.1.0)
15
+ [Full changelog](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/compare/6-1-stable...v7.0.0.0.rc1)
16
16
 
17
- #### Fixed
18
-
19
- - [#933](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/933) 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
20
- - [#935](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/935) Fix schema cache generation
21
- (**breaking change**)
22
- - [#936](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/936) Fix deteministic fetch when table has a composite primary key
23
- - [#938](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/938) Fix date columns serialization for range values
24
-
25
- ## v6.1.0.0
26
-
27
- - No changes
28
-
29
- ## v6.1.0.0.rc1
30
-
31
- [Full changelog](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/compare/6-0-stable...v6.1.0.0.rc1)
32
-
33
- #### Fixed
17
+ #### Changed
34
18
 
35
- - [#872](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/872) Use native String#start_with
36
- - [#876](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/876) Use native String#end_with
37
- - [#873](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/873) Various fixes to get the tests running for Rails 6.1
38
- - [#874](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/874) Deduplicate schema cache structures
39
- - [#875](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/875) Handle default boolean column values when deduplicating
40
- - [#879](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/879) Added visit method for HomogeneousIn
41
- - [#880](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/880) Handle any default column class when deduplicating
42
- - [#861](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/861) Fix Rails 6.1 database config
43
- - [#890](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/890) Fix removal of invalid ordering from select statements
44
- - [#881](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/881) Dump column collation to schema.rb and allow collation changes using column_change
45
- - [#891](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/891) Add support for if_not_exists to indexes
46
- - [#892](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/892) Add support for if_exists on remove_column
47
- - [#883](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/885) Fix quoting of ActiveRecord::Relation::QueryAttribute and ActiveModel::Attributes
48
- - [#893](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/893) Add Active Record Marshal forward compatibility tests
49
- - [#903](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/903) Raise ActiveRecord::ConnectionNotEstablished on calls to execute with a disconnected connection
19
+ - [#968](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/968) Define adapter type maps statically
20
+ - [#983](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/983) Optimize remove_columns to use a single SQL statement
21
+ - [#984](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/984) Better handle SQL queries with invalid encoding
22
+ - [#988](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/988) Raise `ActiveRecord::StatementInvalid` when `columns` is called with a non-existing table (***breaking change***)
50
23
 
51
- #### Changed
24
+ #### Added
52
25
 
53
- - [#917](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/917) Refactored to use new_client connection pattern
26
+ - [#972](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/972) Support `ActiveRecord::QueryLogs`
27
+ - [#981](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/981) Support `find_by` an encrypted attribute
28
+ - [#985](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/985) Support string returning clause for `ActiveRecord#insert_all`
54
29
 
55
- Please check [6-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/6-0-stable/CHANGELOG.md) for previous changes.
30
+ Please check [6-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/6-1-stable/CHANGELOG.md) for previous changes.
data/Gemfile CHANGED
@@ -10,6 +10,7 @@ gem "bcrypt"
10
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 "benchmark-ips"
13
14
 
14
15
  if ENV["RAILS_SOURCE"]
15
16
  gemspec path: ENV["RAILS_SOURCE"]
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008-2015
1
+ Copyright (c) 2008-2022
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -9,13 +9,14 @@
9
9
 
10
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 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.
12
+ Interested in older versions? We follow a rational versioning policy that tracks Rails. That means that our 7.x version 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 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
13
 
14
14
  | Adapter Version | Rails Version | Support |
15
15
  | --------------- | ------------- | ------------------------------------------------------------------------------------------- |
16
- | `6.1.2.0` | `6.1.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) |
16
+ | `7.0.0.0` | `7.0.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) |
17
+ | `6.1.2.1` | `6.1.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/6-1-stable) |
17
18
  | `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.2.1` | `5.2.x` | [ended](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/5-2-stable) |
19
20
  | `5.1.6` | `5.1.x` | [ended](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/5-1-stable) |
20
21
  | `4.2.18` | `4.2.x` | [ended](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/4-2-stable) |
21
22
  | `4.1.8` | `4.1.x` | [ended](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/4-1-stable) |
@@ -66,7 +67,7 @@ ActiveRecord::Base.table_name_prefix = 'dbo.'
66
67
  It's also possible to create/change/drop a schema in the migration file as in the example below:
67
68
 
68
69
  ```ruby
69
- class CreateFooSchema < ActiveRecord::Migration[6.0]
70
+ class CreateFooSchema < ActiveRecord::Migration[7.0]
70
71
  def up
71
72
  create_schema('foo')
72
73
 
@@ -107,7 +108,7 @@ Below shows how you might use the database.yml file to use the process ID in you
107
108
  ```yaml
108
109
  development:
109
110
  adapter: sqlserver
110
- appname: <%= myapp_#{Process.pid} %>
111
+ appname: <%= "myapp_#{Process.pid}" %>
111
112
  ```
112
113
 
113
114
  #### Executing Stored Procedures
@@ -152,16 +153,25 @@ ActiveRecord::ConnectionAdapters::SQLServerAdapter.showplan_option = 'SHOWPLAN_X
152
153
  **NOTE:** The method we utilize to make SHOWPLANs work is very brittle to complex SQL. There is no getting around this as we have to deconstruct an already prepared statement for the sp_executesql method. If you find that explain breaks your app, simple disable it. Do not open a github issue unless you have a patch. Please [consult the Rails guides](http://guides.rubyonrails.org/active_record_querying.html#running-explain) for more info.
153
154
 
154
155
 
156
+ ## New Rails Applications
157
+
158
+ When creating a new Rails application you can specify that you want to use the SQL Server adapter using the `database` option:
159
+
160
+ ```
161
+ rails new my_app --database=sqlserver
162
+ ```
163
+
164
+ To then connect the application to your SQL Server instance edit the `config/database.yml` file with the username, password and host of your SQL Server instance.
165
+
166
+
155
167
  ## Installation
156
168
 
157
169
  The adapter has no strict gem dependencies outside of `ActiveRecord`. You will have to pick a connection mode, the default is dblib which uses the `TinyTDS` gem. Just bundle the gem and the adapter will use it.
158
170
 
159
171
  ```ruby
160
- gem 'tiny_tds'
161
172
  gem 'activerecord-sqlserver-adapter'
162
173
  ```
163
174
 
164
-
165
175
  ## Contributing
166
176
 
167
177
  If you would like to contribute a feature or bugfix, thanks! To make sure your fix/feature has a high chance of being added, please read the following guidelines. First, ask on the Gitter, or post a ticket on github issues. Second, make sure there are tests! We will not accept any patch that is not tested. Please read the [`RUNNING_UNIT_TESTS`](RUNNING_UNIT_TESTS.md) file for the details of how to run the unit tests.
@@ -178,5 +188,4 @@ You can see an up-to-date list of contributors here: http://github.com/rails-sql
178
188
 
179
189
  ## License
180
190
 
181
- Copyright © 2008-2020. It is free software, and may be redistributed under the terms specified in the [MIT-LICENSE](MIT-LICENSE) file.
182
-
191
+ Copyright © 2008-2022. It is free software, and may be redistributed under the terms specified in the [MIT-LICENSE](MIT-LICENSE) file.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 6.1.2.0
1
+ 7.0.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 = ">= 2.5.0"
10
+ spec.required_ruby_version = ">= 2.7.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"]
@@ -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.1.0"
30
+ spec.add_dependency "activerecord", "~> 7.0.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: "25"
9
- - ruby_version: "26"
10
8
  - ruby_version: "27"
11
9
  - ruby_version: "27-x64"
10
+ - ruby_version: "30"
11
+ - ruby_version: "30-x64"
12
12
  services:
13
13
  - mssql2014
14
14
 
@@ -38,9 +38,7 @@ 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"
45
41
  - ruby_version: "27-x64"
46
42
  - ruby_version: "27"
43
+ - ruby_version: "30-x64"
44
+ - ruby_version: "30"
@@ -6,20 +6,12 @@ module ActiveRecord
6
6
  module ConnectionAdapters
7
7
  module SQLServer
8
8
  module CoreExt
9
- module Preloader
10
- private
9
+ module LoaderQuery
10
+ def load_records_for_keys(keys, &block)
11
+ return super unless scope.connection.adapter_name == "SQLServer"
11
12
 
12
- def records_for(ids)
13
- return super unless klass.connection.adapter_name == "SQLServer"
14
-
15
- ids.each_slice(in_clause_length).flat_map do |slice|
16
- scope.where(association_key_name => slice).load do |record|
17
- # Processing only the first owner
18
- # because the record is modified but not an owner
19
- owner = owners_by_key[convert_key(record[association_key_name])].first
20
- association = owner.association(reflection.name)
21
- association.set_inverse_instance(record)
22
- end.records
13
+ keys.each_slice(in_clause_length).flat_map do |slice|
14
+ scope.where(association_key_name => slice).load(&block).records
23
15
  end
24
16
  end
25
17
 
@@ -33,6 +25,6 @@ module ActiveRecord
33
25
  end
34
26
 
35
27
  ActiveSupport.on_load(:active_record) do
36
- mod = ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::Preloader
37
- ActiveRecord::Associations::Preloader::Association.prepend(mod)
28
+ mod = ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::LoaderQuery
29
+ ActiveRecord::Associations::Preloader::Association::LoaderQuery.prepend(mod)
38
30
  end
@@ -9,9 +9,12 @@ module ActiveRecord
9
9
 
10
10
  def write_query?(sql) # :nodoc:
11
11
  !READ_QUERY.match?(sql)
12
+ rescue ArgumentError # Invalid encoding
13
+ !READ_QUERY.match?(sql.b)
12
14
  end
13
15
 
14
16
  def execute(sql, name = nil)
17
+ sql = transform_query(sql)
15
18
  if preventing_writes? && write_query?(sql)
16
19
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
17
20
  end
@@ -26,7 +29,8 @@ module ActiveRecord
26
29
  end
27
30
  end
28
31
 
29
- def exec_query(sql, name = "SQL", binds = [], prepare: false)
32
+ def exec_query(sql, name = "SQL", binds = [], prepare: false, async: false)
33
+ sql = transform_query(sql)
30
34
  if preventing_writes? && write_query?(sql)
31
35
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
32
36
  end
@@ -34,7 +38,7 @@ module ActiveRecord
34
38
  materialize_transactions
35
39
  mark_transaction_written_if_write(sql)
36
40
 
37
- sp_executesql(sql, name, binds, prepare: prepare)
41
+ sp_executesql(sql, name, binds, prepare: prepare, async: async)
38
42
  end
39
43
 
40
44
  def exec_insert(sql, name = nil, binds = [], pk = nil, _sequence_name = nil)
@@ -145,7 +149,12 @@ module ActiveRecord
145
149
  sql = +"INSERT #{insert.into}"
146
150
 
147
151
  if returning = insert.send(:insert_all).returning
148
- sql << " OUTPUT " << returning.map { |column| "INSERTED.#{quote_column_name(column)}" }.join(", ")
152
+ returning_sql = if returning.is_a?(String)
153
+ returning
154
+ else
155
+ returning.map { |column| "INSERTED.#{quote_column_name(column)}" }.join(", ")
156
+ end
157
+ sql << " OUTPUT #{returning_sql}"
149
158
  end
150
159
 
151
160
  sql << " #{insert.values_list}"
@@ -168,7 +177,7 @@ module ActiveRecord
168
177
  case @connection_options[:mode]
169
178
  when :dblib
170
179
  result = ensure_established_connection! { dblib_execute(sql) }
171
- options = { as: :hash, cache_rows: true, timezone: ActiveRecord::Base.default_timezone || :utc }
180
+ options = { as: :hash, cache_rows: true, timezone: ActiveRecord.default_timezone || :utc }
172
181
  result.each(options) do |row|
173
182
  r = row.with_indifferent_access
174
183
  yield(r) if block_given?
@@ -409,7 +418,7 @@ module ActiveRecord
409
418
  # === SQLServer Specific (Selecting) ============================ #
410
419
 
411
420
  def raw_select(sql, name = "SQL", binds = [], options = {})
412
- log(sql, name, binds) { _raw_select(sql, options) }
421
+ log(sql, name, binds, async: options[:async]) { _raw_select(sql, options) }
413
422
  end
414
423
 
415
424
  def _raw_select(sql, options = {})
@@ -441,7 +450,7 @@ module ActiveRecord
441
450
 
442
451
  def handle_to_names_and_values_dblib(handle, options = {})
443
452
  query_options = {}.tap do |qo|
444
- qo[:timezone] = ActiveRecord::Base.default_timezone || :utc
453
+ qo[:timezone] = ActiveRecord.default_timezone || :utc
445
454
  qo[:as] = (options[:ar_result] || options[:fetch] == :rows) ? :array : :hash
446
455
  end
447
456
  results = handle.each(query_options)
@@ -109,9 +109,7 @@ module ActiveRecord
109
109
 
110
110
  private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
111
111
 
112
- private
113
-
114
- def _quote(value)
112
+ def quote(value)
115
113
  case value
116
114
  when Type::Binary::Data
117
115
  "0x#{value.hex}"
@@ -124,7 +122,7 @@ module ActiveRecord
124
122
  end
125
123
  end
126
124
 
127
- def _type_cast(value)
125
+ def type_cast(value)
128
126
  case value
129
127
  when ActiveRecord::Type::SQLServer::Data
130
128
  value.to_s
@@ -34,17 +34,19 @@ module ActiveRecord
34
34
  end
35
35
 
36
36
  def visit_CreateIndexDefinition(o)
37
- if_not_exists = o.if_not_exists
38
-
39
- o.if_not_exists = false
40
-
41
- sql = super
37
+ index = o.index
42
38
 
43
- if if_not_exists
44
- sql = "IF NOT EXISTS (SELECT name FROM sysindexes WHERE name = '#{o.index.name}') #{sql}"
45
- end
39
+ sql = []
40
+ sql << "IF NOT EXISTS (SELECT name FROM sysindexes WHERE name = '#{o.index.name}')" if o.if_not_exists
41
+ sql << "CREATE"
42
+ sql << "UNIQUE" if index.unique
43
+ sql << index.type.upcase if index.type
44
+ sql << "INDEX"
45
+ sql << "#{quote_column_name(index.name)} ON #{quote_table_name(index.table)}"
46
+ sql << "(#{quoted_columns(index)})"
47
+ sql << "WHERE #{index.where}" if index.where
46
48
 
47
- sql
49
+ sql.join(" ")
48
50
  end
49
51
 
50
52
  def add_column_options!(sql, options)
@@ -378,7 +378,7 @@ module ActiveRecord
378
378
  binds << Relation::QueryAttribute.new("TABLE_NAME", identifier.object, nv128)
379
379
  binds << Relation::QueryAttribute.new("TABLE_SCHEMA", identifier.schema, nv128) unless identifier.schema.blank?
380
380
  results = sp_executesql(sql, "SCHEMA", binds)
381
- results.map do |ci|
381
+ columns = results.map do |ci|
382
382
  ci = ci.symbolize_keys
383
383
  ci[:_type] = ci[:type]
384
384
  ci[:table_name] = view_tblnm || table_name
@@ -437,6 +437,13 @@ module ActiveRecord
437
437
  ci[:is_identity] = ci[:is_identity].to_i == 1 unless [TrueClass, FalseClass].include?(ci[:is_identity].class)
438
438
  ci
439
439
  end
440
+
441
+ # Since Rails 7, it's expected that all adapter raise error when table doesn't exists.
442
+ # I'm not aware of the possibility of tables without columns on SQL Server (postgres have those).
443
+ # Raise error if the method return an empty array
444
+ columns.tap do |result|
445
+ raise ActiveRecord::StatementInvalid, "Table '#{table_name}' doesn't exist" if result.empty?
446
+ end
440
447
  end
441
448
 
442
449
  def column_definitions_sql(database, identifier)
@@ -507,6 +514,13 @@ module ActiveRecord
507
514
  }.gsub(/[ \t\r\n]+/, " ").strip
508
515
  end
509
516
 
517
+ def remove_columns_for_alter(table_name, *column_names, **options)
518
+ first, *rest = column_names
519
+
520
+ # return an array like this [DROP COLUMN col_1, col_2, col_3]. Abstract adapter joins fragments with ", "
521
+ [remove_column_for_alter(table_name, first)] + rest.map { |column_name| quote_column_name(column_name) }
522
+ end
523
+
510
524
  def remove_check_constraints(table_name, column_name)
511
525
  constraints = select_values "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{quote_string(table_name)}' and COLUMN_NAME = '#{quote_string(column_name)}'", "SCHEMA"
512
526
  constraints.each do |constraint|
@@ -531,17 +545,22 @@ module ActiveRecord
531
545
 
532
546
  # === SQLServer Specific (Misc Helpers) ========================= #
533
547
 
548
+ # Parses just the table name from the SQL. Table name does not include database/schema/etc.
534
549
  def get_table_name(sql)
535
- tn = if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
536
- Regexp.last_match[3] || Regexp.last_match[4]
537
- elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
538
- Regexp.last_match[1]
539
- else
540
- nil
541
- end
550
+ tn = get_raw_table_name(sql)
542
551
  SQLServer::Utils.extract_identifiers(tn).object
543
552
  end
544
553
 
554
+ # Parses the raw table name that is used in the SQL. Table name could include database/schema/etc.
555
+ def get_raw_table_name(sql)
556
+ case sql
557
+ when /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
558
+ Regexp.last_match[3] || Regexp.last_match[4]
559
+ when /FROM\s+([^\(\s]+)\s*/i
560
+ Regexp.last_match[1]
561
+ end
562
+ end
563
+
545
564
  def default_constraint_name(table_name, column_name)
546
565
  "DF_#{table_name}_#{column_name}"
547
566
  end
@@ -7,6 +7,8 @@ module ActiveRecord
7
7
  class Data
8
8
  attr_reader :value, :type
9
9
 
10
+ delegate :sub, to: :value
11
+
10
12
  def initialize(value, type)
11
13
  @value, @type = value, type
12
14
  end
@@ -25,7 +27,7 @@ module ActiveRecord
25
27
  end
26
28
 
27
29
  def eql?(other)
28
- self.class == other.class && self.value == other.value
30
+ self.class == other.class && value == other.value
29
31
  end
30
32
  alias :== :eql?
31
33
  end
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  value = super
14
14
  return value unless value.acts_like?(:date)
15
15
 
16
- date = super(value).to_s(:_sqlserver_dateformat)
16
+ date = super(value).to_formatted_s(:_sqlserver_dateformat)
17
17
  Data.new date, self
18
18
  end
19
19
 
@@ -15,7 +15,7 @@ module ActiveRecord
15
15
  value = super
16
16
  return value unless value.acts_like?(:time)
17
17
 
18
- datetime = "#{value.to_s(:_sqlserver_datetime)}.#{quote_fractional(value)}"
18
+ datetime = "#{value.to_formatted_s(:_sqlserver_datetime)}.#{quote_fractional(value)}"
19
19
 
20
20
  Data.new datetime, self
21
21
  end
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
  value = super
12
12
  return value unless value.acts_like?(:time)
13
13
 
14
- time = "#{value.to_s(:_sqlserver_time)}.#{quote_fractional(value)}"
14
+ time = "#{value.to_formatted_s(:_sqlserver_time)}.#{quote_fractional(value)}"
15
15
 
16
16
  Data.new time, self
17
17
  end
@@ -46,7 +46,22 @@ module ActiveRecord
46
46
  end
47
47
 
48
48
  def fully_qualified?
49
- parts.compact.size == 4
49
+ qualified_level == :fully
50
+ end
51
+
52
+ def qualified_level
53
+ case parts.compact.size
54
+ when 4
55
+ :fully
56
+ when 3
57
+ :database
58
+ when 2
59
+ :schema
60
+ when 1
61
+ :table
62
+ else
63
+ :none
64
+ end
50
65
  end
51
66
 
52
67
  def to_s