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.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/issue_template.md +23 -0
  4. data/.github/workflows/ci.yml +26 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop.yml +29 -0
  7. data/CHANGELOG.md +58 -20
  8. data/{Dockerfile → Dockerfile.ci} +1 -1
  9. data/Gemfile +48 -41
  10. data/Guardfile +9 -8
  11. data/README.md +28 -31
  12. data/RUNNING_UNIT_TESTS.md +3 -0
  13. data/Rakefile +14 -16
  14. data/VERSION +1 -1
  15. data/activerecord-sqlserver-adapter.gemspec +25 -14
  16. data/appveyor.yml +24 -17
  17. data/docker-compose.ci.yml +7 -5
  18. data/guides/RELEASING.md +11 -0
  19. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -4
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +3 -4
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -4
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +3 -3
  23. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
  24. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +8 -7
  25. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +36 -0
  26. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +6 -4
  27. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -0
  28. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +88 -44
  29. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +9 -12
  30. data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -3
  31. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +46 -8
  32. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +16 -5
  33. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -7
  34. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +210 -163
  35. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +8 -8
  36. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +4 -2
  37. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +3 -1
  38. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -2
  39. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +43 -44
  40. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +7 -9
  41. data/lib/active_record/connection_adapters/sqlserver/type.rb +38 -35
  42. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -3
  43. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -4
  44. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -3
  45. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +7 -4
  46. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +2 -2
  47. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +4 -3
  48. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +8 -8
  49. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -2
  50. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -2
  51. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -4
  52. data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
  53. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -3
  54. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
  55. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
  56. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
  57. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
  58. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
  59. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
  60. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
  61. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
  62. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
  63. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
  64. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
  65. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
  66. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
  68. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
  69. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
  70. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
  71. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
  72. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
  73. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
  74. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
  75. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
  76. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
  77. data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -11
  78. data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
  79. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +145 -94
  80. data/lib/active_record/connection_adapters/sqlserver_column.rb +9 -5
  81. data/lib/active_record/sqlserver_base.rb +9 -1
  82. data/lib/active_record/tasks/sqlserver_database_tasks.rb +28 -32
  83. data/lib/activerecord-sqlserver-adapter.rb +3 -1
  84. data/lib/arel/visitors/sqlserver.rb +108 -34
  85. data/lib/arel_sqlserver.rb +4 -2
  86. data/test/appveyor/dbsetup.ps1 +4 -4
  87. data/test/cases/adapter_test_sqlserver.rb +246 -171
  88. data/test/cases/change_column_null_test_sqlserver.rb +14 -12
  89. data/test/cases/coerced_tests.rb +722 -381
  90. data/test/cases/column_test_sqlserver.rb +287 -285
  91. data/test/cases/connection_test_sqlserver.rb +17 -20
  92. data/test/cases/execute_procedure_test_sqlserver.rb +20 -20
  93. data/test/cases/fetch_test_sqlserver.rb +16 -22
  94. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
  95. data/test/cases/helper_sqlserver.rb +15 -15
  96. data/test/cases/in_clause_test_sqlserver.rb +36 -0
  97. data/test/cases/index_test_sqlserver.rb +15 -15
  98. data/test/cases/json_test_sqlserver.rb +25 -25
  99. data/test/cases/lateral_test_sqlserver.rb +35 -0
  100. data/test/cases/migration_test_sqlserver.rb +67 -27
  101. data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
  102. data/test/cases/order_test_sqlserver.rb +53 -54
  103. data/test/cases/pessimistic_locking_test_sqlserver.rb +27 -33
  104. data/test/cases/rake_test_sqlserver.rb +33 -45
  105. data/test/cases/schema_dumper_test_sqlserver.rb +115 -109
  106. data/test/cases/schema_test_sqlserver.rb +20 -26
  107. data/test/cases/scratchpad_test_sqlserver.rb +4 -4
  108. data/test/cases/showplan_test_sqlserver.rb +28 -35
  109. data/test/cases/specific_schema_test_sqlserver.rb +68 -65
  110. data/test/cases/transaction_test_sqlserver.rb +18 -20
  111. data/test/cases/trigger_test_sqlserver.rb +14 -13
  112. data/test/cases/utils_test_sqlserver.rb +70 -70
  113. data/test/cases/uuid_test_sqlserver.rb +13 -14
  114. data/test/debug.rb +8 -6
  115. data/test/migrations/create_clients_and_change_column_null.rb +3 -1
  116. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +4 -4
  117. data/test/models/sqlserver/booking.rb +3 -1
  118. data/test/models/sqlserver/customers_view.rb +3 -1
  119. data/test/models/sqlserver/datatype.rb +2 -0
  120. data/test/models/sqlserver/datatype_migration.rb +2 -0
  121. data/test/models/sqlserver/dollar_table_name.rb +3 -1
  122. data/test/models/sqlserver/edge_schema.rb +3 -3
  123. data/test/models/sqlserver/fk_has_fk.rb +3 -1
  124. data/test/models/sqlserver/fk_has_pk.rb +3 -1
  125. data/test/models/sqlserver/natural_pk_data.rb +4 -2
  126. data/test/models/sqlserver/natural_pk_int_data.rb +3 -1
  127. data/test/models/sqlserver/no_pk_data.rb +3 -1
  128. data/test/models/sqlserver/object_default.rb +3 -1
  129. data/test/models/sqlserver/quoted_table.rb +4 -2
  130. data/test/models/sqlserver/quoted_view_1.rb +3 -1
  131. data/test/models/sqlserver/quoted_view_2.rb +3 -1
  132. data/test/models/sqlserver/sst_memory.rb +3 -1
  133. data/test/models/sqlserver/string_default.rb +3 -1
  134. data/test/models/sqlserver/string_defaults_big_view.rb +3 -1
  135. data/test/models/sqlserver/string_defaults_view.rb +3 -1
  136. data/test/models/sqlserver/tinyint_pk.rb +3 -1
  137. data/test/models/sqlserver/trigger.rb +4 -2
  138. data/test/models/sqlserver/trigger_history.rb +3 -1
  139. data/test/models/sqlserver/upper.rb +3 -1
  140. data/test/models/sqlserver/uppered.rb +3 -1
  141. data/test/models/sqlserver/uuid.rb +3 -1
  142. data/test/schema/sqlserver_specific_schema.rb +31 -21
  143. data/test/support/coerceable_test_sqlserver.rb +15 -9
  144. data/test/support/connection_reflection.rb +3 -2
  145. data/test/support/core_ext/query_cache.rb +4 -1
  146. data/test/support/load_schema_sqlserver.rb +5 -5
  147. data/test/support/minitest_sqlserver.rb +3 -1
  148. data/test/support/paths_sqlserver.rb +11 -11
  149. data/test/support/rake_helpers.rb +13 -10
  150. data/test/support/sql_counter_sqlserver.rb +3 -4
  151. data/test/support/test_in_memory_oltp.rb +9 -7
  152. metadata +27 -12
  153. 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
- def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment = nil, sqlserver_options = {})
6
- @sqlserver_options = sqlserver_options || {}
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 'tiny_tds'
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
- require 'active_record/tasks/database_tasks'
2
- require 'shellwords'
3
- require 'ipaddr'
4
- require 'socket'
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['database'], configuration.merge('collation' => default_collation)
22
+ connection.create_database configuration["database"], configuration.merge("collation" => default_collation)
23
23
  establish_connection configuration
24
- rescue ActiveRecord::StatementInvalid => error
25
- if /database .* already exists/i === error.message
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['database']
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['port']
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 'Error dumping database' unless Kernel.system(command.join(' '))
66
+ raise "Error dumping database" unless Kernel.system(command.join(" "))
67
+
67
68
  dump = File.read(filename)
68
- dump.gsub!(/^USE .*$\nGO\n/, '') # Strip db USE statements
69
- dump.gsub!(/^GO\n/, '') # Strip db GO statements
70
- dump.gsub!(/nvarchar\(8000\)/, 'nvarchar(4000)') # Fix nvarchar(8000) column defs
71
- dump.gsub!(/nvarchar\(-1\)/, 'nvarchar(max)') # Fix nvarchar(-1) column defs
72
- dump.gsub!(/text\(\d+\)/, 'text') # Fix text(16) column defs
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['collation'] || DEFAULT_COLLATION
88
+ configuration["collation"] || DEFAULT_COLLATION
89
89
  end
90
90
 
91
91
  def establish_master_connection
92
- establish_connection configuration.merge('database' => 'master')
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('192.168.0.0/16'),
105
- IPAddr.new('10.0.0.0/8'),
106
- IPAddr.new('172.16.0.0/12')
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['host']
117
- Socket::getaddrinfo(configuration['host'], 'echo', Socket::AF_INET)[0][3]
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 +1,3 @@
1
- require 'active_record/connection_adapters/sqlserver_adapter'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/connection_adapters/sqlserver_adapter"
@@ -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('WITH(UPDLOCK)') if o.expr.to_s =~ /FOR UPDATE/
34
- collector << SPACE
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 << SPACE
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 = begin
77
- if o.class.engine.connection.respond_to?(:sqlserver?) && o.class.engine.connection.database_prefix_remote_server?
78
- remote_server_table_name(o)
79
- else
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
- rescue Exception => e
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 << SPACE if o.left
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
- collector << "INNER JOIN "
106
- collector = visit o.left, collector
107
- collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
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
- collector << "LEFT OUTER JOIN "
118
- collector = visit o.left, collector
119
- collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
120
- collector << SPACE
121
- visit o.right, collector
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 << SPACE if options[:space]
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 << SPACE
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 << COMMA unless len == i
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
- @connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
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