activerecord-sqlserver-adapter 6.0.1 → 6.0.3
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 +28 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +2 -1
- data/README.md +19 -1
- data/RUNNING_UNIT_TESTS.md +1 -1
- data/VERSION +1 -1
- data/appveyor.yml +5 -7
- 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 +4 -0
- 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 +2 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +23 -3
- data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
- data/lib/active_record/connection_adapters/sqlserver/type.rb +1 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +17 -2
- data/lib/arel/visitors/sqlserver.rb +52 -12
- data/test/cases/adapter_test_sqlserver.rb +34 -2
- data/test/cases/coerced_tests.rb +179 -34
- data/test/cases/column_test_sqlserver.rb +5 -2
- data/test/cases/lateral_test_sqlserver.rb +35 -0
- data/test/cases/migration_test_sqlserver.rb +44 -0
- data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +5 -3
- metadata +11 -6
- data/.travis.yml +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a4ddc7c26331060001ac99b3e2ea5c7d30a97204237c48c383b356280a763ac
|
4
|
+
data.tar.gz: c9e1154f442e957e1930fa7cc36addf9b8f465d579d44fcb87fb6a4ffe0426bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc3e2cbdcd6ef7d2f334a4af20a66e9c1f1a4ec28548dc664e879a94e87aa487a0d68c6468182b3a026f3e05aa0c735ce16c774b8b404cb8f7e140188d8c06f0
|
7
|
+
data.tar.gz: 5f759150e7c204c5de007a320897b71bfe80913ee78d162dcfa891ab840102089f3798a7641ccaa2fe6ef6a56250deed62877f2b7900983f705a21677985c291
|
@@ -0,0 +1,28 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
test:
|
7
|
+
name: Run test suite
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
|
10
|
+
env:
|
11
|
+
COMPOSE_FILE: docker-compose.ci.yml
|
12
|
+
|
13
|
+
strategy:
|
14
|
+
fail-fast: false
|
15
|
+
matrix:
|
16
|
+
ruby:
|
17
|
+
- 2.7.5
|
18
|
+
- 3.0.3
|
19
|
+
|
20
|
+
steps:
|
21
|
+
- name: Checkout code
|
22
|
+
uses: actions/checkout@v2
|
23
|
+
|
24
|
+
- name: Build docker images
|
25
|
+
run: docker-compose build --build-arg TARGET_VERSION=${{ matrix.ruby }}
|
26
|
+
|
27
|
+
- name: Run tests
|
28
|
+
run: docker-compose run ci
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
## v6.0.3
|
2
|
+
|
3
|
+
#### Fixed
|
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
|
+
|
1
25
|
## v6.0.1
|
2
26
|
|
3
27
|
#### Fixed
|
data/Gemfile
CHANGED
@@ -7,9 +7,10 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
|
7
7
|
gemspec
|
8
8
|
|
9
9
|
gem "bcrypt"
|
10
|
-
gem "pg",
|
10
|
+
gem "pg", "~> 1.3"
|
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"
|
13
14
|
|
14
15
|
if ENV["RAILS_SOURCE"]
|
15
16
|
gemspec path: ENV["RAILS_SOURCE"]
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# ActiveRecord SQL Server Adapter. For SQL Server 2012 And Higher.
|
2
2
|
|
3
|
-
* [![
|
3
|
+
* [![CI](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/actions/workflows/ci.yml/badge.svg)](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/actions/workflows/ci.yml) - CI
|
4
4
|
* [![Build Status](https://ci.appveyor.com/api/projects/status/mtgbx8f57vr7k2qa/branch/master?svg=true)](https://ci.appveyor.com/project/rails-sqlserver/activerecord-sqlserver-adapter/branch/master) - Appveyor
|
5
5
|
* [![Gem Version](http://img.shields.io/gem/v/activerecord-sqlserver-adapter.svg)](https://rubygems.org/gems/activerecord-sqlserver-adapter) - Gem Version
|
6
6
|
* [![Gitter chat](https://img.shields.io/badge/%E2%8A%AA%20GITTER%20-JOIN%20CHAT%20%E2%86%92-brightgreen.svg?style=flat)](https://gitter.im/rails-sqlserver/activerecord-sqlserver-adapter) - Community
|
@@ -52,6 +52,24 @@ Depending on your user and schema setup, it may be needed to use a table name pr
|
|
52
52
|
ActiveRecord::Base.table_name_prefix = 'dbo.'
|
53
53
|
```
|
54
54
|
|
55
|
+
It's also possible to create/change/drop a schema in the migration file as in the example below:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
class CreateFooSchema < ActiveRecord::Migration[6.0]
|
59
|
+
def up
|
60
|
+
create_schema('foo')
|
61
|
+
|
62
|
+
# Or you could move a table to a different schema
|
63
|
+
|
64
|
+
change_table_schema('foo', 'dbo.admin')
|
65
|
+
end
|
66
|
+
|
67
|
+
def down
|
68
|
+
drop_schema('foo')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
55
73
|
|
56
74
|
#### Configure Connection & App Name
|
57
75
|
|
data/RUNNING_UNIT_TESTS.md
CHANGED
@@ -5,7 +5,7 @@ This process is much easier than it has been before!
|
|
5
5
|
|
6
6
|
## MS SQL SERVER
|
7
7
|
|
8
|
-
If you don't have easy access to MS SQL Server, you can set up a Vagrant/VirtualBox virtual machine with MS SQL Server. [Here's how](
|
8
|
+
If you don't have easy access to MS SQL Server, you can set up a Vagrant/VirtualBox virtual machine with MS SQL Server. [Here's how](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter-dev-box).
|
9
9
|
|
10
10
|
## TL;DR
|
11
11
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
6.0.
|
1
|
+
6.0.3
|
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
|
-
- ruby_version: "27"
|
11
8
|
- ruby_version: "27-x64"
|
9
|
+
- ruby_version: "27"
|
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"
|
44
|
+
- ruby_version: "30-x64"
|
@@ -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
|
|
@@ -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
|
@@ -10,6 +10,8 @@ module ActiveRecord
|
|
10
10
|
private
|
11
11
|
|
12
12
|
def records_for(ids)
|
13
|
+
return super unless klass.connection.adapter_name == "SQLServer"
|
14
|
+
|
13
15
|
ids.each_slice(in_clause_length).flat_map do |slice|
|
14
16
|
scope.where(association_key_name => slice).load do |record|
|
15
17
|
# Processing only the first owner
|
@@ -4,7 +4,7 @@ module ActiveRecord
|
|
4
4
|
module ConnectionAdapters
|
5
5
|
module SQLServer
|
6
6
|
module DatabaseStatements
|
7
|
-
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :dbcc, :explain, :save, :select, :set, :rollback) # :nodoc:
|
7
|
+
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :dbcc, :explain, :save, :select, :set, :rollback, :waitfor) # :nodoc:
|
8
8
|
private_constant :READ_QUERY
|
9
9
|
|
10
10
|
def write_query?(sql) # :nodoc:
|
@@ -83,8 +83,8 @@ module ActiveRecord
|
|
83
83
|
\A
|
84
84
|
(
|
85
85
|
(?:
|
86
|
-
# [table_name].[column_name] | function(one or no argument)
|
87
|
-
((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
|
86
|
+
# [database_name].[database_owner].[table_name].[column_name] | function(one or no argument)
|
87
|
+
((?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
|
88
88
|
)
|
89
89
|
(?:\s+AS\s+(?:\w+|\[\w+\]))?
|
90
90
|
)
|
@@ -96,8 +96,8 @@ module ActiveRecord
|
|
96
96
|
\A
|
97
97
|
(
|
98
98
|
(?:
|
99
|
-
# [table_name].[column_name] | function(one or no argument)
|
100
|
-
((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
|
99
|
+
# [database_name].[database_owner].[table_name].[column_name] | function(one or no argument)
|
100
|
+
((?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
|
101
101
|
)
|
102
102
|
(?:\s+ASC|\s+DESC)?
|
103
103
|
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
@@ -281,14 +281,33 @@ module ActiveRecord
|
|
281
281
|
SQLServer::SchemaDumper.create(self, options)
|
282
282
|
end
|
283
283
|
|
284
|
+
def create_schema(schema_name, authorization = nil)
|
285
|
+
sql = "CREATE SCHEMA [#{schema_name}]"
|
286
|
+
sql += " AUTHORIZATION [#{authorization}]" if authorization
|
287
|
+
|
288
|
+
execute sql
|
289
|
+
end
|
290
|
+
|
291
|
+
def change_table_schema(schema_name, table_name)
|
292
|
+
execute "ALTER SCHEMA [#{schema_name}] TRANSFER [#{table_name}]"
|
293
|
+
end
|
294
|
+
|
295
|
+
def drop_schema(schema_name)
|
296
|
+
execute "DROP SCHEMA [#{schema_name}]"
|
297
|
+
end
|
298
|
+
|
284
299
|
private
|
285
300
|
|
286
301
|
def data_source_sql(name = nil, type: nil)
|
287
302
|
scope = quoted_scope name, type: type
|
288
|
-
|
303
|
+
|
304
|
+
table_name = lowercase_schema_reflection_sql 'TABLE_NAME'
|
305
|
+
database = scope[:database].present? ? "#{scope[:database]}." : ""
|
306
|
+
table_catalog = scope[:database].present? ? quote(scope[:database]) : "DB_NAME()"
|
307
|
+
|
289
308
|
sql = "SELECT #{table_name}"
|
290
|
-
sql += " FROM INFORMATION_SCHEMA.TABLES WITH (NOLOCK)"
|
291
|
-
sql += " WHERE TABLE_CATALOG =
|
309
|
+
sql += " FROM #{database}INFORMATION_SCHEMA.TABLES WITH (NOLOCK)"
|
310
|
+
sql += " WHERE TABLE_CATALOG = #{table_catalog}"
|
292
311
|
sql += " AND TABLE_SCHEMA = #{quote(scope[:schema])}"
|
293
312
|
sql += " AND TABLE_NAME = #{quote(scope[:name])}" if scope[:name]
|
294
313
|
sql += " AND TABLE_TYPE = #{quote(scope[:type])}" if scope[:type]
|
@@ -299,6 +318,7 @@ module ActiveRecord
|
|
299
318
|
def quoted_scope(name = nil, type: nil)
|
300
319
|
identifier = SQLServer::Utils.extract_identifiers(name)
|
301
320
|
{}.tap do |scope|
|
321
|
+
scope[:database] = identifier.database if identifier.database
|
302
322
|
scope[:schema] = identifier.schema || "dbo"
|
303
323
|
scope[:name] = identifier.object if identifier.object
|
304
324
|
scope[:type] = type if type
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module SQLServer
|
6
|
+
module Type
|
7
|
+
class DecimalWithoutScale < ActiveRecord::Type::DecimalWithoutScale
|
8
|
+
def sqlserver_type
|
9
|
+
"decimal".yield_self do |type|
|
10
|
+
type += "(#{precision.to_i},0)" if precision
|
11
|
+
type
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def type_cast_for_schema(value)
|
16
|
+
value.is_a?(BigDecimal) ? value.to_s : value.inspect
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -11,6 +11,7 @@ require "active_record/connection_adapters/sqlserver/type/small_integer"
|
|
11
11
|
require "active_record/connection_adapters/sqlserver/type/tiny_integer"
|
12
12
|
require "active_record/connection_adapters/sqlserver/type/boolean"
|
13
13
|
require "active_record/connection_adapters/sqlserver/type/decimal"
|
14
|
+
require "active_record/connection_adapters/sqlserver/type/decimal_without_scale"
|
14
15
|
require "active_record/connection_adapters/sqlserver/type/money"
|
15
16
|
require "active_record/connection_adapters/sqlserver/type/small_money"
|
16
17
|
# Approximate Numerics
|
@@ -152,6 +152,10 @@ module ActiveRecord
|
|
152
152
|
true
|
153
153
|
end
|
154
154
|
|
155
|
+
def supports_optimizer_hints?
|
156
|
+
true
|
157
|
+
end
|
158
|
+
|
155
159
|
def supports_lazy_transactions?
|
156
160
|
true
|
157
161
|
end
|
@@ -296,6 +300,7 @@ module ActiveRecord
|
|
296
300
|
|
297
301
|
def initialize_type_map(m = type_map)
|
298
302
|
m.register_type %r{.*}, SQLServer::Type::UnicodeString.new
|
303
|
+
|
299
304
|
# Exact Numerics
|
300
305
|
register_class_with_limit m, "bigint(8)", SQLServer::Type::BigInteger
|
301
306
|
m.alias_type "bigint", "bigint(8)"
|
@@ -308,16 +313,22 @@ module ActiveRecord
|
|
308
313
|
m.alias_type "tinyint", "tinyint(1)"
|
309
314
|
m.register_type "bit", SQLServer::Type::Boolean.new
|
310
315
|
m.register_type %r{\Adecimal}i do |sql_type|
|
311
|
-
scale
|
316
|
+
scale = extract_scale(sql_type)
|
312
317
|
precision = extract_precision(sql_type)
|
313
|
-
|
318
|
+
if scale == 0
|
319
|
+
SQLServer::Type::DecimalWithoutScale.new(precision: precision)
|
320
|
+
else
|
321
|
+
SQLServer::Type::Decimal.new(precision: precision, scale: scale)
|
322
|
+
end
|
314
323
|
end
|
315
324
|
m.alias_type %r{\Anumeric}i, "decimal"
|
316
325
|
m.register_type "money", SQLServer::Type::Money.new
|
317
326
|
m.register_type "smallmoney", SQLServer::Type::SmallMoney.new
|
327
|
+
|
318
328
|
# Approximate Numerics
|
319
329
|
m.register_type "float", SQLServer::Type::Float.new
|
320
330
|
m.register_type "real", SQLServer::Type::Real.new
|
331
|
+
|
321
332
|
# Date and Time
|
322
333
|
m.register_type "date", SQLServer::Type::Date.new
|
323
334
|
m.register_type %r{\Adatetime} do |sql_type|
|
@@ -337,11 +348,13 @@ module ActiveRecord
|
|
337
348
|
precision = extract_precision(sql_type) || DEFAULT_TIME_PRECISION
|
338
349
|
SQLServer::Type::Time.new precision: precision
|
339
350
|
end
|
351
|
+
|
340
352
|
# Character Strings
|
341
353
|
register_class_with_limit m, %r{\Achar}i, SQLServer::Type::Char
|
342
354
|
register_class_with_limit m, %r{\Avarchar}i, SQLServer::Type::Varchar
|
343
355
|
m.register_type "varchar(max)", SQLServer::Type::VarcharMax.new
|
344
356
|
m.register_type "text", SQLServer::Type::Text.new
|
357
|
+
|
345
358
|
# Unicode Character Strings
|
346
359
|
register_class_with_limit m, %r{\Anchar}i, SQLServer::Type::UnicodeChar
|
347
360
|
register_class_with_limit m, %r{\Anvarchar}i, SQLServer::Type::UnicodeVarchar
|
@@ -349,10 +362,12 @@ module ActiveRecord
|
|
349
362
|
m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
|
350
363
|
m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
|
351
364
|
m.register_type "ntext", SQLServer::Type::UnicodeText.new
|
365
|
+
|
352
366
|
# Binary Strings
|
353
367
|
register_class_with_limit m, %r{\Abinary}i, SQLServer::Type::Binary
|
354
368
|
register_class_with_limit m, %r{\Avarbinary}i, SQLServer::Type::Varbinary
|
355
369
|
m.register_type "varbinary(max)", SQLServer::Type::VarbinaryMax.new
|
370
|
+
|
356
371
|
# Other Data Types
|
357
372
|
m.register_type "uniqueidentifier", SQLServer::Type::Uuid.new
|
358
373
|
m.register_type "timestamp", SQLServer::Type::Timestamp.new
|
@@ -80,6 +80,16 @@ module Arel
|
|
80
80
|
@select_statement = nil
|
81
81
|
end
|
82
82
|
|
83
|
+
def visit_Arel_Nodes_SelectCore(o, collector)
|
84
|
+
collector = super
|
85
|
+
maybe_visit o.optimizer_hints, collector
|
86
|
+
end
|
87
|
+
|
88
|
+
def visit_Arel_Nodes_OptimizerHints(o, collector)
|
89
|
+
hints = o.expr.map { |v| sanitize_as_option_clause(v) }.join(", ")
|
90
|
+
collector << "OPTION (#{hints})"
|
91
|
+
end
|
92
|
+
|
83
93
|
def visit_Arel_Table o, collector
|
84
94
|
# Apparently, o.engine.connection can actually be a different adapter
|
85
95
|
# than sqlserver. Can be removed if fixed in ActiveRecord. See:
|
@@ -115,23 +125,33 @@ module Arel
|
|
115
125
|
end
|
116
126
|
|
117
127
|
def visit_Arel_Nodes_InnerJoin o, collector
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
if o.right
|
122
|
-
collector << " "
|
123
|
-
visit(o.right, collector)
|
128
|
+
if o.left.is_a?(Arel::Nodes::As) && o.left.left.is_a?(Arel::Nodes::Lateral)
|
129
|
+
collector << "CROSS "
|
130
|
+
visit o.left, collector
|
124
131
|
else
|
125
|
-
collector
|
132
|
+
collector << "INNER JOIN "
|
133
|
+
collector = visit o.left, collector
|
134
|
+
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
|
135
|
+
if o.right
|
136
|
+
collector << " "
|
137
|
+
visit(o.right, collector)
|
138
|
+
else
|
139
|
+
collector
|
140
|
+
end
|
126
141
|
end
|
127
142
|
end
|
128
143
|
|
129
144
|
def visit_Arel_Nodes_OuterJoin o, collector
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
145
|
+
if o.left.is_a?(Arel::Nodes::As) && o.left.left.is_a?(Arel::Nodes::Lateral)
|
146
|
+
collector << "OUTER "
|
147
|
+
visit o.left, collector
|
148
|
+
else
|
149
|
+
collector << "LEFT OUTER JOIN "
|
150
|
+
collector = visit o.left, collector
|
151
|
+
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
|
152
|
+
collector << " "
|
153
|
+
visit o.right, collector
|
154
|
+
end
|
135
155
|
end
|
136
156
|
|
137
157
|
def collect_in_clause(left, right, collector)
|
@@ -144,6 +164,10 @@ module Arel
|
|
144
164
|
super
|
145
165
|
end
|
146
166
|
|
167
|
+
def collect_optimizer_hints(o, collector)
|
168
|
+
collector
|
169
|
+
end
|
170
|
+
|
147
171
|
# SQLServer ToSql/Visitor (Additions)
|
148
172
|
|
149
173
|
def visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, options = {}
|
@@ -174,6 +198,18 @@ module Arel
|
|
174
198
|
collector
|
175
199
|
end
|
176
200
|
|
201
|
+
def visit_Arel_Nodes_Lateral o, collector
|
202
|
+
collector << "APPLY"
|
203
|
+
collector << " "
|
204
|
+
if o.expr.is_a?(Arel::Nodes::SelectStatement)
|
205
|
+
collector << "("
|
206
|
+
visit(o.expr, collector)
|
207
|
+
collector << ")"
|
208
|
+
else
|
209
|
+
visit(o.expr, collector)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
177
213
|
# SQLServer Helpers
|
178
214
|
|
179
215
|
def node_value(node)
|
@@ -247,6 +283,10 @@ module Arel
|
|
247
283
|
|
248
284
|
node.orders = [] unless node.offset || node.limit
|
249
285
|
end
|
286
|
+
|
287
|
+
def sanitize_as_option_clause(value)
|
288
|
+
value.gsub(%r{OPTION \s* \( (.+) \)}xi, "\\1")
|
289
|
+
end
|
250
290
|
end
|
251
291
|
end
|
252
292
|
end
|
@@ -6,6 +6,7 @@ require "models/task"
|
|
6
6
|
require "models/post"
|
7
7
|
require "models/subscriber"
|
8
8
|
require "models/minimalistic"
|
9
|
+
require "models/college"
|
9
10
|
|
10
11
|
class AdapterTestSQLServer < ActiveRecord::TestCase
|
11
12
|
fixtures :tasks
|
@@ -42,17 +43,48 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
42
43
|
assert connection.supports_ddl_transactions?
|
43
44
|
end
|
44
45
|
|
45
|
-
it "
|
46
|
+
it "table exists works if table name prefixed by schema and owner" do
|
46
47
|
begin
|
47
48
|
assert_equal "topics", Topic.table_name
|
48
49
|
assert Topic.table_exists?
|
50
|
+
|
51
|
+
# Test when owner included in table name.
|
49
52
|
Topic.table_name = "dbo.topics"
|
50
|
-
assert Topic.table_exists?, "
|
53
|
+
assert Topic.table_exists?, "Topics table name of 'dbo.topics' should return true for exists."
|
54
|
+
|
55
|
+
# Test when database and owner included in table name.
|
56
|
+
Topic.table_name = "#{ActiveRecord::Base.configurations["arunit"]['database']}.dbo.topics"
|
57
|
+
assert Topic.table_exists?, "Topics table name of '[DATABASE].dbo.topics' should return true for exists."
|
51
58
|
ensure
|
52
59
|
Topic.table_name = "topics"
|
53
60
|
end
|
54
61
|
end
|
55
62
|
|
63
|
+
it "test table existence across database schemas" do
|
64
|
+
arunit_connection = Topic.connection
|
65
|
+
arunit2_connection = College.connection
|
66
|
+
|
67
|
+
arunit_database = arunit_connection.pool.spec.config[:database]
|
68
|
+
arunit2_database = arunit2_connection.pool.spec.config[:database]
|
69
|
+
|
70
|
+
# Assert that connections use different default databases schemas.
|
71
|
+
assert_not_equal arunit_database, arunit2_database
|
72
|
+
|
73
|
+
# Assert that the Topics table exists when using the Topics connection.
|
74
|
+
assert arunit_connection.table_exists?('topics'), 'Topics table exists using table name'
|
75
|
+
assert arunit_connection.table_exists?('dbo.topics'), 'Topics table exists using owner and table name'
|
76
|
+
assert arunit_connection.table_exists?("#{arunit_database}.dbo.topics"), 'Topics table exists using database, owner and table name'
|
77
|
+
|
78
|
+
# Assert that the Colleges table exists when using the Colleges connection.
|
79
|
+
assert arunit2_connection.table_exists?('colleges'), 'College table exists using table name'
|
80
|
+
assert arunit2_connection.table_exists?('dbo.colleges'), 'College table exists using owner and table name'
|
81
|
+
assert arunit2_connection.table_exists?("#{arunit2_database}.dbo.colleges"), 'College table exists using database, owner and table name'
|
82
|
+
|
83
|
+
# Assert that the tables exist when using each others connection.
|
84
|
+
assert arunit_connection.table_exists?("#{arunit2_database}.dbo.colleges"), 'Colleges table exists using Topics connection'
|
85
|
+
assert arunit2_connection.table_exists?("#{arunit_database}.dbo.topics"), 'Topics table exists using Colleges connection'
|
86
|
+
end
|
87
|
+
|
56
88
|
it "return true to insert sql query for inserts only" do
|
57
89
|
assert connection.send(:insert_sql?, "INSERT...")
|
58
90
|
assert connection.send(:insert_sql?, "EXEC sp_executesql N'INSERT INTO [fk_test_has_fks] ([fk_id]) VALUES (@0); SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident', N'@0 int', @0 = 0")
|
data/test/cases/coerced_tests.rb
CHANGED
@@ -396,39 +396,6 @@ module ActiveRecord
|
|
396
396
|
end
|
397
397
|
|
398
398
|
class MigrationTest < ActiveRecord::TestCase
|
399
|
-
# We do not have do the DecimalWithoutScale type.
|
400
|
-
coerce_tests! :test_add_table_with_decimals
|
401
|
-
def test_add_table_with_decimals_coerced
|
402
|
-
Person.connection.drop_table :big_numbers rescue nil
|
403
|
-
assert !BigNumber.table_exists?
|
404
|
-
GiveMeBigNumbers.up
|
405
|
-
BigNumber.reset_column_information
|
406
|
-
assert BigNumber.create(
|
407
|
-
:bank_balance => 1586.43,
|
408
|
-
:big_bank_balance => BigDecimal("1000234000567.95"),
|
409
|
-
:world_population => 6000000000,
|
410
|
-
:my_house_population => 3,
|
411
|
-
:value_of_e => BigDecimal("2.7182818284590452353602875")
|
412
|
-
)
|
413
|
-
b = BigNumber.first
|
414
|
-
assert_not_nil b
|
415
|
-
assert_not_nil b.bank_balance
|
416
|
-
assert_not_nil b.big_bank_balance
|
417
|
-
assert_not_nil b.world_population
|
418
|
-
assert_not_nil b.my_house_population
|
419
|
-
assert_not_nil b.value_of_e
|
420
|
-
assert_kind_of BigDecimal, b.world_population
|
421
|
-
assert_equal "6000000000.0", b.world_population.to_s
|
422
|
-
assert_kind_of Integer, b.my_house_population
|
423
|
-
assert_equal 3, b.my_house_population
|
424
|
-
assert_kind_of BigDecimal, b.bank_balance
|
425
|
-
assert_equal BigDecimal("1586.43"), b.bank_balance
|
426
|
-
assert_kind_of BigDecimal, b.big_bank_balance
|
427
|
-
assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance
|
428
|
-
GiveMeBigNumbers.down
|
429
|
-
assert_raise(ActiveRecord::StatementInvalid) { BigNumber.first }
|
430
|
-
end
|
431
|
-
|
432
399
|
# For some reason our tests set Rails.@_env which breaks test env switching.
|
433
400
|
coerce_tests! :test_internal_metadata_stores_environment_when_other_data_exists
|
434
401
|
coerce_tests! :test_internal_metadata_stores_environment
|
@@ -982,6 +949,21 @@ class RelationTest < ActiveRecord::TestCase
|
|
982
949
|
end
|
983
950
|
end
|
984
951
|
|
952
|
+
module ActiveRecord
|
953
|
+
class RelationTest < ActiveRecord::TestCase
|
954
|
+
# Skipping this test. SQL Server doesn't support optimizer hint as comments
|
955
|
+
coerce_tests! :test_relation_with_optimizer_hints_filters_sql_comment_delimiters
|
956
|
+
|
957
|
+
coerce_tests! :test_does_not_duplicate_optimizer_hints_on_merge
|
958
|
+
def test_does_not_duplicate_optimizer_hints_on_merge_coerced
|
959
|
+
escaped_table = Post.connection.quote_table_name("posts")
|
960
|
+
expected = "SELECT #{escaped_table}.* FROM #{escaped_table} OPTION (OMGHINT)"
|
961
|
+
query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
|
962
|
+
assert_equal expected, query
|
963
|
+
end
|
964
|
+
end
|
965
|
+
end
|
966
|
+
|
985
967
|
require "models/post"
|
986
968
|
class SanitizeTest < ActiveRecord::TestCase
|
987
969
|
# Use nvarchar string (N'') in assert
|
@@ -1106,7 +1088,8 @@ class YamlSerializationTest < ActiveRecord::TestCase
|
|
1106
1088
|
coerce_tests! :test_types_of_virtual_columns_are_not_changed_on_round_trip
|
1107
1089
|
def test_types_of_virtual_columns_are_not_changed_on_round_trip_coerced
|
1108
1090
|
author = Author.select("authors.*, 5 as posts_count").first
|
1109
|
-
|
1091
|
+
dumped_author = YAML.dump(author)
|
1092
|
+
dumped = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(dumped_author) : YAML.load(dumped_author)
|
1110
1093
|
assert_equal 5, author.posts_count
|
1111
1094
|
assert_equal 5, dumped.posts_count
|
1112
1095
|
end
|
@@ -1225,6 +1208,7 @@ module ActiveRecord
|
|
1225
1208
|
|
1226
1209
|
original_test_statement_cache_values_differ
|
1227
1210
|
ensure
|
1211
|
+
Book.where(author_id: nil, name: 'my book').delete_all
|
1228
1212
|
Book.connection.add_index(:books, [:author_id, :name], unique: true)
|
1229
1213
|
end
|
1230
1214
|
end
|
@@ -1243,7 +1227,11 @@ module ActiveRecord
|
|
1243
1227
|
end
|
1244
1228
|
end
|
1245
1229
|
|
1230
|
+
require "models/post"
|
1231
|
+
require "models/comment"
|
1246
1232
|
class UnsafeRawSqlTest < ActiveRecord::TestCase
|
1233
|
+
fixtures :posts
|
1234
|
+
|
1247
1235
|
# Use LEN() vs length() function.
|
1248
1236
|
coerce_tests! %r{order: always allows Arel}
|
1249
1237
|
test "order: always allows Arel" do
|
@@ -1273,6 +1261,86 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
|
|
1273
1261
|
assert_equal ids_expected, ids_depr
|
1274
1262
|
assert_equal ids_expected, ids_disabled
|
1275
1263
|
end
|
1264
|
+
|
1265
|
+
test "order: allows string column names that are quoted" do
|
1266
|
+
ids_expected = Post.order(Arel.sql("id")).pluck(:id)
|
1267
|
+
|
1268
|
+
ids_depr = with_unsafe_raw_sql_deprecated { Post.order("[id]").pluck(:id) }
|
1269
|
+
ids_disabled = with_unsafe_raw_sql_disabled { Post.order("[id]").pluck(:id) }
|
1270
|
+
|
1271
|
+
assert_equal ids_expected, ids_depr
|
1272
|
+
assert_equal ids_expected, ids_disabled
|
1273
|
+
end
|
1274
|
+
|
1275
|
+
test "order: allows string column names that are quoted with table" do
|
1276
|
+
ids_expected = Post.order(Arel.sql("id")).pluck(:id)
|
1277
|
+
|
1278
|
+
ids_depr = with_unsafe_raw_sql_deprecated { Post.order("[posts].[id]").pluck(:id) }
|
1279
|
+
ids_disabled = with_unsafe_raw_sql_disabled { Post.order("[posts].[id]").pluck(:id) }
|
1280
|
+
|
1281
|
+
assert_equal ids_expected, ids_depr
|
1282
|
+
assert_equal ids_expected, ids_disabled
|
1283
|
+
end
|
1284
|
+
|
1285
|
+
test "order: allows string column names that are quoted with table and user" do
|
1286
|
+
ids_expected = Post.order(Arel.sql("id")).pluck(:id)
|
1287
|
+
|
1288
|
+
ids_depr = with_unsafe_raw_sql_deprecated { Post.order("[dbo].[posts].[id]").pluck(:id) }
|
1289
|
+
ids_disabled = with_unsafe_raw_sql_disabled { Post.order("[dbo].[posts].[id]").pluck(:id) }
|
1290
|
+
|
1291
|
+
assert_equal ids_expected, ids_depr
|
1292
|
+
assert_equal ids_expected, ids_disabled
|
1293
|
+
end
|
1294
|
+
|
1295
|
+
test "order: allows string column names that are quoted with table, user and database" do
|
1296
|
+
ids_expected = Post.order(Arel.sql("id")).pluck(:id)
|
1297
|
+
|
1298
|
+
ids_depr = with_unsafe_raw_sql_deprecated { Post.order("[activerecord_unittest].[dbo].[posts].[id]").pluck(:id) }
|
1299
|
+
ids_disabled = with_unsafe_raw_sql_disabled { Post.order("[activerecord_unittest].[dbo].[posts].[id]").pluck(:id) }
|
1300
|
+
|
1301
|
+
assert_equal ids_expected, ids_depr
|
1302
|
+
assert_equal ids_expected, ids_disabled
|
1303
|
+
end
|
1304
|
+
|
1305
|
+
test "pluck: allows string column name that are quoted" do
|
1306
|
+
titles_expected = Post.pluck(Arel.sql("title"))
|
1307
|
+
|
1308
|
+
titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("[title]") }
|
1309
|
+
titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[title]") }
|
1310
|
+
|
1311
|
+
assert_equal titles_expected, titles_depr
|
1312
|
+
assert_equal titles_expected, titles_disabled
|
1313
|
+
end
|
1314
|
+
|
1315
|
+
test "pluck: allows string column name that are quoted with table" do
|
1316
|
+
titles_expected = Post.pluck(Arel.sql("title"))
|
1317
|
+
|
1318
|
+
titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("[posts].[title]") }
|
1319
|
+
titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[posts].[title]") }
|
1320
|
+
|
1321
|
+
assert_equal titles_expected, titles_depr
|
1322
|
+
assert_equal titles_expected, titles_disabled
|
1323
|
+
end
|
1324
|
+
|
1325
|
+
test "pluck: allows string column name that are quoted with table and user" do
|
1326
|
+
titles_expected = Post.pluck(Arel.sql("title"))
|
1327
|
+
|
1328
|
+
titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("[dbo].[posts].[title]") }
|
1329
|
+
titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[dbo].[posts].[title]") }
|
1330
|
+
|
1331
|
+
assert_equal titles_expected, titles_depr
|
1332
|
+
assert_equal titles_expected, titles_disabled
|
1333
|
+
end
|
1334
|
+
|
1335
|
+
test "pluck: allows string column name that are quoted with table, user and database" do
|
1336
|
+
titles_expected = Post.pluck(Arel.sql("title"))
|
1337
|
+
|
1338
|
+
titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("[activerecord_unittest].[dbo].[posts].[title]") }
|
1339
|
+
titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("[activerecord_unittest].[dbo].[posts].[title]") }
|
1340
|
+
|
1341
|
+
assert_equal titles_expected, titles_depr
|
1342
|
+
assert_equal titles_expected, titles_disabled
|
1343
|
+
end
|
1276
1344
|
end
|
1277
1345
|
|
1278
1346
|
class ReservedWordTest < ActiveRecord::TestCase
|
@@ -1333,6 +1401,7 @@ class EnumTest < ActiveRecord::TestCase
|
|
1333
1401
|
|
1334
1402
|
send(:'original_enums are distinct per class')
|
1335
1403
|
ensure
|
1404
|
+
Book.where(author_id: nil, name: nil).delete_all
|
1336
1405
|
Book.connection.add_index(:books, [:author_id, :name], unique: true)
|
1337
1406
|
end
|
1338
1407
|
|
@@ -1343,6 +1412,7 @@ class EnumTest < ActiveRecord::TestCase
|
|
1343
1412
|
|
1344
1413
|
send(:'original_creating new objects with enum scopes')
|
1345
1414
|
ensure
|
1415
|
+
Book.where(author_id: nil, name: nil).delete_all
|
1346
1416
|
Book.connection.add_index(:books, [:author_id, :name], unique: true)
|
1347
1417
|
end
|
1348
1418
|
|
@@ -1353,6 +1423,7 @@ class EnumTest < ActiveRecord::TestCase
|
|
1353
1423
|
|
1354
1424
|
send(:'original_enums are inheritable')
|
1355
1425
|
ensure
|
1426
|
+
Book.where(author_id: nil, name: nil).delete_all
|
1356
1427
|
Book.connection.add_index(:books, [:author_id, :name], unique: true)
|
1357
1428
|
end
|
1358
1429
|
|
@@ -1363,6 +1434,7 @@ class EnumTest < ActiveRecord::TestCase
|
|
1363
1434
|
|
1364
1435
|
send(:'original_declare multiple enums at a time')
|
1365
1436
|
ensure
|
1437
|
+
Book.where(author_id: nil, name: nil).delete_all
|
1366
1438
|
Book.connection.add_index(:books, [:author_id, :name], unique: true)
|
1367
1439
|
end
|
1368
1440
|
end
|
@@ -1456,3 +1528,76 @@ class ReloadModelsTest < ActiveRecord::TestCase
|
|
1456
1528
|
# `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
|
1457
1529
|
coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
|
1458
1530
|
end
|
1531
|
+
|
1532
|
+
require "models/post"
|
1533
|
+
class AnnotateTest < ActiveRecord::TestCase
|
1534
|
+
# Same as original coerced test except our SQL starts with `EXEC sp_executesql`.
|
1535
|
+
# TODO: Remove coerce after Rails 7 (see https://github.com/rails/rails/pull/42027)
|
1536
|
+
coerce_tests! :test_annotate_wraps_content_in_an_inline_comment
|
1537
|
+
def test_annotate_wraps_content_in_an_inline_comment_coerced
|
1538
|
+
quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts")
|
1539
|
+
|
1540
|
+
assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
|
1541
|
+
posts = Post.select(:id).annotate("foo")
|
1542
|
+
assert posts.first
|
1543
|
+
end
|
1544
|
+
end
|
1545
|
+
|
1546
|
+
# Same as original coerced test except our SQL starts with `EXEC sp_executesql`.
|
1547
|
+
# TODO: Remove coerce after Rails 7 (see https://github.com/rails/rails/pull/42027)
|
1548
|
+
coerce_tests! :test_annotate_is_sanitized
|
1549
|
+
def test_annotate_is_sanitized_coerced
|
1550
|
+
quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts")
|
1551
|
+
|
1552
|
+
assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \* /foo/ \* \*/}i) do
|
1553
|
+
posts = Post.select(:id).annotate("*/foo/*")
|
1554
|
+
assert posts.first
|
1555
|
+
end
|
1556
|
+
|
1557
|
+
assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \*\* //foo// \*\* \*/}i) do
|
1558
|
+
posts = Post.select(:id).annotate("**//foo//**")
|
1559
|
+
assert posts.first
|
1560
|
+
end
|
1561
|
+
|
1562
|
+
assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \* \* //foo// \* \* \*/}i) do
|
1563
|
+
posts = Post.select(:id).annotate("* *//foo//* *")
|
1564
|
+
assert posts.first
|
1565
|
+
end
|
1566
|
+
|
1567
|
+
assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \* /foo/ \* \*/ /\* \* /bar \*/}i) do
|
1568
|
+
posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
|
1569
|
+
assert posts.first
|
1570
|
+
end
|
1571
|
+
|
1572
|
+
assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \+ MAX_EXECUTION_TIME\(1\) \*/}i) do
|
1573
|
+
posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)")
|
1574
|
+
assert posts.first
|
1575
|
+
end
|
1576
|
+
end
|
1577
|
+
end
|
1578
|
+
|
1579
|
+
class NestedThroughAssociationsTest < ActiveRecord::TestCase
|
1580
|
+
# Same as original but replace order with "order(:id)" to ensure that assert_includes_and_joins_equal doesn't raise
|
1581
|
+
# "A column has been specified more than once in the order by list"
|
1582
|
+
# Example: original test generate queries like "ORDER BY authors.id, [authors].[id]". We don't support duplicate columns in the order list
|
1583
|
+
coerce_tests! :test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins, :test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins
|
1584
|
+
def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins_coerced
|
1585
|
+
# preload table schemas
|
1586
|
+
Author.joins(:category_post_comments).first
|
1587
|
+
|
1588
|
+
assert_includes_and_joins_equal(
|
1589
|
+
Author.where("comments.id" => comments(:does_it_hurt).id).order(:id),
|
1590
|
+
[authors(:david), authors(:mary)], :category_post_comments
|
1591
|
+
)
|
1592
|
+
end
|
1593
|
+
|
1594
|
+
def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins_coerced
|
1595
|
+
# preload table schemas
|
1596
|
+
Category.joins(:post_comments).first
|
1597
|
+
|
1598
|
+
assert_includes_and_joins_equal(
|
1599
|
+
Category.where("comments.id" => comments(:more_greetings).id).order(:id),
|
1600
|
+
[categories(:general), categories(:technology)], :post_comments
|
1601
|
+
)
|
1602
|
+
end
|
1603
|
+
end
|
@@ -157,13 +157,16 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
|
|
157
157
|
_(col.default).must_equal BigDecimal("191")
|
158
158
|
_(obj.numeric_18_0).must_equal BigDecimal("191")
|
159
159
|
_(col.default_function).must_be_nil
|
160
|
+
|
160
161
|
type = connection.lookup_cast_type_from_column(col)
|
161
|
-
_(type).must_be_instance_of Type::
|
162
|
+
_(type).must_be_instance_of Type::DecimalWithoutScale
|
162
163
|
_(type.limit).must_be_nil
|
163
164
|
_(type.precision).must_equal 18
|
164
|
-
_(type.scale).
|
165
|
+
_(type.scale).must_be_nil
|
166
|
+
|
165
167
|
obj.numeric_18_0 = "192.1"
|
166
168
|
_(obj.numeric_18_0).must_equal BigDecimal("192")
|
169
|
+
|
167
170
|
obj.save!
|
168
171
|
_(obj.reload.numeric_18_0).must_equal BigDecimal("192")
|
169
172
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cases/helper_sqlserver"
|
4
|
+
require "models/post"
|
5
|
+
require "models/author"
|
6
|
+
|
7
|
+
class LateralTestSQLServer < ActiveRecord::TestCase
|
8
|
+
fixtures :posts, :authors
|
9
|
+
|
10
|
+
it 'uses OUTER APPLY for OUTER JOIN LATERAL' do
|
11
|
+
post = Arel::Table.new(:posts)
|
12
|
+
author = Arel::Table.new(:authors)
|
13
|
+
subselect = post.project(Arel.star).take(1).where(post[:author_id].eq(author[:id])).where(post[:id].eq(42))
|
14
|
+
|
15
|
+
one = Arel::Nodes::Quoted.new(1)
|
16
|
+
eq = Arel::Nodes::Equality.new(one, one)
|
17
|
+
|
18
|
+
sql = author.project(Arel.star).where(author[:name].matches("David")).outer_join(subselect.lateral.as("bar")).on(eq).to_sql
|
19
|
+
results = ActiveRecord::Base.connection.exec_query sql
|
20
|
+
assert_equal sql, "SELECT * FROM [authors] OUTER APPLY (SELECT * FROM [posts] WHERE [posts].[author_id] = [authors].[id] AND [posts].[id] = 42 ORDER BY [posts].[id] ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) AS bar WHERE [authors].[name] LIKE N'David'"
|
21
|
+
assert_equal results.length, 1
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'uses CROSS APPLY for INNER JOIN LATERAL' do
|
25
|
+
post = Arel::Table.new(:posts)
|
26
|
+
author = Arel::Table.new(:authors)
|
27
|
+
subselect = post.project(Arel.star).take(1).where(post[:author_id].eq(author[:id])).where(post[:id].eq(42))
|
28
|
+
|
29
|
+
sql = author.project(Arel.star).where(author[:name].matches("David")).join(subselect.lateral.as("bar")).to_sql
|
30
|
+
results = ActiveRecord::Base.connection.exec_query sql
|
31
|
+
|
32
|
+
assert_equal sql, "SELECT * FROM [authors] CROSS APPLY (SELECT * FROM [posts] WHERE [posts].[author_id] = [authors].[id] AND [posts].[id] = 42 ORDER BY [posts].[id] ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) AS bar WHERE [authors].[name] LIKE N'David'"
|
33
|
+
assert_equal results.length, 0
|
34
|
+
end
|
35
|
+
end
|
@@ -64,4 +64,48 @@ class MigrationTestSQLServer < ActiveRecord::TestCase
|
|
64
64
|
assert_nothing_raised { connection.change_column :people, :first_name, :text, null: true, default: nil }
|
65
65
|
end
|
66
66
|
end
|
67
|
+
|
68
|
+
describe "#create_schema" do
|
69
|
+
it "creates a new schema" do
|
70
|
+
connection.create_schema("some schema")
|
71
|
+
|
72
|
+
schemas = connection.exec_query("select name from sys.schemas").to_a
|
73
|
+
|
74
|
+
assert_includes schemas, { "name" => "some schema" }
|
75
|
+
end
|
76
|
+
|
77
|
+
it "creates a new schema with an owner" do
|
78
|
+
connection.create_schema("some schema", :guest)
|
79
|
+
|
80
|
+
schemas = connection.exec_query("select name, principal_id from sys.schemas").to_a
|
81
|
+
|
82
|
+
assert_includes schemas, { "name" => "some schema", "principal_id" => 2 }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#change_table_schema" do
|
87
|
+
before { connection.create_schema("foo") }
|
88
|
+
|
89
|
+
it "transfer the given table to the given schema" do
|
90
|
+
connection.change_table_schema("foo", "orders")
|
91
|
+
|
92
|
+
assert connection.data_source_exists?("foo.orders")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "#drop_schema" do
|
97
|
+
before { connection.create_schema("some schema") }
|
98
|
+
|
99
|
+
it "drops a schema" do
|
100
|
+
schemas = connection.exec_query("select name from sys.schemas").to_a
|
101
|
+
|
102
|
+
assert_includes schemas, { "name" => "some schema" }
|
103
|
+
|
104
|
+
connection.drop_schema("some schema")
|
105
|
+
|
106
|
+
schemas = connection.exec_query("select name from sys.schemas").to_a
|
107
|
+
|
108
|
+
refute_includes schemas, { "name" => "some schema" }
|
109
|
+
end
|
110
|
+
end
|
67
111
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cases/helper_sqlserver"
|
4
|
+
require "models/company"
|
5
|
+
|
6
|
+
class OptimizerHitsTestSQLServer < ActiveRecord::TestCase
|
7
|
+
fixtures :companies
|
8
|
+
|
9
|
+
it "apply optimizations" do
|
10
|
+
assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
|
11
|
+
companies = Company.optimizer_hints("HASH GROUP")
|
12
|
+
companies = companies.distinct.select("firm_id")
|
13
|
+
assert_includes companies.explain, "| Hash Match | Aggregate |"
|
14
|
+
end
|
15
|
+
|
16
|
+
assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(ORDER GROUP\)\z}) do
|
17
|
+
companies = Company.optimizer_hints("ORDER GROUP")
|
18
|
+
companies = companies.distinct.select("firm_id")
|
19
|
+
assert_includes companies.explain, "| Stream Aggregate | Aggregate |"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "apply multiple optimizations" do
|
24
|
+
assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP, FAST 1\)\z}) do
|
25
|
+
companies = Company.optimizer_hints("HASH GROUP", "FAST 1")
|
26
|
+
companies = companies.distinct.select("firm_id")
|
27
|
+
assert_includes companies.explain, "| Hash Match | Flow Distinct |"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "support subqueries" do
|
32
|
+
assert_sql(%r{.*'SELECT COUNT\(count_column\) FROM \(SELECT .*\) subquery_for_count OPTION \(MAXDOP 2\)'.*}) do
|
33
|
+
companies = Company.optimizer_hints("MAXDOP 2")
|
34
|
+
companies = companies.select(:id).where(firm_id: [0, 1]).limit(3)
|
35
|
+
assert_equal 3, companies.count
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "sanitize values" do
|
40
|
+
assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
|
41
|
+
companies = Company.optimizer_hints("OPTION (HASH GROUP)")
|
42
|
+
companies = companies.distinct.select("firm_id")
|
43
|
+
companies.to_a
|
44
|
+
end
|
45
|
+
|
46
|
+
assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
|
47
|
+
companies = Company.optimizer_hints("OPTION(HASH GROUP)")
|
48
|
+
companies = companies.distinct.select("firm_id")
|
49
|
+
companies.to_a
|
50
|
+
end
|
51
|
+
|
52
|
+
assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(TABLE HINT \(\[companies\], INDEX\(1\)\)\)\z}) do
|
53
|
+
companies = Company.optimizer_hints("OPTION(TABLE HINT ([companies], INDEX(1)))")
|
54
|
+
companies = companies.distinct.select("firm_id")
|
55
|
+
companies.to_a
|
56
|
+
end
|
57
|
+
|
58
|
+
assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
|
59
|
+
companies = Company.optimizer_hints("Option(HASH GROUP)")
|
60
|
+
companies = companies.distinct.select("firm_id")
|
61
|
+
companies.to_a
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "skip optimization after unscope" do
|
66
|
+
assert_sql("SELECT DISTINCT [companies].[firm_id] FROM [companies]") do
|
67
|
+
companies = Company.optimizer_hints("HASH GROUP")
|
68
|
+
companies = companies.distinct.select("firm_id")
|
69
|
+
companies.unscope(:optimizer_hints).load
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -16,7 +16,7 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
|
|
16
16
|
assert_line :tinyint, type: "integer", limit: 1, precision: nil, scale: nil, default: 42
|
17
17
|
assert_line :bit, type: "boolean", limit: nil, precision: nil, scale: nil, default: true
|
18
18
|
assert_line :decimal_9_2, type: "decimal", limit: nil, precision: 9, scale: 2, default: 12345.01
|
19
|
-
assert_line :numeric_18_0, type: "decimal", limit: nil, precision: 18, scale:
|
19
|
+
assert_line :numeric_18_0, type: "decimal", limit: nil, precision: 18, scale: nil, default: 191
|
20
20
|
assert_line :numeric_36_2, type: "decimal", limit: nil, precision: 36, scale: 2, default: 12345678901234567890.01
|
21
21
|
assert_line :money, type: "money", limit: nil, precision: 19, scale: 4, default: 4.2
|
22
22
|
assert_line :smallmoney, type: "smallmoney", limit: nil, precision: 10, scale: 4, default: 4.2
|
@@ -75,7 +75,7 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
|
|
75
75
|
assert_line :integer_col, type: "integer", limit: nil, precision: nil, scale: nil, default: nil
|
76
76
|
assert_line :bigint_col, type: "bigint", limit: nil, precision: nil, scale: nil, default: nil
|
77
77
|
assert_line :boolean_col, type: "boolean", limit: nil, precision: nil, scale: nil, default: nil
|
78
|
-
assert_line :decimal_col, type: "decimal", limit: nil, precision: 18, scale:
|
78
|
+
assert_line :decimal_col, type: "decimal", limit: nil, precision: 18, scale: nil, default: nil
|
79
79
|
assert_line :float_col, type: "float", limit: nil, precision: nil, scale: nil, default: nil
|
80
80
|
assert_line :string_col, type: "string", limit: nil, precision: nil, scale: nil, default: nil
|
81
81
|
assert_line :text_col, type: "text", limit: nil, precision: nil, scale: nil, default: nil
|
@@ -166,13 +166,15 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
|
|
166
166
|
|
167
167
|
def assert_line(column_name, options = {})
|
168
168
|
line = line(column_name)
|
169
|
-
assert line, "
|
169
|
+
assert line, "Could not find line with column name: #{column_name.inspect} in schema:\n#{schema}"
|
170
|
+
|
170
171
|
[:type, :limit, :precision, :scale, :collation, :default].each do |key|
|
171
172
|
next unless options.key?(key)
|
172
173
|
|
173
174
|
actual = key == :type ? line.send(:type_method) : line.send(key)
|
174
175
|
expected = options[key]
|
175
176
|
message = "#{key.to_s.titleize} of #{expected.inspect} not found in:\n#{line}"
|
177
|
+
|
176
178
|
if expected.nil?
|
177
179
|
_(actual).must_be_nil message
|
178
180
|
elsif expected.is_a?(Array)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-sqlserver-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.0.
|
4
|
+
version: 6.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ken Collins
|
@@ -14,7 +14,7 @@ authors:
|
|
14
14
|
autorequire:
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
|
-
date:
|
17
|
+
date: 2023-05-23 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: activerecord
|
@@ -54,9 +54,9 @@ extra_rdoc_files: []
|
|
54
54
|
files:
|
55
55
|
- ".editorconfig"
|
56
56
|
- ".github/issue_template.md"
|
57
|
+
- ".github/workflows/ci.yml"
|
57
58
|
- ".gitignore"
|
58
59
|
- ".rubocop.yml"
|
59
|
-
- ".travis.yml"
|
60
60
|
- CHANGELOG.md
|
61
61
|
- CODE_OF_CONDUCT.md
|
62
62
|
- Dockerfile.ci
|
@@ -104,6 +104,7 @@ files:
|
|
104
104
|
- lib/active_record/connection_adapters/sqlserver/type/datetime2.rb
|
105
105
|
- lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb
|
106
106
|
- lib/active_record/connection_adapters/sqlserver/type/decimal.rb
|
107
|
+
- lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb
|
107
108
|
- lib/active_record/connection_adapters/sqlserver/type/float.rb
|
108
109
|
- lib/active_record/connection_adapters/sqlserver/type/integer.rb
|
109
110
|
- lib/active_record/connection_adapters/sqlserver/type/json.rb
|
@@ -154,7 +155,9 @@ files:
|
|
154
155
|
- test/cases/in_clause_test_sqlserver.rb
|
155
156
|
- test/cases/index_test_sqlserver.rb
|
156
157
|
- test/cases/json_test_sqlserver.rb
|
158
|
+
- test/cases/lateral_test_sqlserver.rb
|
157
159
|
- test/cases/migration_test_sqlserver.rb
|
160
|
+
- test/cases/optimizer_hints_test_sqlserver.rb
|
158
161
|
- test/cases/order_test_sqlserver.rb
|
159
162
|
- test/cases/pessimistic_locking_test_sqlserver.rb
|
160
163
|
- test/cases/rake_test_sqlserver.rb
|
@@ -214,8 +217,8 @@ licenses:
|
|
214
217
|
- MIT
|
215
218
|
metadata:
|
216
219
|
bug_tracker_uri: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues
|
217
|
-
changelog_uri: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/v6.0.
|
218
|
-
source_code_uri: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/v6.0.
|
220
|
+
changelog_uri: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/v6.0.3/CHANGELOG.md
|
221
|
+
source_code_uri: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/v6.0.3
|
219
222
|
post_install_message:
|
220
223
|
rdoc_options: []
|
221
224
|
require_paths:
|
@@ -231,7 +234,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
231
234
|
- !ruby/object:Gem::Version
|
232
235
|
version: '0'
|
233
236
|
requirements: []
|
234
|
-
rubygems_version: 3.
|
237
|
+
rubygems_version: 3.4.7
|
235
238
|
signing_key:
|
236
239
|
specification_version: 4
|
237
240
|
summary: ActiveRecord SQL Server Adapter.
|
@@ -253,7 +256,9 @@ test_files:
|
|
253
256
|
- test/cases/in_clause_test_sqlserver.rb
|
254
257
|
- test/cases/index_test_sqlserver.rb
|
255
258
|
- test/cases/json_test_sqlserver.rb
|
259
|
+
- test/cases/lateral_test_sqlserver.rb
|
256
260
|
- test/cases/migration_test_sqlserver.rb
|
261
|
+
- test/cases/optimizer_hints_test_sqlserver.rb
|
257
262
|
- test/cases/order_test_sqlserver.rb
|
258
263
|
- test/cases/pessimistic_locking_test_sqlserver.rb
|
259
264
|
- test/cases/rake_test_sqlserver.rb
|
data/.travis.yml
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
sudo: required
|
2
|
-
cache: bundler
|
3
|
-
services:
|
4
|
-
- docker
|
5
|
-
env:
|
6
|
-
global:
|
7
|
-
- COMPOSE_FILE: docker-compose.ci.yml
|
8
|
-
before_install:
|
9
|
-
- sudo rm /usr/local/bin/docker-compose
|
10
|
-
- sudo curl -L "https://github.com/docker/compose/releases/download/1.22.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
11
|
-
- sudo chmod +x /usr/local/bin/docker-compose
|
12
|
-
install:
|
13
|
-
- docker-compose build --build-arg TARGET_VERSION=$TARGET_VERSION
|
14
|
-
script:
|
15
|
-
- docker-compose run ci
|
16
|
-
matrix:
|
17
|
-
include:
|
18
|
-
- name: 2.5.8
|
19
|
-
env: TARGET_VERSION=2.5.8
|
20
|
-
- name: 2.6.6
|
21
|
-
env: TARGET_VERSION=2.6.6
|
22
|
-
- name: 2.7.1
|
23
|
-
env: TARGET_VERSION=2.7.1
|