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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +4 -1
- data/CHANGELOG.md +12 -23
- data/Gemfile +1 -0
- data/MIT-LICENSE +1 -1
- data/README.md +31 -16
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +2 -2
- data/appveyor.yml +4 -6
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +7 -13
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +15 -6
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +4 -5
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +28 -9
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +14 -5
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +3 -2
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +16 -1
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +99 -76
- data/lib/active_record/connection_adapters/sqlserver_column.rb +74 -35
- data/lib/arel/visitors/sqlserver.rb +17 -2
- data/test/cases/adapter_test_sqlserver.rb +10 -2
- data/test/cases/coerced_tests.rb +314 -85
- data/test/cases/column_test_sqlserver.rb +62 -58
- data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
- data/test/cases/fetch_test_sqlserver.rb +18 -0
- data/test/cases/rake_test_sqlserver.rb +36 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +2 -2
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +1 -1
- data/test/models/sqlserver/composite_pk.rb +9 -0
- data/test/schema/sqlserver_specific_schema.rb +18 -0
- data/test/support/coerceable_test_sqlserver.rb +4 -4
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
- data/test/support/rake_helpers.rb +3 -1
- metadata +18 -15
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +0 -28
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9983757834cfcc185a0a292ec422cc8e090a5cdff995be814738bb82b0620373
|
4
|
+
data.tar.gz: 527f419f372081cd743fcd0898c16183136ac36eaf36dd14a521bb455a5147cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e64604834556a9bf9bd9be39e3aff2cc267729c6d32f28c697d293a6b9be5f5a44c924bbe6fae48f528b88a6c24c2c2d6337a5658aa295284764cb9a66a46c4
|
7
|
+
data.tar.gz: c09d354e4af8a5dbb9eb4ef4ab3fdf3b3a527f3ed779895995aac6d60c6335e0698d57cae2509b43e5e3e2825a724bcc41ed2424822b6f067ae6fddfd26ea9bb
|
data/.github/workflows/ci.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,29 +1,18 @@
|
|
1
|
-
##
|
1
|
+
## v7.0.0.0.rc1
|
2
2
|
|
3
|
-
-
|
3
|
+
[Full changelog](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/compare/6-1-stable...v7.0.0.0.rc1)
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
#### Fixed
|
5
|
+
#### Changed
|
8
6
|
|
9
|
-
- [#
|
10
|
-
- [#
|
11
|
-
- [#
|
12
|
-
- [#
|
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
|
-
####
|
12
|
+
#### Added
|
26
13
|
|
27
|
-
- [#
|
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-
|
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
data/MIT-LICENSE
CHANGED
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
|
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
|
-
| `
|
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[
|
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
|
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.
|
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-
|
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
|
-
|
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.
|
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", "~>
|
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.
|
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
|
10
|
-
|
9
|
+
module LoaderQuery
|
10
|
+
def load_records_for_keys(keys, &block)
|
11
|
+
return super unless scope.connection.adapter_name == "SQLServer"
|
11
12
|
|
12
|
-
|
13
|
-
|
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::
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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 =
|
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 :
|
11
|
+
attr_reader :is_identity, :is_primary, :table_name, :ordinal_position
|
12
12
|
|
13
|
-
def initialize(type_metadata,
|
13
|
+
def initialize(type_metadata, is_identity: nil, is_primary: nil, table_name: nil, ordinal_position: nil)
|
14
14
|
super(type_metadata)
|
15
|
-
@
|
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
|
-
|
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
|
-
|
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 &&
|
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
|
-
|
13
|
+
value = super
|
14
|
+
return value unless value.acts_like?(:date)
|
14
15
|
|
15
|
-
date = super(value).
|
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.
|
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.
|
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
|
-
|
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
|