activerecord-sqlserver-adapter 6.1.0.0 → 7.0.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +4 -1
  3. data/CHANGELOG.md +12 -23
  4. data/Gemfile +1 -0
  5. data/MIT-LICENSE +1 -1
  6. data/README.md +31 -16
  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/attribute_methods.rb +2 -0
  11. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -1
  12. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +2 -0
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +2 -0
  14. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +7 -13
  15. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +15 -6
  16. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +4 -5
  17. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +28 -9
  18. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +14 -5
  19. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +3 -1
  20. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +3 -2
  21. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +1 -1
  22. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -1
  23. data/lib/active_record/connection_adapters/sqlserver/utils.rb +16 -1
  24. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +99 -76
  25. data/lib/active_record/connection_adapters/sqlserver_column.rb +74 -35
  26. data/lib/arel/visitors/sqlserver.rb +17 -2
  27. data/test/cases/adapter_test_sqlserver.rb +10 -2
  28. data/test/cases/coerced_tests.rb +314 -85
  29. data/test/cases/column_test_sqlserver.rb +62 -58
  30. data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
  31. data/test/cases/fetch_test_sqlserver.rb +18 -0
  32. data/test/cases/rake_test_sqlserver.rb +36 -0
  33. data/test/cases/schema_dumper_test_sqlserver.rb +2 -2
  34. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +1 -1
  35. data/test/models/sqlserver/composite_pk.rb +9 -0
  36. data/test/schema/sqlserver_specific_schema.rb +18 -0
  37. data/test/support/coerceable_test_sqlserver.rb +4 -4
  38. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
  39. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  40. data/test/support/rake_helpers.rb +3 -1
  41. metadata +18 -15
  42. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +0 -28
  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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb745869999fcde64e516e7a09aae4c0405396bb36f8fe3c57f8b01ff4b2e5b8
4
- data.tar.gz: ad90fcd48bd9141986cdb5b9e5ea364c08c7a045545ef88e24030d1bed1b6e54
3
+ metadata.gz: 9983757834cfcc185a0a292ec422cc8e090a5cdff995be814738bb82b0620373
4
+ data.tar.gz: 527f419f372081cd743fcd0898c16183136ac36eaf36dd14a521bb455a5147cc
5
5
  SHA512:
6
- metadata.gz: 3f1becc13785706f0074d5ca4339a21531c3b4bcffe4582f8858dace5d75100a20e60518963effcd6edd933a7d3c434aeb4c16d62c5b518aa7e29e4d7bf79ddf
7
- data.tar.gz: 02b54c72e7abf889b1f51e77b86e7a70d47657991a21cdb27cdd42334eb06040a15f8c5a936bae70c399baba5d699c766f4b46030e27844f569fe98d12e11080
6
+ metadata.gz: 4e64604834556a9bf9bd9be39e3aff2cc267729c6d32f28c697d293a6b9be5f5a44c924bbe6fae48f528b88a6c24c2c2d6337a5658aa295284764cb9a66a46c4
7
+ data.tar.gz: c09d354e4af8a5dbb9eb4ef4ab3fdf3b3a527f3ed779895995aac6d60c6335e0698d57cae2509b43e5e3e2825a724bcc41ed2424822b6f067ae6fddfd26ea9bb
@@ -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,29 +1,18 @@
1
- ## v6.1.0.0
1
+ ## v7.0.0.0.rc1
2
2
 
3
- - No changes
3
+ [Full changelog](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/compare/6-1-stable...v7.0.0.0.rc1)
4
4
 
5
- ## v6.1.0.0.rc1
6
-
7
- #### Fixed
5
+ #### Changed
8
6
 
9
- - [#872](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/872) Use native String#start_with
10
- - [#876](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/876) Use native String#end_with
11
- - [#873](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/873) Various fixes to get the tests running for Rails 6.1
12
- - [#874](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/874) Deduplicate schema cache structures
13
- - [#875](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/875) Handle default boolean column values when deduplicating
14
- - [#879](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/879) Added visit method for HomogeneousIn
15
- - [#880](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/880) Handle any default column class when deduplicating
16
- - [#861](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/861) Fix Rails 6.1 database config
17
- - [#890](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/890) Fix removal of invalid ordering from select statements
18
- - [#881](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/881) Dump column collation to schema.rb and allow collation changes using column_change
19
- - [#891](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/891) Add support for if_not_exists to indexes
20
- - [#892](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/892) Add support for if_exists on remove_column
21
- - [#883](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/885) Fix quoting of ActiveRecord::Relation::QueryAttribute and ActiveModel::Attributes
22
- - [#893](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/893) Add Active Record Marshal forward compatibility tests
23
- - [#903](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/903) Raise ActiveRecord::ConnectionNotEstablished on calls to execute with a disconnected connection
7
+ - [#968](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/968) Define adapter type maps statically
8
+ - [#983](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/983) Optimize remove_columns to use a single SQL statement
9
+ - [#984](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/984) Better handle SQL queries with invalid encoding
10
+ - [#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***)
24
11
 
25
- #### Changed
12
+ #### Added
26
13
 
27
- - [#917](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/917) Refactored to use new_client connection pattern
14
+ - [#972](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/972) Support `ActiveRecord::QueryLogs`
15
+ - [#981](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/981) Support `find_by` an encrypted attribute
16
+ - [#985](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/985) Support string returning clause for `ActiveRecord#insert_all`
28
17
 
29
- Please check [6-0-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/6-0-stable/CHANGELOG.md) for previous changes.
18
+ 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,11 +9,12 @@
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.0.0` | `6.1.x` | [active](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/main) |
16
+ | `7.0.0.0.rc1` | `7.0.x` | [unreleased](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
19
  | `5.2.1` | `5.2.x` | [active](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) |
@@ -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
 
@@ -82,34 +83,40 @@ end
82
83
  ```
83
84
 
84
85
 
85
- #### Configure Connection & App Name
86
+ #### Configure Connection
86
87
 
87
- We currently conform to an unpublished and non-standard AbstractAdapter interface to configure connections made to the database. To do so, just override the `configure_connection` method in an initializer like so. In this case below we are setting the `TEXTSIZE` to 64 megabytes. Also, TinyTDS supports an application name when it logs into SQL Server. This can be used to identify the connection in SQL Server's activity monitor. By default it will use the `appname` from your database.yml file or a lowercased version of your Rails::Application name. It is now possible to define a `configure_application_name` method that can give you per instance details. Below shows how you might use this to get the process id and thread id of the current connection.
88
+ We currently conform to an unpublished and non-standard AbstractAdapter interface to configure connections made to the database. To do so, just override the `configure_connection` method in an initializer like so. In this case below we are setting the `TEXTSIZE` to 64 megabytes.
88
89
 
89
90
  ```ruby
90
91
  module ActiveRecord
91
92
  module ConnectionAdapters
92
93
  class SQLServerAdapter < AbstractAdapter
93
-
94
94
  def configure_connection
95
95
  raw_connection_do "SET TEXTSIZE #{64.megabytes}"
96
96
  end
97
-
98
- def configure_application_name
99
- "myapp_#{$$}_#{Thread.current.object_id}".to(29)
100
- end
101
-
102
97
  end
103
98
  end
104
99
  end
105
100
  ```
106
101
 
102
+ #### Configure Application Name
103
+
104
+ TinyTDS supports an application name when it logs into SQL Server. This can be used to identify the connection in SQL Server's activity monitor. By default it will use the `appname` from your database.yml file or your Rails::Application name.
105
+
106
+ Below shows how you might use the database.yml file to use the process ID in your application name.
107
+
108
+ ```yaml
109
+ development:
110
+ adapter: sqlserver
111
+ appname: <%= "myapp_#{Process.pid}" %>
112
+ ```
113
+
107
114
  #### Executing Stored Procedures
108
115
 
109
116
  Every class that sub classes ActiveRecord::Base will now have an execute_procedure class method to use. This method takes the name of the stored procedure which can be a string or symbol and any number of variables to pass to the procedure. Arguments will automatically be quoted per the connection's standards as normal. For example:
110
117
 
111
118
  ```ruby
112
- Account.execute_procedure(:update_totals, 'admin', nil, true
119
+ Account.execute_procedure(:update_totals, 'admin', nil, true)
113
120
  # Or with named parameters.
114
121
  Account.execute_procedure(:update_totals, named: 'params')
115
122
  ```
@@ -146,16 +153,25 @@ ActiveRecord::ConnectionAdapters::SQLServerAdapter.showplan_option = 'SHOWPLAN_X
146
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.
147
154
 
148
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
+
149
167
  ## Installation
150
168
 
151
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.
152
170
 
153
171
  ```ruby
154
- gem 'tiny_tds'
155
172
  gem 'activerecord-sqlserver-adapter'
156
173
  ```
157
174
 
158
-
159
175
  ## Contributing
160
176
 
161
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.
@@ -172,5 +188,4 @@ You can see an up-to-date list of contributors here: http://github.com/rails-sql
172
188
 
173
189
  ## License
174
190
 
175
- Copyright © 2008-2020. It is free software, and may be redistributed under the terms specified in the [MIT-LICENSE](MIT-LICENSE) file.
176
-
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.0.0
1
+ 7.0.0.0.rc1
@@ -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"
@@ -10,6 +10,8 @@ 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
+
13
15
  super.reject do |name|
14
16
  column = self.class.columns_hash[name]
15
17
  column && column.respond_to?(:is_identity?) && column.is_identity?
@@ -10,6 +10,8 @@ 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
+
13
15
  if has_include?(column_name)
14
16
  relation = apply_join_dependency
15
17
 
@@ -29,6 +31,8 @@ module ActiveRecord
29
31
  private
30
32
 
31
33
  def build_count_subquery(relation, column_name, distinct)
34
+ return super unless klass.connection.adapter_name == "SQLServer"
35
+
32
36
  super(relation.unscope(:order), column_name, distinct)
33
37
  end
34
38
  end
@@ -39,5 +43,5 @@ end
39
43
 
40
44
  ActiveSupport.on_load(:active_record) do
41
45
  mod = ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::Calculations
42
- ActiveRecord::Relation.prepend(mod)
46
+ ActiveRecord::Relation.include(mod)
43
47
  end
@@ -9,6 +9,8 @@ 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
+
12
14
  unprepared_queries = queries.map do |(sql, binds)|
13
15
  [unprepare_sqlserver_statement(sql, binds), binds]
14
16
  end
@@ -12,6 +12,8 @@ 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
+
15
17
  conditions = sanitize_forbidden_attributes(conditions)
16
18
 
17
19
  if distinct_value && offset_value
@@ -6,18 +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
- ids.each_slice(in_clause_length).flat_map do |slice|
14
- scope.where(association_key_name => slice).load do |record|
15
- # Processing only the first owner
16
- # because the record is modified but not an owner
17
- owner = owners_by_key[convert_key(record[association_key_name])].first
18
- association = owner.association(reflection.name)
19
- association.set_inverse_instance(record)
20
- end.records
13
+ keys.each_slice(in_clause_length).flat_map do |slice|
14
+ scope.where(association_key_name => slice).load(&block).records
21
15
  end
22
16
  end
23
17
 
@@ -31,6 +25,6 @@ module ActiveRecord
31
25
  end
32
26
 
33
27
  ActiveSupport.on_load(:active_record) do
34
- mod = ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::Preloader
35
- ActiveRecord::Associations::Preloader::Association.prepend(mod)
28
+ mod = ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::LoaderQuery
29
+ ActiveRecord::Associations::Preloader::Association::LoaderQuery.prepend(mod)
36
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)
@@ -17,7 +17,8 @@ module ActiveRecord
17
17
  precision: cast_type.precision,
18
18
  scale: cast_type.scale
19
19
  )
20
- SQLServer::TypeMetadata.new(simple_type, sqlserver_options: sqlserver_options)
20
+
21
+ SQLServer::TypeMetadata.new(simple_type, **sqlserver_options)
21
22
  end
22
23
 
23
24
  def quote_string(s)
@@ -108,9 +109,7 @@ module ActiveRecord
108
109
 
109
110
  private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
110
111
 
111
- private
112
-
113
- def _quote(value)
112
+ def quote(value)
114
113
  case value
115
114
  when Type::Binary::Data
116
115
  "0x#{value.hex}"
@@ -123,7 +122,7 @@ module ActiveRecord
123
122
  end
124
123
  end
125
124
 
126
- def _type_cast(value)
125
+ def type_cast(value)
127
126
  case value
128
127
  when ActiveRecord::Type::SQLServer::Data
129
128
  value.to_s
@@ -84,7 +84,7 @@ module ActiveRecord
84
84
  end
85
85
 
86
86
  def new_column(name, default, sql_type_metadata, null, default_function = nil, collation = nil, comment = nil, sqlserver_options = {})
87
- SQLServerColumn.new(
87
+ SQLServer::Column.new(
88
88
  name,
89
89
  default,
90
90
  sql_type_metadata,
@@ -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
@@ -8,24 +8,33 @@ module ActiveRecord
8
8
 
9
9
  include Deduplicable
10
10
 
11
- attr_reader :sqlserver_options
11
+ attr_reader :is_identity, :is_primary, :table_name, :ordinal_position
12
12
 
13
- def initialize(type_metadata, sqlserver_options: nil)
13
+ def initialize(type_metadata, is_identity: nil, is_primary: nil, table_name: nil, ordinal_position: nil)
14
14
  super(type_metadata)
15
- @sqlserver_options = sqlserver_options
15
+ @is_identity = is_identity
16
+ @is_primary = is_primary
17
+ @table_name = table_name
18
+ @ordinal_position = ordinal_position
16
19
  end
17
20
 
18
21
  def ==(other)
19
22
  other.is_a?(TypeMetadata) &&
20
23
  __getobj__ == other.__getobj__ &&
21
- sqlserver_options == other.sqlserver_options
24
+ is_identity == other.is_identity &&
25
+ is_primary == other.is_primary &&
26
+ table_name == other.table_name &&
27
+ ordinal_position == other.ordinal_position
22
28
  end
23
29
  alias eql? ==
24
30
 
25
31
  def hash
26
32
  TypeMetadata.hash ^
27
33
  __getobj__.hash ^
28
- sqlserver_options.hash
34
+ is_identity.hash ^
35
+ is_primary.hash ^
36
+ table_name.hash ^
37
+ ordinal_position.hash
29
38
  end
30
39
 
31
40
  private
@@ -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
@@ -10,9 +10,10 @@ module ActiveRecord
10
10
  end
11
11
 
12
12
  def serialize(value)
13
- return unless value.present?
13
+ value = super
14
+ return value unless value.acts_like?(:date)
14
15
 
15
- date = super(value).to_s(:_sqlserver_dateformat)
16
+ date = super(value).to_formatted_s(:_sqlserver_dateformat)
16
17
  Data.new date, self
17
18
  end
18
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