activerecord-sqlserver-adapter 5.2.1 → 6.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.editorconfig +9 -0
- data/.github/issue_template.md +23 -0
- data/.github/workflows/ci.yml +26 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +29 -0
- data/CHANGELOG.md +58 -20
- data/{Dockerfile → Dockerfile.ci} +1 -1
- data/Gemfile +48 -41
- data/Guardfile +9 -8
- data/README.md +28 -31
- data/RUNNING_UNIT_TESTS.md +3 -0
- data/Rakefile +14 -16
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +25 -14
- data/appveyor.yml +24 -17
- data/docker-compose.ci.yml +7 -5
- data/guides/RELEASING.md +11 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +3 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +8 -7
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +36 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +6 -4
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +88 -44
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +9 -12
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +46 -8
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +16 -5
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -7
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +210 -163
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +8 -8
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +4 -2
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +43 -44
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +7 -9
- data/lib/active_record/connection_adapters/sqlserver/type.rb +38 -35
- data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/char.rb +7 -4
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +4 -3
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +8 -8
- data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
- data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
- data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
- data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
- data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -11
- data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +145 -94
- data/lib/active_record/connection_adapters/sqlserver_column.rb +9 -5
- data/lib/active_record/sqlserver_base.rb +9 -1
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +28 -32
- data/lib/activerecord-sqlserver-adapter.rb +3 -1
- data/lib/arel/visitors/sqlserver.rb +108 -34
- data/lib/arel_sqlserver.rb +4 -2
- data/test/appveyor/dbsetup.ps1 +4 -4
- data/test/cases/adapter_test_sqlserver.rb +246 -171
- data/test/cases/change_column_null_test_sqlserver.rb +14 -12
- data/test/cases/coerced_tests.rb +722 -381
- data/test/cases/column_test_sqlserver.rb +287 -285
- data/test/cases/connection_test_sqlserver.rb +17 -20
- data/test/cases/execute_procedure_test_sqlserver.rb +20 -20
- data/test/cases/fetch_test_sqlserver.rb +16 -22
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
- data/test/cases/helper_sqlserver.rb +15 -15
- data/test/cases/in_clause_test_sqlserver.rb +36 -0
- data/test/cases/index_test_sqlserver.rb +15 -15
- data/test/cases/json_test_sqlserver.rb +25 -25
- data/test/cases/lateral_test_sqlserver.rb +35 -0
- data/test/cases/migration_test_sqlserver.rb +67 -27
- data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
- data/test/cases/order_test_sqlserver.rb +53 -54
- data/test/cases/pessimistic_locking_test_sqlserver.rb +27 -33
- data/test/cases/rake_test_sqlserver.rb +33 -45
- data/test/cases/schema_dumper_test_sqlserver.rb +115 -109
- data/test/cases/schema_test_sqlserver.rb +20 -26
- data/test/cases/scratchpad_test_sqlserver.rb +4 -4
- data/test/cases/showplan_test_sqlserver.rb +28 -35
- data/test/cases/specific_schema_test_sqlserver.rb +68 -65
- data/test/cases/transaction_test_sqlserver.rb +18 -20
- data/test/cases/trigger_test_sqlserver.rb +14 -13
- data/test/cases/utils_test_sqlserver.rb +70 -70
- data/test/cases/uuid_test_sqlserver.rb +13 -14
- data/test/debug.rb +8 -6
- data/test/migrations/create_clients_and_change_column_null.rb +3 -1
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +4 -4
- data/test/models/sqlserver/booking.rb +3 -1
- data/test/models/sqlserver/customers_view.rb +3 -1
- data/test/models/sqlserver/datatype.rb +2 -0
- data/test/models/sqlserver/datatype_migration.rb +2 -0
- data/test/models/sqlserver/dollar_table_name.rb +3 -1
- data/test/models/sqlserver/edge_schema.rb +3 -3
- data/test/models/sqlserver/fk_has_fk.rb +3 -1
- data/test/models/sqlserver/fk_has_pk.rb +3 -1
- data/test/models/sqlserver/natural_pk_data.rb +4 -2
- data/test/models/sqlserver/natural_pk_int_data.rb +3 -1
- data/test/models/sqlserver/no_pk_data.rb +3 -1
- data/test/models/sqlserver/object_default.rb +3 -1
- data/test/models/sqlserver/quoted_table.rb +4 -2
- data/test/models/sqlserver/quoted_view_1.rb +3 -1
- data/test/models/sqlserver/quoted_view_2.rb +3 -1
- data/test/models/sqlserver/sst_memory.rb +3 -1
- data/test/models/sqlserver/string_default.rb +3 -1
- data/test/models/sqlserver/string_defaults_big_view.rb +3 -1
- data/test/models/sqlserver/string_defaults_view.rb +3 -1
- data/test/models/sqlserver/tinyint_pk.rb +3 -1
- data/test/models/sqlserver/trigger.rb +4 -2
- data/test/models/sqlserver/trigger_history.rb +3 -1
- data/test/models/sqlserver/upper.rb +3 -1
- data/test/models/sqlserver/uppered.rb +3 -1
- data/test/models/sqlserver/uuid.rb +3 -1
- data/test/schema/sqlserver_specific_schema.rb +31 -21
- data/test/support/coerceable_test_sqlserver.rb +15 -9
- data/test/support/connection_reflection.rb +3 -2
- data/test/support/core_ext/query_cache.rb +4 -1
- data/test/support/load_schema_sqlserver.rb +5 -5
- data/test/support/minitest_sqlserver.rb +3 -1
- data/test/support/paths_sqlserver.rb +11 -11
- data/test/support/rake_helpers.rb +13 -10
- data/test/support/sql_counter_sqlserver.rb +3 -4
- data/test/support/test_in_memory_oltp.rb +9 -7
- metadata +27 -12
- data/.travis.yml +0 -25
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
class SQLServerColumn < Column
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
super(name, default, sql_type_metadata, null, table_name, default_function, collation, comment: comment)
|
|
6
|
+
def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **sqlserver_options)
|
|
7
|
+
@sqlserver_options = sqlserver_options
|
|
8
|
+
super
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
def is_identity?
|
|
@@ -15,6 +16,10 @@ module ActiveRecord
|
|
|
15
16
|
@sqlserver_options[:is_primary]
|
|
16
17
|
end
|
|
17
18
|
|
|
19
|
+
def table_name
|
|
20
|
+
@sqlserver_options[:table_name]
|
|
21
|
+
end
|
|
22
|
+
|
|
18
23
|
def is_utf8?
|
|
19
24
|
sql_type =~ /nvarchar|ntext|nchar/i
|
|
20
25
|
end
|
|
@@ -22,7 +27,6 @@ module ActiveRecord
|
|
|
22
27
|
def case_sensitive?
|
|
23
28
|
collation && collation.match(/_CS/)
|
|
24
29
|
end
|
|
25
|
-
|
|
26
30
|
end
|
|
27
31
|
end
|
|
28
32
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionHandling
|
|
3
5
|
def sqlserver_connection(config) #:nodoc:
|
|
@@ -6,11 +8,17 @@ module ActiveRecord
|
|
|
6
8
|
mode = config[:mode].to_s.downcase.underscore.to_sym
|
|
7
9
|
case mode
|
|
8
10
|
when :dblib
|
|
9
|
-
require
|
|
11
|
+
require "tiny_tds"
|
|
10
12
|
else
|
|
11
13
|
raise ArgumentError, "Unknown connection mode in #{config.inspect}."
|
|
12
14
|
end
|
|
13
15
|
ConnectionAdapters::SQLServerAdapter.new(nil, nil, config.merge(mode: mode))
|
|
16
|
+
rescue TinyTds::Error => e
|
|
17
|
+
if e.message.match(/database .* does not exist/i)
|
|
18
|
+
raise ActiveRecord::NoDatabaseError
|
|
19
|
+
else
|
|
20
|
+
raise
|
|
21
|
+
end
|
|
14
22
|
end
|
|
15
23
|
end
|
|
16
24
|
end
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/tasks/database_tasks"
|
|
4
|
+
require "shellwords"
|
|
5
|
+
require "ipaddr"
|
|
6
|
+
require "socket"
|
|
5
7
|
|
|
6
8
|
module ActiveRecord
|
|
7
9
|
module Tasks
|
|
8
|
-
|
|
9
10
|
class SQLServerDatabaseTasks
|
|
10
|
-
|
|
11
|
-
DEFAULT_COLLATION = 'SQL_Latin1_General_CP1_CI_AS'
|
|
11
|
+
DEFAULT_COLLATION = "SQL_Latin1_General_CP1_CI_AS"
|
|
12
12
|
|
|
13
13
|
delegate :connection, :establish_connection, :clear_active_connections!,
|
|
14
14
|
to: ActiveRecord::Base
|
|
@@ -19,10 +19,10 @@ module ActiveRecord
|
|
|
19
19
|
|
|
20
20
|
def create(master_established = false)
|
|
21
21
|
establish_master_connection unless master_established
|
|
22
|
-
connection.create_database configuration[
|
|
22
|
+
connection.create_database configuration["database"], configuration.merge("collation" => default_collation)
|
|
23
23
|
establish_connection configuration
|
|
24
|
-
rescue ActiveRecord::StatementInvalid =>
|
|
25
|
-
if /database .* already exists/i ===
|
|
24
|
+
rescue ActiveRecord::StatementInvalid => e
|
|
25
|
+
if /database .* already exists/i === e.message
|
|
26
26
|
raise DatabaseAlreadyExists
|
|
27
27
|
else
|
|
28
28
|
raise
|
|
@@ -31,7 +31,7 @@ module ActiveRecord
|
|
|
31
31
|
|
|
32
32
|
def drop
|
|
33
33
|
establish_master_connection
|
|
34
|
-
connection.drop_database configuration[
|
|
34
|
+
connection.drop_database configuration["database"]
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def charset
|
|
@@ -50,7 +50,7 @@ module ActiveRecord
|
|
|
50
50
|
|
|
51
51
|
def structure_dump(filename, extra_flags)
|
|
52
52
|
server_arg = "-S #{Shellwords.escape(configuration['host'])}"
|
|
53
|
-
server_arg += ":#{Shellwords.escape(configuration['port'])}" if configuration[
|
|
53
|
+
server_arg += ":#{Shellwords.escape(configuration['port'])}" if configuration["port"]
|
|
54
54
|
command = [
|
|
55
55
|
"defncopy-ttds",
|
|
56
56
|
server_arg,
|
|
@@ -63,13 +63,14 @@ module ActiveRecord
|
|
|
63
63
|
command.concat(table_args)
|
|
64
64
|
view_args = connection.views.map { |v| Shellwords.escape(v) }
|
|
65
65
|
command.concat(view_args)
|
|
66
|
-
raise
|
|
66
|
+
raise "Error dumping database" unless Kernel.system(command.join(" "))
|
|
67
|
+
|
|
67
68
|
dump = File.read(filename)
|
|
68
|
-
dump.gsub!(/^USE .*$\nGO\n/,
|
|
69
|
-
dump.gsub!(/^GO\n/,
|
|
70
|
-
dump.gsub!(/nvarchar\(8000\)/,
|
|
71
|
-
dump.gsub!(/nvarchar\(-1\)/,
|
|
72
|
-
dump.gsub!(/text\(\d+\)/,
|
|
69
|
+
dump.gsub!(/^USE .*$\nGO\n/, "") # Strip db USE statements
|
|
70
|
+
dump.gsub!(/^GO\n/, "") # Strip db GO statements
|
|
71
|
+
dump.gsub!(/nvarchar\(8000\)/, "nvarchar(4000)") # Fix nvarchar(8000) column defs
|
|
72
|
+
dump.gsub!(/nvarchar\(-1\)/, "nvarchar(max)") # Fix nvarchar(-1) column defs
|
|
73
|
+
dump.gsub!(/text\(\d+\)/, "text") # Fix text(16) column defs
|
|
73
74
|
File.open(filename, "w") { |file| file.puts dump }
|
|
74
75
|
end
|
|
75
76
|
|
|
@@ -77,7 +78,6 @@ module ActiveRecord
|
|
|
77
78
|
connection.execute File.read(filename)
|
|
78
79
|
end
|
|
79
80
|
|
|
80
|
-
|
|
81
81
|
private
|
|
82
82
|
|
|
83
83
|
def configuration
|
|
@@ -85,25 +85,22 @@ module ActiveRecord
|
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
def default_collation
|
|
88
|
-
configuration[
|
|
88
|
+
configuration["collation"] || DEFAULT_COLLATION
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def establish_master_connection
|
|
92
|
-
establish_connection configuration.merge(
|
|
92
|
+
establish_connection configuration.merge("database" => "master")
|
|
93
93
|
end
|
|
94
|
-
|
|
95
94
|
end
|
|
96
95
|
|
|
97
96
|
module DatabaseTasksSQLServer
|
|
98
|
-
|
|
99
97
|
extend ActiveSupport::Concern
|
|
100
98
|
|
|
101
99
|
module ClassMethods
|
|
102
|
-
|
|
103
100
|
LOCAL_IPADDR = [
|
|
104
|
-
IPAddr.new(
|
|
105
|
-
IPAddr.new(
|
|
106
|
-
IPAddr.new(
|
|
101
|
+
IPAddr.new("192.168.0.0/16"),
|
|
102
|
+
IPAddr.new("10.0.0.0/8"),
|
|
103
|
+
IPAddr.new("172.16.0.0/12")
|
|
107
104
|
]
|
|
108
105
|
|
|
109
106
|
private
|
|
@@ -113,21 +110,20 @@ module ActiveRecord
|
|
|
113
110
|
end
|
|
114
111
|
|
|
115
112
|
def configuration_host_ip(configuration)
|
|
116
|
-
return nil unless configuration[
|
|
117
|
-
|
|
113
|
+
return nil unless configuration["host"]
|
|
114
|
+
|
|
115
|
+
Socket::getaddrinfo(configuration["host"], "echo", Socket::AF_INET)[0][3]
|
|
118
116
|
end
|
|
119
117
|
|
|
120
118
|
def local_ipaddr?(host_ip)
|
|
121
119
|
return false unless host_ip
|
|
120
|
+
|
|
122
121
|
LOCAL_IPADDR.any? { |ip| ip.include?(host_ip) }
|
|
123
122
|
end
|
|
124
|
-
|
|
125
123
|
end
|
|
126
|
-
|
|
127
124
|
end
|
|
128
125
|
|
|
129
126
|
DatabaseTasks.register_task %r{sqlserver}, SQLServerDatabaseTasks
|
|
130
127
|
DatabaseTasks.send :include, DatabaseTasksSQLServer
|
|
131
|
-
|
|
132
128
|
end
|
|
133
129
|
end
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Arel
|
|
2
4
|
module Visitors
|
|
3
5
|
class SQLServer < Arel::Visitors::ToSql
|
|
4
|
-
|
|
5
6
|
OFFSET = " OFFSET "
|
|
6
7
|
ROWS = " ROWS"
|
|
7
8
|
FETCH = " FETCH NEXT "
|
|
8
9
|
FETCH0 = " FETCH FIRST (SELECT 0) "
|
|
9
10
|
ROWS_ONLY = " ROWS ONLY"
|
|
10
11
|
|
|
11
|
-
|
|
12
12
|
private
|
|
13
13
|
|
|
14
14
|
# SQLServer ToSql/Visitor (Overides)
|
|
15
15
|
|
|
16
16
|
def visit_Arel_Nodes_BindParam o, collector
|
|
17
|
-
collector.add_bind(o.value) { |i| "@#{i-1}" }
|
|
17
|
+
collector.add_bind(o.value) { |i| "@#{i - 1}" }
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def visit_Arel_Nodes_Bin o, collector
|
|
@@ -22,6 +22,12 @@ module Arel
|
|
|
22
22
|
collector << " #{ActiveRecord::ConnectionAdapters::SQLServerAdapter.cs_equality_operator} "
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
def visit_Arel_Nodes_Concat(o, collector)
|
|
26
|
+
visit o.left, collector
|
|
27
|
+
collector << " + "
|
|
28
|
+
visit o.right, collector
|
|
29
|
+
end
|
|
30
|
+
|
|
25
31
|
def visit_Arel_Nodes_UpdateStatement(o, a)
|
|
26
32
|
if o.orders.any? && o.limit.nil?
|
|
27
33
|
o.limit = Nodes::Limit.new(9_223_372_036_854_775_807)
|
|
@@ -30,8 +36,8 @@ module Arel
|
|
|
30
36
|
end
|
|
31
37
|
|
|
32
38
|
def visit_Arel_Nodes_Lock o, collector
|
|
33
|
-
o.expr = Arel.sql(
|
|
34
|
-
collector <<
|
|
39
|
+
o.expr = Arel.sql("WITH(UPDLOCK)") if o.expr.to_s =~ /FOR UPDATE/
|
|
40
|
+
collector << " "
|
|
35
41
|
visit o.expr, collector
|
|
36
42
|
end
|
|
37
43
|
|
|
@@ -52,14 +58,19 @@ module Arel
|
|
|
52
58
|
end
|
|
53
59
|
end
|
|
54
60
|
|
|
61
|
+
def visit_Arel_Nodes_Grouping(o, collector)
|
|
62
|
+
remove_invalid_ordering_from_select_statement(o.expr)
|
|
63
|
+
super
|
|
64
|
+
end
|
|
65
|
+
|
|
55
66
|
def visit_Arel_Nodes_SelectStatement o, collector
|
|
56
67
|
@select_statement = o
|
|
57
68
|
distinct_One_As_One_Is_So_Not_Fetch o
|
|
58
69
|
if o.with
|
|
59
70
|
collector = visit o.with, collector
|
|
60
|
-
collector <<
|
|
71
|
+
collector << " "
|
|
61
72
|
end
|
|
62
|
-
collector = o.cores.inject(collector) { |c,x|
|
|
73
|
+
collector = o.cores.inject(collector) { |c, x|
|
|
63
74
|
visit_Arel_Nodes_SelectCore(x, c)
|
|
64
75
|
}
|
|
65
76
|
collector = visit_Orders_And_Let_Fetch_Happen o, collector
|
|
@@ -69,19 +80,31 @@ module Arel
|
|
|
69
80
|
@select_statement = nil
|
|
70
81
|
end
|
|
71
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
|
+
|
|
72
93
|
def visit_Arel_Table o, collector
|
|
73
94
|
# Apparently, o.engine.connection can actually be a different adapter
|
|
74
95
|
# than sqlserver. Can be removed if fixed in ActiveRecord. See:
|
|
75
96
|
# github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/450
|
|
76
|
-
table_name =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
97
|
+
table_name =
|
|
98
|
+
begin
|
|
99
|
+
if o.class.engine.connection.respond_to?(:sqlserver?) && o.class.engine.connection.database_prefix_remote_server?
|
|
100
|
+
remote_server_table_name(o)
|
|
101
|
+
else
|
|
102
|
+
quote_table_name(o.name)
|
|
103
|
+
end
|
|
104
|
+
rescue Exception
|
|
80
105
|
quote_table_name(o.name)
|
|
81
106
|
end
|
|
82
|
-
|
|
83
|
-
quote_table_name(o.name)
|
|
84
|
-
end
|
|
107
|
+
|
|
85
108
|
if o.table_alias
|
|
86
109
|
collector << "#{table_name} #{quote_table_name o.table_alias}"
|
|
87
110
|
else
|
|
@@ -95,30 +118,54 @@ module Arel
|
|
|
95
118
|
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector
|
|
96
119
|
end
|
|
97
120
|
if o.right.any?
|
|
98
|
-
collector <<
|
|
99
|
-
collector = inject_join o.right, collector,
|
|
121
|
+
collector << " " if o.left
|
|
122
|
+
collector = inject_join o.right, collector, " "
|
|
100
123
|
end
|
|
101
124
|
collector
|
|
102
125
|
end
|
|
103
126
|
|
|
104
127
|
def visit_Arel_Nodes_InnerJoin o, collector
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if o.right
|
|
109
|
-
collector << SPACE
|
|
110
|
-
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
|
|
111
131
|
else
|
|
112
|
-
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
|
|
113
141
|
end
|
|
114
142
|
end
|
|
115
143
|
|
|
116
144
|
def visit_Arel_Nodes_OuterJoin o, collector
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def collect_in_clause(left, right, collector)
|
|
158
|
+
if Array === right
|
|
159
|
+
right.each { |node| remove_invalid_ordering_from_select_statement(node) }
|
|
160
|
+
else
|
|
161
|
+
remove_invalid_ordering_from_select_statement(right)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
super
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def collect_optimizer_hints(o, collector)
|
|
168
|
+
collector
|
|
122
169
|
end
|
|
123
170
|
|
|
124
171
|
# SQLServer ToSql/Visitor (Additions)
|
|
@@ -126,7 +173,7 @@ module Arel
|
|
|
126
173
|
def visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, options = {}
|
|
127
174
|
if select_statement_lock?
|
|
128
175
|
collector = visit @select_statement.lock, collector
|
|
129
|
-
collector <<
|
|
176
|
+
collector << " " if options[:space]
|
|
130
177
|
end
|
|
131
178
|
collector
|
|
132
179
|
end
|
|
@@ -134,12 +181,11 @@ module Arel
|
|
|
134
181
|
def visit_Orders_And_Let_Fetch_Happen o, collector
|
|
135
182
|
make_Fetch_Possible_And_Deterministic o
|
|
136
183
|
unless o.orders.empty?
|
|
137
|
-
collector <<
|
|
138
|
-
collector << ORDER_BY
|
|
184
|
+
collector << " ORDER BY "
|
|
139
185
|
len = o.orders.length - 1
|
|
140
186
|
o.orders.each_with_index { |x, i|
|
|
141
187
|
collector = visit(x, collector)
|
|
142
|
-
collector <<
|
|
188
|
+
collector << ", " unless len == i
|
|
143
189
|
}
|
|
144
190
|
end
|
|
145
191
|
collector
|
|
@@ -152,10 +198,23 @@ module Arel
|
|
|
152
198
|
collector
|
|
153
199
|
end
|
|
154
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
|
+
|
|
155
213
|
# SQLServer Helpers
|
|
156
214
|
|
|
157
215
|
def node_value(node)
|
|
158
216
|
return nil unless node
|
|
217
|
+
|
|
159
218
|
case node.expr
|
|
160
219
|
when NilClass then nil
|
|
161
220
|
when Numeric then node.expr
|
|
@@ -169,9 +228,11 @@ module Arel
|
|
|
169
228
|
|
|
170
229
|
def make_Fetch_Possible_And_Deterministic o
|
|
171
230
|
return if o.limit.nil? && o.offset.nil?
|
|
231
|
+
|
|
172
232
|
t = table_From_Statement o
|
|
173
233
|
pk = primary_Key_From_Table t
|
|
174
234
|
return unless pk
|
|
235
|
+
|
|
175
236
|
if o.orders.empty?
|
|
176
237
|
# Prefer deterministic vs a simple `(SELECT NULL)` expr.
|
|
177
238
|
o.orders = [pk.asc]
|
|
@@ -196,14 +257,15 @@ module Arel
|
|
|
196
257
|
elsif Arel::Nodes::SqlLiteral === core.from
|
|
197
258
|
Arel::Table.new(core.from)
|
|
198
259
|
elsif Arel::Nodes::JoinSource === core.source
|
|
199
|
-
Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left
|
|
260
|
+
Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left.left
|
|
200
261
|
end
|
|
201
262
|
end
|
|
202
263
|
|
|
203
264
|
def primary_Key_From_Table t
|
|
204
265
|
return unless t
|
|
266
|
+
|
|
205
267
|
column_name = @connection.schema_cache.primary_keys(t.name) ||
|
|
206
|
-
|
|
268
|
+
@connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
|
|
207
269
|
column_name ? t[column_name] : nil
|
|
208
270
|
end
|
|
209
271
|
|
|
@@ -213,6 +275,18 @@ module Arel
|
|
|
213
275
|
).quoted
|
|
214
276
|
end
|
|
215
277
|
|
|
278
|
+
# Need to remove ordering from subqueries unless TOP/OFFSET also used. Otherwise, SQLServer
|
|
279
|
+
# returns error "The ORDER BY clause is invalid in views, inline functions, derived tables,
|
|
280
|
+
# subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified."
|
|
281
|
+
def remove_invalid_ordering_from_select_statement(node)
|
|
282
|
+
return unless Arel::Nodes::SelectStatement === node
|
|
283
|
+
|
|
284
|
+
node.orders = [] unless node.offset || node.limit
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def sanitize_as_option_clause(value)
|
|
288
|
+
value.gsub(%r{OPTION \s* \( (.+) \)}xi, "\\1")
|
|
289
|
+
end
|
|
216
290
|
end
|
|
217
291
|
end
|
|
218
292
|
end
|