activerecord-sqlserver-adapter 3.2.18 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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