activerecord-sqlserver-adapter 3.2.18 → 4.0.0

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +4 -28
  3. data/VERSION +1 -1
  4. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -7
  5. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +6 -9
  6. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +3 -25
  7. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +4 -14
  8. data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +1 -3
  9. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +2 -4
  10. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +74 -80
  11. data/lib/active_record/connection_adapters/sqlserver/errors.rb +10 -14
  12. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +24 -15
  13. data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +24 -19
  14. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +28 -0
  15. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +118 -77
  16. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +10 -13
  17. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +8 -11
  18. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +2 -5
  19. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +23 -0
  20. data/lib/active_record/connection_adapters/sqlserver/utils.rb +4 -10
  21. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +121 -247
  22. data/lib/active_record/connection_adapters/sqlserver_column.rb +116 -0
  23. data/lib/active_record/sqlserver_base.rb +28 -0
  24. data/lib/active_record/sqlserver_test_case.rb +17 -0
  25. data/lib/arel/arel_sqlserver.rb +5 -0
  26. data/lib/arel/nodes_sqlserver.rb +14 -0
  27. data/lib/arel/select_manager_sqlserver.rb +62 -0
  28. data/lib/arel/visitors/sqlserver.rb +251 -188
  29. metadata +32 -10
  30. data/lib/active_record/connection_adapters/sqlserver/core_ext/database_statements.rb +0 -97
@@ -0,0 +1,116 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class SQLServerColumn < Column
4
+ def initialize(name, default, sql_type = nil, null = true, sqlserver_options = {})
5
+ @sqlserver_options = sqlserver_options.symbolize_keys
6
+ super(name, default, sql_type, null)
7
+ @primary = @sqlserver_options[:is_identity] || @sqlserver_options[:is_primary]
8
+ end
9
+
10
+ class << self
11
+ def string_to_binary(value)
12
+ "0x#{value.unpack("H*")[0]}"
13
+ end
14
+
15
+ def binary_to_string(value)
16
+ if value.encoding != Encoding::ASCII_8BIT
17
+ value = value.force_encoding(Encoding::ASCII_8BIT)
18
+ end
19
+ value
20
+ end
21
+ end
22
+
23
+ def is_identity?
24
+ @sqlserver_options[:is_identity]
25
+ end
26
+
27
+ def is_primary?
28
+ @sqlserver_options[:is_primary]
29
+ end
30
+
31
+ def is_utf8?
32
+ @sql_type =~ /nvarchar|ntext|nchar/i
33
+ end
34
+
35
+ def is_integer?
36
+ @sql_type =~ /int/i
37
+ end
38
+
39
+ def is_real?
40
+ @sql_type =~ /real/i
41
+ end
42
+
43
+ def sql_type_for_statement
44
+ if is_integer? || is_real?
45
+ sql_type.sub(/\((\d+)?\)/, '')
46
+ else
47
+ sql_type
48
+ end
49
+ end
50
+
51
+ def default_function
52
+ @sqlserver_options[:default_function]
53
+ end
54
+
55
+ def table_name
56
+ @sqlserver_options[:table_name]
57
+ end
58
+
59
+ def table_klass
60
+ @table_klass ||= begin
61
+ table_name.classify.constantize
62
+ rescue StandardError, NameError, LoadError
63
+ nil
64
+ end
65
+ (@table_klass && @table_klass < ActiveRecord::Base) ? @table_klass : nil
66
+ end
67
+
68
+ def database_year
69
+ @sqlserver_options[:database_year]
70
+ end
71
+
72
+ private
73
+
74
+ def extract_limit(sql_type)
75
+ case sql_type
76
+ when /^smallint/i
77
+ 2
78
+ when /^int/i
79
+ 4
80
+ when /^bigint/i
81
+ 8
82
+ when /\(max\)/, /decimal/, /numeric/
83
+ nil
84
+ else
85
+ super
86
+ end
87
+ end
88
+
89
+ def simplified_type(field_type)
90
+ case field_type
91
+ when /real/i then :float
92
+ when /money/i then :decimal
93
+ when /image/i then :binary
94
+ when /bit/i then :boolean
95
+ when /uniqueidentifier/i then :uuid
96
+ when /datetime/i then simplified_datetime
97
+ when /varchar\(max\)/ then :text
98
+ when /timestamp/ then :binary
99
+ else super
100
+ end
101
+ end
102
+
103
+ def simplified_datetime
104
+ if database_year >= 2008
105
+ :datetime
106
+ elsif table_klass && table_klass.coerced_sqlserver_date_columns.include?(name)
107
+ :date
108
+ elsif table_klass && table_klass.coerced_sqlserver_time_columns.include?(name)
109
+ :time
110
+ else
111
+ :datetime
112
+ end
113
+ end
114
+ end # class SQLServerColumn
115
+ end # module ConnectionAdapters
116
+ end # module ActiveRecord
@@ -0,0 +1,28 @@
1
+ module ActiveRecord
2
+ class Base
3
+ def self.sqlserver_connection(config) #:nodoc:
4
+ config = config.symbolize_keys
5
+ config.reverse_merge! mode: :dblib
6
+ mode = config[:mode].to_s.downcase.underscore.to_sym
7
+ case mode
8
+ when :dblib
9
+ require 'tiny_tds'
10
+ when :odbc
11
+ raise ArgumentError, 'Missing :dsn configuration.' unless config.key?(:dsn)
12
+ require 'odbc'
13
+ require 'active_record/connection_adapters/sqlserver/core_ext/odbc'
14
+ else
15
+ raise ArgumentError, "Unknown connection mode in #{config.inspect}."
16
+ end
17
+ ConnectionAdapters::SQLServerAdapter.new(nil, logger, nil, config.merge(mode: mode))
18
+ end
19
+
20
+ def self.did_retry_sqlserver_connection(connection, count)
21
+ logger.info "CONNECTION RETRY: #{connection.class.name} retry ##{count}."
22
+ end
23
+
24
+ def self.did_lose_sqlserver_connection(connection)
25
+ logger.info "CONNECTION LOST: #{connection.class.name}"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ require 'active_record/test_case.rb'
2
+
3
+ # TODO: I'm struggling to figure out how to unsubscribe from only one 'sql.active_record'
4
+ # This is a temporary hack until we can just get the sqlserver_ignored regex in rails
5
+ ActiveSupport::Notifications.notifier.listeners_for('sql.active_record').each do |listener|
6
+ if listener.inspect =~ /ActiveRecord::SQLCounter/
7
+ ActiveSupport::Notifications.unsubscribe(listener)
8
+ end
9
+ end
10
+
11
+ module ActiveRecord
12
+ class SQLCounter
13
+ sqlserver_ignored = [/SELECT SCOPE_IDENTITY/, /INFORMATION_SCHEMA\.(TABLES|VIEWS|COLUMNS)/, /SELECT @@version/, /SELECT @@TRANCOUNT/, /(BEGIN|COMMIT|ROLLBACK|SAVE) TRANSACTION/]
14
+ ignored_sql.concat sqlserver_ignored
15
+ end
16
+ ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
17
+ end
@@ -0,0 +1,5 @@
1
+ require 'arel'
2
+ require 'arel/select_manager_sqlserver'
3
+ require 'arel/nodes_sqlserver'
4
+ require 'arel/visitors/sqlserver'
5
+ require 'arel/visitors/bind_visitor'
@@ -0,0 +1,14 @@
1
+ module Arel
2
+ module Nodes
3
+ # Extending the Ordering class to be comparison friendly which allows us to call #uniq on a
4
+ # collection of them. See SelectManager#order for more details.
5
+ class Ordering < Arel::Nodes::Unary
6
+ def eql?(other)
7
+ # Arel::Nodes::Ascending or Arel::Nodes::Desecnding
8
+ other.is_a?(Arel::Nodes::Ordering) &&
9
+ expr == other.expr
10
+ end
11
+ alias_method :==, :eql?
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,62 @@
1
+ module Arel
2
+ class SelectManager < Arel::TreeManager
3
+ AR_CA_SQLSA_NAME = 'ActiveRecord::ConnectionAdapters::SQLServerAdapter'.freeze
4
+
5
+ # Getting real Ordering objects is very important for us. We need to be able to call #uniq on
6
+ # a colleciton of them reliably as well as using their true object attributes to mutate them
7
+ # to grouping objects for the inner sql during a select statment with an offset/rownumber. So this
8
+ # is here till ActiveRecord & ARel does this for us instead of using SqlLiteral objects.
9
+ alias_method :order_without_sqlserver, :order
10
+ def order(*expr)
11
+ return order_without_sqlserver(*expr) unless engine_activerecord_sqlserver_adapter?
12
+ @ast.orders.concat(expr.map do |x|
13
+ case x
14
+ when Arel::Attributes::Attribute
15
+ table = Arel::Table.new(x.relation.table_alias || x.relation.name)
16
+ e = table[x.name]
17
+ Arel::Nodes::Ascending.new e
18
+ when Arel::Nodes::Ordering
19
+ x
20
+ when String
21
+ x.split(',').map do |s|
22
+ s = x if x.strip =~ /\A\b\w+\b\(.*,.*\)(\s+(ASC|DESC))?\Z/i # Allow functions with comma(s) to pass thru.
23
+ s.strip!
24
+ d = s =~ /(ASC|DESC)\Z/i ? Regexp.last_match[1].upcase : nil
25
+ e = d.nil? ? s : s.mb_chars[0...-d.length].strip
26
+ e = Arel.sql(e)
27
+ d && d == 'DESC' ? Arel::Nodes::Descending.new(e) : Arel::Nodes::Ascending.new(e)
28
+ end
29
+ else
30
+ e = Arel.sql(x.to_s)
31
+ Arel::Nodes::Ascending.new e
32
+ end
33
+ end.flatten)
34
+ self
35
+ end
36
+
37
+ # A friendly over ride that allows us to put a special lock object that can have a default or pass
38
+ # custom string hints down. See the visit_Arel_Nodes_LockWithSQLServer delegation method.
39
+ alias_method :lock_without_sqlserver, :lock
40
+ def lock(locking = true)
41
+ if engine_activerecord_sqlserver_adapter?
42
+ case locking
43
+ when true
44
+ locking = Arel.sql('WITH(HOLDLOCK, ROWLOCK)')
45
+ when Arel::Nodes::SqlLiteral
46
+ when String
47
+ locking = Arel.sql locking
48
+ end
49
+ @ast.lock = Arel::Nodes::Lock.new(locking)
50
+ self
51
+ else
52
+ lock_without_sqlserver(locking)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def engine_activerecord_sqlserver_adapter?
59
+ @engine.connection && @engine.connection.class.name == AR_CA_SQLSA_NAME
60
+ end
61
+ end
62
+ end
@@ -1,214 +1,217 @@
1
- require 'arel'
2
-
3
1
  module Arel
4
-
5
- module Nodes
6
-
7
- # Extending the Ordering class to be comparrison friendly which allows us to call #uniq on a
8
- # collection of them. See SelectManager#order for more details.
9
- class Ordering < Arel::Nodes::Unary
10
- def hash
11
- expr.hash
12
- end
13
- def ==(other)
14
- other.is_a?(Arel::Nodes::Ordering) && self.expr == other.expr
15
- end
16
- def eql?(other)
17
- self == other
18
- end
19
- end
20
-
21
- end
22
-
23
- class SelectManager < Arel::TreeManager
24
-
25
- AR_CA_SQLSA_NAME = 'ActiveRecord::ConnectionAdapters::SQLServerAdapter'.freeze
26
-
27
- # Getting real Ordering objects is very important for us. We need to be able to call #uniq on
28
- # a colleciton of them reliably as well as using their true object attributes to mutate them
29
- # to grouping objects for the inner sql during a select statment with an offset/rownumber. So this
30
- # is here till ActiveRecord & ARel does this for us instead of using SqlLiteral objects.
31
- alias :order_without_sqlserver :order
32
- def order(*expr)
33
- return order_without_sqlserver(*expr) unless engine_activerecord_sqlserver_adapter?
34
- @ast.orders.concat(expr.map{ |x|
35
- case x
36
- when Arel::Attributes::Attribute
37
- table = Arel::Table.new(x.relation.table_alias || x.relation.name)
38
- e = table[x.name]
39
- Arel::Nodes::Ascending.new e
40
- when Arel::Nodes::Ordering
41
- x
42
- when String
43
- x.split(',').map do |s|
44
- s = x if x.strip =~ /\A\b\w+\b\(.*,.*\)(\s+(ASC|DESC))?\Z/i # Allow functions with comma(s) to pass thru.
45
- s.strip!
46
- d = s =~ /(ASC|DESC)\Z/i ? $1.upcase : nil
47
- e = d.nil? ? s : s.mb_chars[0...-d.length].strip
48
- e = Arel.sql(e)
49
- d && d == "DESC" ? Arel::Nodes::Descending.new(e) : Arel::Nodes::Ascending.new(e)
50
- end
51
- else
52
- e = Arel.sql(x.to_s)
53
- Arel::Nodes::Ascending.new e
54
- end
55
- }.flatten)
56
- self
57
- end
58
-
59
- # A friendly over ride that allows us to put a special lock object that can have a default or pass
60
- # custom string hints down. See the visit_Arel_Nodes_LockWithSQLServer delegation method.
61
- alias :lock_without_sqlserver :lock
62
- def lock(locking=true)
63
- if engine_activerecord_sqlserver_adapter?
64
- case locking
65
- when true
66
- locking = Arel.sql('WITH(HOLDLOCK, ROWLOCK)')
67
- when Arel::Nodes::SqlLiteral
68
- when String
69
- locking = Arel.sql locking
70
- end
71
- @ast.lock = Arel::Nodes::Lock.new(locking)
72
- self
73
- else
74
- lock_without_sqlserver(locking)
75
- end
76
- end
77
-
78
- private
79
-
80
- def engine_activerecord_sqlserver_adapter?
81
- @engine.connection && @engine.connection.class.name == AR_CA_SQLSA_NAME
82
- end
83
-
84
- end
85
-
86
2
  module Visitors
87
3
  class SQLServer < Arel::Visitors::ToSql
88
-
89
4
  private
90
5
 
91
6
  # SQLServer ToSql/Visitor (Overides)
92
-
93
- def visit_Arel_Nodes_SelectStatement(o)
7
+ def visit_Arel_Nodes_SelectStatement(o, a)
94
8
  if complex_count_sql?(o)
95
- visit_Arel_Nodes_SelectStatementForComplexCount(o)
9
+ visit_Arel_Nodes_SelectStatementForComplexCount(o, a)
10
+ elsif distinct_non_present_orders?(o, a)
11
+ visit_Arel_Nodes_SelectStatementDistinctNonPresentOrders(o, a)
96
12
  elsif o.offset
97
- visit_Arel_Nodes_SelectStatementWithOffset(o)
13
+ visit_Arel_Nodes_SelectStatementWithOffset(o, a)
98
14
  else
99
- visit_Arel_Nodes_SelectStatementWithOutOffset(o)
15
+ visit_Arel_Nodes_SelectStatementWithOutOffset(o, a)
100
16
  end
101
17
  end
102
-
103
- def visit_Arel_Nodes_UpdateStatement(o)
18
+
19
+ def visit_Arel_Nodes_UpdateStatement(o, a)
104
20
  if o.orders.any? && o.limit.nil?
105
- o.limit = Nodes::Limit.new(9223372036854775807)
21
+ o.limit = Nodes::Limit.new(9_223_372_036_854_775_807)
106
22
  end
107
23
  super
108
24
  end
109
25
 
110
- def visit_Arel_Nodes_Offset(o)
111
- "WHERE [__rnt].[__rn] > (#{visit o.expr})"
26
+ def visit_Arel_Nodes_Offset(o, a)
27
+ "WHERE [__rnt].[__rn] > (#{visit o.expr, a})"
112
28
  end
113
29
 
114
- def visit_Arel_Nodes_Limit(o)
115
- "TOP (#{visit o.expr})"
30
+ def visit_Arel_Nodes_Limit(o, a)
31
+ "TOP (#{visit o.expr, a})"
116
32
  end
117
33
 
118
- def visit_Arel_Nodes_Lock(o)
119
- visit o.expr
34
+ def visit_Arel_Nodes_Lock(o, a)
35
+ visit o.expr, a
120
36
  end
121
-
122
- def visit_Arel_Nodes_Ordering(o)
37
+
38
+ def visit_Arel_Nodes_Ordering(o, a)
123
39
  if o.respond_to?(:direction)
124
- "#{visit o.expr} #{o.ascending? ? 'ASC' : 'DESC'}"
40
+ "#{visit o.expr, a} #{o.ascending? ? 'ASC' : 'DESC'}"
125
41
  else
126
- visit o.expr
42
+ visit o.expr, a
127
43
  end
128
44
  end
129
-
130
- def visit_Arel_Nodes_Bin(o)
131
- "#{visit o.expr} #{@connection.cs_equality_operator}"
45
+
46
+ def visit_Arel_Nodes_Bin(o, a)
47
+ "#{visit o.expr, a} #{@connection.cs_equality_operator}"
132
48
  end
133
49
 
134
50
  # SQLServer ToSql/Visitor (Additions)
135
51
 
136
- def visit_Arel_Nodes_SelectStatementWithOutOffset(o, windowed=false)
52
+ # This constructs a query using DENSE_RANK() and ROW_NUMBER() to allow
53
+ # ordering a DISTINCT set of data by columns in another table that are
54
+ # not part of what we actually want to be DISTINCT. Without this, it is
55
+ # possible for the DISTINCT qualifier combined with TOP to return fewer
56
+ # rows than were requested.
57
+ def visit_Arel_Nodes_SelectStatementDistinctNonPresentOrders(o, a)
58
+ core = o.cores.first
59
+ projections = core.projections
60
+ groups = core.groups
61
+ orders = o.orders.uniq
62
+
63
+ select_frags = projections.map do |x|
64
+ frag = projection_to_sql_remove_distinct(x, core, a)
65
+ # Remove the table specifier
66
+ frag.gsub!(/^[^\.]*\./, '')
67
+ # If there is an alias, remove everything but
68
+ frag.gsub(/^.*\sAS\s+/i, '')
69
+ end
70
+
71
+ if o.offset
72
+ select_frags << 'ROW_NUMBER() OVER (ORDER BY __order) AS __offset'
73
+ else
74
+ select_frags << '__order'
75
+ end
76
+
77
+ projection_list = projections.map { |x| projection_to_sql_remove_distinct(x, core, a) }.join(', ')
78
+
79
+ sql = [
80
+ ('SELECT'),
81
+ (visit(core.set_quantifier, a) if core.set_quantifier && !o.offset),
82
+ (visit(o.limit, a) if o.limit && !o.offset),
83
+ (select_frags.join(', ')),
84
+ ('FROM ('),
85
+ ('SELECT'),
86
+ (
87
+ [
88
+ (projection_list),
89
+ (', DENSE_RANK() OVER ('),
90
+ ("ORDER BY #{orders.map { |x| visit(x, a) }.join(', ')}" unless orders.empty?),
91
+ (') AS __order'),
92
+ (', ROW_NUMBER() OVER ('),
93
+ ("PARTITION BY #{projection_list}" if !orders.empty?),
94
+ (" ORDER BY #{orders.map { |x| visit(x, a) }.join(', ')}" unless orders.empty?),
95
+ (') AS __joined_row_num')
96
+ ].join('')
97
+ ),
98
+ (source_with_lock_for_select_statement(o, a)),
99
+ ("WHERE #{core.wheres.map { |x| visit(x, a) }.join ' AND ' }" unless core.wheres.empty?),
100
+ ("GROUP BY #{groups.map { |x| visit(x, a) }.join ', ' }" unless groups.empty?),
101
+ (visit(core.having, a) if core.having),
102
+ (') AS __sq'),
103
+ ('WHERE __joined_row_num = 1'),
104
+ ('ORDER BY __order' unless o.offset)
105
+ ].compact.join(' ')
106
+
107
+ if o.offset
108
+ sql = [
109
+ ('SELECT'),
110
+ (visit(core.set_quantifier, a) if core.set_quantifier),
111
+ (visit(o.limit, a) if o.limit),
112
+ ('*'),
113
+ ('FROM (' + sql + ') AS __osq'),
114
+ ("WHERE __offset > #{visit(o.offset.expr, a)}"),
115
+ ('ORDER BY __offset')
116
+ ].join(' ')
117
+ end
118
+
119
+ sql
120
+ end
121
+
122
+ def visit_Arel_Nodes_SelectStatementWithOutOffset(o, a, windowed = false)
137
123
  find_and_fix_uncorrelated_joins_in_select_statement(o)
138
124
  core = o.cores.first
139
125
  projections = core.projections
140
126
  groups = core.groups
141
127
  orders = o.orders.uniq
142
128
  if windowed
143
- projections = function_select_statement?(o) ? projections : projections.map { |x| projection_without_expression(x) }
144
- groups = projections.map { |x| projection_without_expression(x) } if windowed_single_distinct_select_statement?(o) && groups.empty?
129
+ projections = function_select_statement?(o) ? projections : projections.map { |x| projection_without_expression(x, a) }
130
+ groups = projections.map { |x| projection_without_expression(x, a) } if windowed_single_distinct_select_statement?(o) && groups.empty?
145
131
  groups += orders.map { |x| Arel.sql(x.expr) } if windowed_single_distinct_select_statement?(o)
146
- elsif eager_limiting_select_statement?(o)
147
- projections = projections.map { |x| projection_without_expression(x) }
148
- groups = projections.map { |x| projection_without_expression(x) }
132
+ elsif eager_limiting_select_statement?(o, a)
133
+ projections = projections.map { |x| projection_without_expression(x, a) }
134
+ groups = projections.map { |x| projection_without_expression(x, a) }
149
135
  orders = orders.map do |x|
150
- expr = Arel.sql projection_without_expression(x.expr)
136
+ expr = Arel.sql projection_without_expression(x.expr, a)
151
137
  x.descending? ? Arel::Nodes::Max.new([expr]) : Arel::Nodes::Min.new([expr])
152
138
  end
153
- elsif top_one_everything_for_through_join?(o)
154
- projections = projections.map { |x| projection_without_expression(x) }
139
+ elsif top_one_everything_for_through_join?(o, a)
140
+ projections = projections.map { |x| projection_without_expression(x, a) }
155
141
  end
156
- [ ("SELECT" if !windowed),
157
- (visit(core.set_quantifier) if core.set_quantifier && !windowed),
158
- (visit(o.limit) if o.limit && !windowed),
159
- (projections.map{ |x| v = visit(x); v == "1" ? "1 AS [__wrp]" : v }.join(', ')),
160
- (source_with_lock_for_select_statement(o)),
161
- ("WHERE #{core.wheres.map{ |x| visit(x) }.join ' AND ' }" unless core.wheres.empty?),
162
- ("GROUP BY #{groups.map { |x| visit x }.join ', ' }" unless groups.empty?),
163
- (visit(core.having) if core.having),
164
- ("ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}" if !orders.empty? && !windowed)
142
+ [
143
+ ('SELECT' unless windowed),
144
+ (visit(core.set_quantifier, a) if core.set_quantifier && !windowed),
145
+ (visit(o.limit, a) if o.limit && !windowed),
146
+ (projections.map do |x|
147
+ v = visit(x, a)
148
+ v == '1' ? '1 AS [__wrp]' : v
149
+ end.join(', ')),
150
+ (source_with_lock_for_select_statement(o, a)),
151
+ ("WHERE #{core.wheres.map { |x| visit(x, a) }.join ' AND ' }" unless core.wheres.empty?),
152
+ ("GROUP BY #{groups.map { |x| visit(x, a) }.join ', ' }" unless groups.empty?),
153
+ (visit(core.having, a) if core.having),
154
+ ("ORDER BY #{orders.map { |x| visit(x, a) }.join(', ')}" if !orders.empty? && !windowed)
165
155
  ].compact.join ' '
166
156
  end
167
157
 
168
- def visit_Arel_Nodes_SelectStatementWithOffset(o)
158
+ def visit_Arel_Nodes_SelectStatementWithOffset(o, a)
169
159
  core = o.cores.first
170
- o.limit ||= Arel::Nodes::Limit.new(9223372036854775807)
160
+ o.limit ||= Arel::Nodes::Limit.new(9_223_372_036_854_775_807)
171
161
  orders = rowtable_orders(o)
172
- [ "SELECT",
173
- (visit(o.limit) if o.limit && !windowed_single_distinct_select_statement?(o)),
174
- (rowtable_projections(o).map{ |x| visit(x) }.join(', ')),
175
- "FROM (",
176
- "SELECT #{core.set_quantifier ? 'DISTINCT DENSE_RANK()' : 'ROW_NUMBER()'} OVER (ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}) AS [__rn],",
177
- visit_Arel_Nodes_SelectStatementWithOutOffset(o,true),
178
- ") AS [__rnt]",
179
- (visit(o.offset) if o.offset),
180
- "ORDER BY [__rnt].[__rn] ASC"
162
+ [
163
+ 'SELECT',
164
+ (visit(o.limit, a) if o.limit && !windowed_single_distinct_select_statement?(o)),
165
+ (rowtable_projections(o, a).map { |x| visit(x, a) }.join(', ')),
166
+ 'FROM (',
167
+ "SELECT #{core.set_quantifier ? 'DISTINCT DENSE_RANK()' : 'ROW_NUMBER()'} OVER (ORDER BY #{orders.map { |x| visit(x, a) }.join(', ')}) AS [__rn],",
168
+ visit_Arel_Nodes_SelectStatementWithOutOffset(o, a, true),
169
+ ') AS [__rnt]',
170
+ (visit(o.offset, a) if o.offset),
171
+ 'ORDER BY [__rnt].[__rn] ASC'
181
172
  ].compact.join ' '
182
173
  end
183
174
 
184
- def visit_Arel_Nodes_SelectStatementForComplexCount(o)
175
+ def visit_Arel_Nodes_SelectStatementForComplexCount(o, a)
185
176
  core = o.cores.first
186
177
  o.limit.expr = Arel.sql("#{o.limit.expr} + #{o.offset ? o.offset.expr : 0}") if o.limit
187
178
  orders = rowtable_orders(o)
188
- [ "SELECT COUNT([count]) AS [count_id]",
189
- "FROM (",
190
- "SELECT",
191
- (visit(o.limit) if o.limit),
192
- "ROW_NUMBER() OVER (ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}) AS [__rn],",
193
- "1 AS [count]",
194
- (source_with_lock_for_select_statement(o)),
195
- ("WHERE #{core.wheres.map{ |x| visit(x) }.join ' AND ' }" unless core.wheres.empty?),
196
- ("GROUP BY #{core.groups.map { |x| visit x }.join ', ' }" unless core.groups.empty?),
197
- (visit(core.having) if core.having),
198
- ("ORDER BY #{o.orders.map{ |x| visit(x) }.join(', ')}" if !o.orders.empty?),
199
- ") AS [__rnt]",
200
- (visit(o.offset) if o.offset)
179
+ [
180
+ 'SELECT COUNT([count]) AS [count_id]',
181
+ 'FROM (',
182
+ 'SELECT',
183
+ (visit(o.limit, a) if o.limit),
184
+ "ROW_NUMBER() OVER (ORDER BY #{orders.map { |x| visit(x, a) }.join(', ')}) AS [__rn],",
185
+ '1 AS [count]',
186
+ (source_with_lock_for_select_statement(o, a)),
187
+ ("WHERE #{core.wheres.map { |x| visit(x, a) }.join ' AND ' }" unless core.wheres.empty?),
188
+ ("GROUP BY #{core.groups.map { |x| visit(x, a) }.join ', ' }" unless core.groups.empty?),
189
+ (visit(core.having, a) if core.having),
190
+ ("ORDER BY #{o.orders.map { |x| visit(x, a) }.join(', ')}" unless o.orders.empty?),
191
+ ') AS [__rnt]',
192
+ (visit(o.offset, a) if o.offset)
201
193
  ].compact.join ' '
202
194
  end
203
195
 
204
-
205
196
  # SQLServer Helpers
206
197
 
207
- def source_with_lock_for_select_statement(o)
198
+ def projection_to_sql_remove_distinct(x, core, a)
199
+ frag = Arel.sql(visit(x, a))
200
+ # In Rails 4.0.0, DISTINCT was in a projection, whereas with 4.0.1
201
+ # it is now stored in the set_quantifier. This moves it to the correct
202
+ # place so the code works on both 4.0.0 and 4.0.1.
203
+ if frag =~ /^\s*DISTINCT\s+/i
204
+ core.set_quantifier = Arel::Nodes::Distinct.new
205
+ frag.gsub!(/\s*DISTINCT\s+/, '')
206
+ end
207
+ frag
208
+ end
209
+
210
+ def source_with_lock_for_select_statement(o, a)
208
211
  core = o.cores.first
209
- source = "FROM #{visit(core.source).strip}" if core.source
212
+ source = "FROM #{visit(core.source, a).strip}" if core.source
210
213
  if source && o.lock
211
- lock = visit o.lock
214
+ lock = visit o.lock, a
212
215
  index = source.match(/FROM [\w\[\]\.]+/)[0].mb_chars.length
213
216
  source.insert index, " #{lock}"
214
217
  else
@@ -226,7 +229,7 @@ module Arel
226
229
  # elsif Arel::Nodes::JoinSource === core.source
227
230
  # Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left
228
231
  # end
229
- table_finder = lambda { |x|
232
+ table_finder = lambda do |x|
230
233
  case x
231
234
  when Arel::Table
232
235
  x
@@ -235,7 +238,7 @@ module Arel
235
238
  when Arel::Nodes::Join
236
239
  table_finder.call(x.left)
237
240
  end
238
- }
241
+ end
239
242
  table_finder.call(core.froms)
240
243
  end
241
244
 
@@ -246,25 +249,88 @@ module Arel
246
249
  ((p1.respond_to?(:distinct) && p1.distinct) ||
247
250
  p1.respond_to?(:include?) && p1.include?('DISTINCT'))
248
251
  end
249
-
252
+
253
+ # Determine if the SELECT statement is asking for DISTINCT results,
254
+ # but is using columns not part of the SELECT list in the ORDER BY.
255
+ # This is necessary because SQL Server requires all ORDER BY entries
256
+ # be in the SELECT list with DISTINCT. However, these ordering columns
257
+ # can cause duplicate rows, which affect when using a limit.
258
+ def distinct_non_present_orders?(o, a)
259
+ projections = o.cores.first.projections
260
+
261
+ sq = o.cores.first.set_quantifier
262
+ p1 = projections.first
263
+
264
+ found_distinct = sq && sq.class.to_s =~ /Distinct/
265
+ if (p1.respond_to?(:distinct) && p1.distinct) || (p1.respond_to?(:include?) && p1.include?('DISTINCT'))
266
+ found_distinct = true
267
+ end
268
+
269
+ return false if !found_distinct || o.orders.uniq.empty?
270
+
271
+ tables_all_columns = []
272
+ expressions = projections.map do |p|
273
+ visit(p, a).split(',').map do |x|
274
+ x.strip!
275
+ # Rails 4.0.0 included DISTINCT in the first projection
276
+ x.gsub!(/\s*DISTINCT\s+/, '')
277
+ # Aliased column names
278
+ x.gsub!(/\s+AS\s+\w+/i, '')
279
+ # Identifier quoting
280
+ x.gsub!(/\[|\]/, '')
281
+ star_match = /^(\w+)\.\*$/.match(x)
282
+ tables_all_columns << star_match[1] if star_match
283
+ x.strip.downcase
284
+ end.join(', ')
285
+ end
286
+
287
+ # Make sure each order by is in the select list, otherwise there needs
288
+ # to be a subquery with row_numbe()
289
+ o.orders.uniq.each do |order|
290
+ order = visit(order, a)
291
+ order.strip!
292
+
293
+ order.gsub!(/\s+(asc|desc)/i, '')
294
+ # Identifier quoting
295
+ order.gsub!(/\[|\]/, '')
296
+
297
+ order.strip!
298
+ order.downcase!
299
+
300
+ # If we selected all columns from a table, the order is ok
301
+ table_match = /^(\w+)\.\w+$/.match(order)
302
+ next if table_match && tables_all_columns.include?(table_match[1])
303
+
304
+ next if expressions.include?(order)
305
+
306
+ return true
307
+ end
308
+
309
+ # We didn't find anything in the order by no being selected
310
+ false
311
+ end
312
+
250
313
  def windowed_single_distinct_select_statement?(o)
251
- o.limit && o.offset && single_distinct_select_statement?(o)
314
+ o.limit &&
315
+ o.offset &&
316
+ single_distinct_select_statement?(o)
252
317
  end
253
-
254
- def single_distinct_select_everything_statement?(o)
255
- single_distinct_select_statement?(o) && visit(o.cores.first.projections.first).ends_with?(".*")
318
+
319
+ def single_distinct_select_everything_statement?(o, a)
320
+ single_distinct_select_statement?(o) &&
321
+ visit(o.cores.first.projections.first, a).ends_with?('.*')
256
322
  end
257
-
258
- def top_one_everything_for_through_join?(o)
259
- single_distinct_select_everything_statement?(o) &&
260
- (o.limit && !o.offset) &&
323
+
324
+ def top_one_everything_for_through_join?(o, a)
325
+ single_distinct_select_everything_statement?(o, a) &&
326
+ (o.limit && !o.offset) &&
261
327
  join_in_select_statement?(o)
262
328
  end
263
329
 
264
- def all_projections_aliased_in_select_statement?(o)
330
+ def all_projections_aliased_in_select_statement?(o, a)
265
331
  projections = o.cores.first.projections
266
332
  projections.all? do |x|
267
- visit(x).split(',').all? { |y| y.include?(' AS ') }
333
+ visit(x, a).split(',').all? { |y| y.include?(' AS ') }
268
334
  end
269
335
  end
270
336
 
@@ -273,12 +339,12 @@ module Arel
273
339
  core.projections.any? { |x| Arel::Nodes::Function === x }
274
340
  end
275
341
 
276
- def eager_limiting_select_statement?(o)
342
+ def eager_limiting_select_statement?(o, a)
277
343
  core = o.cores.first
278
- single_distinct_select_statement?(o) &&
279
- (o.limit && !o.offset) &&
280
- core.groups.empty? &&
281
- !single_distinct_select_everything_statement?(o)
344
+ single_distinct_select_statement?(o) &&
345
+ (o.limit && !o.offset) &&
346
+ core.groups.empty? &&
347
+ !single_distinct_select_everything_statement?(o, a)
282
348
  end
283
349
 
284
350
  def join_in_select_statement?(o)
@@ -293,7 +359,7 @@ module Arel
293
359
  o.limit &&
294
360
  !join_in_select_statement?(o)
295
361
  end
296
-
362
+
297
363
  def select_primary_key_sql?(o)
298
364
  core = o.cores.first
299
365
  return false if core.projections.size != 1
@@ -320,22 +386,22 @@ module Arel
320
386
  j2 = core.froms.right
321
387
  return unless Arel::Nodes::OuterJoin === j1 && Arel::Nodes::SqlLiteral === j2 && j2.include?('JOIN ')
322
388
  j1_tn = j1.right.name
323
- j2_tn = j2.match(/JOIN \[(.*)\].*ON/).try(:[],1)
389
+ j2_tn = j2.match(/JOIN \[(.*)\].*ON/).try(:[], 1)
324
390
  return unless j1_tn == j2_tn
325
391
  on_index = j2.index(' ON ')
326
392
  j2.insert on_index, " AS [#{j2_tn}_crltd]"
327
393
  j2.sub! "[#{j2_tn}].", "[#{j2_tn}_crltd]."
328
394
  end
329
395
 
330
- def rowtable_projections(o)
396
+ def rowtable_projections(o, a)
331
397
  core = o.cores.first
332
398
  if windowed_single_distinct_select_statement?(o) && core.groups.blank?
333
399
  tn = table_from_select_statement(o).name
334
400
  core.projections.map do |x|
335
401
  x.dup.tap do |p|
336
402
  p.sub! 'DISTINCT', ''
337
- p.insert 0, visit(o.limit) if o.limit
338
- p.gsub! /\[?#{tn}\]?\./, '[__rnt].'
403
+ p.insert 0, visit(o.limit, a) if o.limit
404
+ p.gsub!(/\[?#{tn}\]?\./, '[__rnt].')
339
405
  p.strip!
340
406
  end
341
407
  end
@@ -343,14 +409,14 @@ module Arel
343
409
  tn = table_from_select_statement(o).name
344
410
  core.projections.map do |x|
345
411
  x.dup.tap do |p|
346
- p.sub! 'DISTINCT', "DISTINCT #{visit(o.limit)}".strip if o.limit
347
- p.gsub! /\[?#{tn}\]?\./, '[__rnt].'
412
+ p.sub! 'DISTINCT', "DISTINCT #{visit(o.limit, a)}".strip if o.limit
413
+ p.gsub!(/\[?#{tn}\]?\./, '[__rnt].')
348
414
  p.strip!
349
415
  end
350
416
  end
351
- elsif join_in_select_statement?(o) && all_projections_aliased_in_select_statement?(o)
417
+ elsif join_in_select_statement?(o) && all_projections_aliased_in_select_statement?(o, a)
352
418
  core.projections.map do |x|
353
- Arel.sql visit(x).split(',').map{ |y| y.split(' AS ').last.strip }.join(', ')
419
+ Arel.sql visit(x, a).split(',').map { |y| y.split(' AS ').last.strip }.join(', ')
354
420
  end
355
421
  elsif select_primary_key_sql?(o)
356
422
  [Arel.sql("[__rnt].#{quote_column_name(core.projections.first.name)}")]
@@ -360,7 +426,6 @@ module Arel
360
426
  end
361
427
 
362
428
  def rowtable_orders(o)
363
- core = o.cores.first
364
429
  if !o.orders.empty?
365
430
  o.orders
366
431
  else
@@ -371,19 +436,17 @@ module Arel
371
436
  end
372
437
 
373
438
  # TODO: We use this for grouping too, maybe make Grouping objects vs SqlLiteral.
374
- def projection_without_expression(projection)
375
- Arel.sql(visit(projection).split(',').map do |x|
439
+ def projection_without_expression(projection, a)
440
+ Arel.sql(visit(projection, a).split(',').map do |x|
376
441
  x.strip!
377
- x.sub!(/^(COUNT|SUM|MAX|MIN|AVG)\s*(\((.*)\))?/,'\3')
378
- x.sub!(/^DISTINCT\s*/,'')
379
- x.sub!(/TOP\s*\(\d+\)\s*/i,'')
442
+ x.sub!(/^(COUNT|SUM|MAX|MIN|AVG)\s*(\((.*)\))?/, '\3')
443
+ x.sub!(/^DISTINCT\s*/, '')
444
+ x.sub!(/TOP\s*\(\d+\)\s*/i, '')
380
445
  x.strip
381
446
  end.join(', '))
382
447
  end
383
-
384
448
  end
385
449
  end
386
-
387
450
  end
388
451
 
389
452
  Arel::Visitors::VISITORS['sqlserver'] = Arel::Visitors::SQLServer