activerecord-sqlserver-adapter 3.0.7 → 3.0.8

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,4 +1,9 @@
1
1
 
2
+ * 3.0.8 *
3
+
4
+ * Support for ActiveRecord v3.0.3 and ARel v2.0.7
5
+
6
+
2
7
  * 3.0.7 *
3
8
 
4
9
  * Properly quote table names when reflecting on views.
@@ -1,3 +1,4 @@
1
+ require 'arel/visitors/sqlserver'
1
2
  require 'active_record'
2
3
  require 'active_record/connection_adapters/abstract_adapter'
3
4
  require 'active_record/connection_adapters/sqlserver/core_ext/active_record'
@@ -163,7 +164,7 @@ module ActiveRecord
163
164
  include Sqlserver::Errors
164
165
 
165
166
  ADAPTER_NAME = 'SQLServer'.freeze
166
- VERSION = '3.0.7'.freeze
167
+ VERSION = '3.0.8'.freeze
167
168
  DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+(\d{4})/
168
169
  SUPPORTED_VERSIONS = [2005,2008].freeze
169
170
 
@@ -0,0 +1,342 @@
1
+ module Arel
2
+
3
+ module Nodes
4
+
5
+ # See the SelectManager#lock method on why this custom class is needed.
6
+ class LockWithSQLServer < Arel::Nodes::Unary
7
+ end
8
+
9
+ # In versions of ActiveRecord prior to v3.0.3 limits and offset were always integers via
10
+ # a #to_i. Somewhere between ActiveRecord and ARel this is not happening anymore nor are they
11
+ # in agreement which should be responsible. Since we need to make sure that these are visited
12
+ # correctly and that we can do math with them, these are here to cast to integers.
13
+ class Limit < Arel::Nodes::Unary
14
+ def initialize expr
15
+ @expr = expr.to_i
16
+ end
17
+ end
18
+ class Offset < Arel::Nodes::Unary
19
+ def initialize expr
20
+ @expr = expr.to_i
21
+ end
22
+ end
23
+
24
+ # Extending the Ordering class to be comparrison friendly which allows us to call #uniq on a
25
+ # collection of them. See SelectManager#order for more details.
26
+ class Ordering < Arel::Nodes::Binary
27
+ def hash
28
+ expr.hash
29
+ end
30
+ def ==(other)
31
+ self.class == other.class && self.expr == other.expr
32
+ end
33
+ def eql?(other)
34
+ self == other
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ class SelectManager < Arel::TreeManager
41
+
42
+ alias :lock_without_sqlserver :lock
43
+
44
+ # Getting real Ordering objects is very important for us. We need to be able to call #uniq on
45
+ # a colleciton of them reliably as well as using their true object attributes to mutate them
46
+ # to grouping objects for the inner sql during a select statment with an offset/rownumber. So this
47
+ # is here till ActiveRecord & ARel does this for us instead of using SqlLiteral objects.
48
+ def order(*exprs)
49
+ @ast.orders.concat(exprs.map{ |x|
50
+ case x
51
+ when Arel::Attributes::Attribute
52
+ c = engine.connection
53
+ tn = x.relation.table_alias || x.relation.name
54
+ expr = Arel::Nodes::SqlLiteral.new "#{c.quote_table_name(tn)}.#{c.quote_column_name(x.name)}"
55
+ Arel::Nodes::Ordering.new expr
56
+ when String
57
+ x.split(',').map do |s|
58
+ expr, direction = s.split
59
+ expr = Arel::Nodes::SqlLiteral.new(expr)
60
+ direction = direction =~ /desc/i ? :desc : :asc
61
+ Arel::Nodes::Ordering.new expr, direction
62
+ end
63
+ else
64
+ expr = Arel::Nodes::SqlLiteral.new x.to_s
65
+ Arel::Nodes::Ordering.new expr
66
+ end
67
+ }.flatten)
68
+ self
69
+ end
70
+
71
+ # A friendly over ride that allows us to put a special lock object that can have a default or pass
72
+ # custom string hints down. See the visit_Arel_Nodes_LockWithSQLServer delegation method.
73
+ def lock(locking=true)
74
+ if Arel::Visitors::SQLServer === @visitor
75
+ @ast.lock = Arel::Nodes::LockWithSQLServer.new(locking)
76
+ self
77
+ else
78
+ lock_without_sqlserver(locking)
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ module Visitors
85
+ class SQLServer < Arel::Visitors::ToSql
86
+
87
+ private
88
+
89
+ # SQLServer ToSql/Visitor (Overides)
90
+
91
+ def visit_Arel_Nodes_SelectStatement(o)
92
+ if complex_count_sql?(o)
93
+ visit_Arel_Nodes_SelectStatementForComplexCount(o)
94
+ elsif o.offset
95
+ visit_Arel_Nodes_SelectStatementWithOffset(o)
96
+ else
97
+ visit_Arel_Nodes_SelectStatementWithOutOffset(o)
98
+ end
99
+ end
100
+
101
+ def visit_Arel_Nodes_Offset(o)
102
+ "WHERE [__rnt].[__rn] > #{visit o.expr}"
103
+ end
104
+
105
+ def visit_Arel_Nodes_Limit(o)
106
+ "TOP (#{visit o.expr})"
107
+ end
108
+
109
+ def visit_Arel_Nodes_Lock o
110
+ "WITH(HOLDLOCK, ROWLOCK)"
111
+ end
112
+
113
+ def visit_Arel_Nodes_LockWithSQLServer o
114
+ case o.expr
115
+ when TrueClass
116
+ "WITH(HOLDLOCK, ROWLOCK)"
117
+ when String
118
+ o.expr
119
+ else
120
+ ""
121
+ end
122
+ end
123
+
124
+
125
+ # SQLServer ToSql/Visitor (Additions)
126
+
127
+ def visit_Arel_Nodes_SelectStatementWithOutOffset(o, windowed=false)
128
+ find_and_fix_uncorrelated_joins_in_select_statement(o)
129
+ core = o.cores.first
130
+ projections = core.projections
131
+ groups = core.groups
132
+ orders = o.orders.uniq
133
+ if windowed
134
+ projections = function_select_statement?(o) ? projections : projections.map { |x| projection_without_expression(x) }
135
+ elsif eager_limiting_select_statement?(o)
136
+ groups = projections.map { |x| projection_without_expression(x) }
137
+ projections = projections.map { |x| projection_without_expression(x) }
138
+ orders = orders.map do |x|
139
+ expr = Arel::Nodes::SqlLiteral.new projection_without_expression(x.expr)
140
+ x.descending? ? Arel::Nodes::Max.new([expr]) : Arel::Nodes::Min.new([expr])
141
+ end
142
+ end
143
+ [ ("SELECT" if !windowed),
144
+ (visit(o.limit) if o.limit && !windowed),
145
+ (projections.map{ |x| visit(x) }.join(', ')),
146
+ (source_with_lock_for_select_statement(o)),
147
+ ("WHERE #{core.wheres.map{ |x| visit(x) }.join ' AND ' }" unless core.wheres.empty?),
148
+ ("GROUP BY #{groups.map { |x| visit x }.join ', ' }" unless groups.empty?),
149
+ (visit(core.having) if core.having),
150
+ ("ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}" if !orders.empty? && !windowed)
151
+ ].compact.join ' '
152
+ end
153
+
154
+ def visit_Arel_Nodes_SelectStatementWithOffset(o)
155
+ orders = rowtable_orders(o)
156
+ [ "SELECT",
157
+ (visit(o.limit) if o.limit && !single_distinct_select_statement?(o)),
158
+ (rowtable_projections(o).map{ |x| visit(x) }.join(', ')),
159
+ "FROM (",
160
+ "SELECT ROW_NUMBER() OVER (ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}) AS [__rn],",
161
+ visit_Arel_Nodes_SelectStatementWithOutOffset(o,true),
162
+ ") AS [__rnt]",
163
+ (visit(o.offset) if o.offset),
164
+ ].compact.join ' '
165
+ end
166
+
167
+ def visit_Arel_Nodes_SelectStatementForComplexCount(o)
168
+ core = o.cores.first
169
+ o.limit.expr = o.limit.expr + (o.offset ? o.offset.expr : 0) if o.limit
170
+ orders = rowtable_orders(o)
171
+ [ "SELECT COUNT([count]) AS [count_id]",
172
+ "FROM (",
173
+ "SELECT",
174
+ (visit(o.limit) if o.limit),
175
+ "ROW_NUMBER() OVER (ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}) AS [__rn],",
176
+ "1 AS [count]",
177
+ (source_with_lock_for_select_statement(o)),
178
+ ("WHERE #{core.wheres.map{ |x| visit(x) }.join ' AND ' }" unless core.wheres.empty?),
179
+ ("GROUP BY #{core.groups.map { |x| visit x }.join ', ' }" unless core.groups.empty?),
180
+ (visit(core.having) if core.having),
181
+ ("ORDER BY #{o.orders.map{ |x| visit(x) }.join(', ')}" if !o.orders.empty?),
182
+ ") AS [__rnt]",
183
+ (visit(o.offset) if o.offset)
184
+ ].compact.join ' '
185
+ end
186
+
187
+
188
+ # SQLServer Helpers
189
+
190
+ def source_with_lock_for_select_statement(o)
191
+ # TODO: [ARel 2.2] Use #from/#source vs. #froms
192
+ core = o.cores.first
193
+ source = "FROM #{visit core.froms}" if core.froms
194
+ if source && o.lock
195
+ lock = visit o.lock
196
+ index = source.match(/FROM [\w\[\]\.]+/)[0].length
197
+ source.insert index, " #{lock}"
198
+ else
199
+ source
200
+ end
201
+ end
202
+
203
+ def table_from_select_statement(o)
204
+ core = o.cores.first
205
+ # TODO: [ARel 2.2] Use #from/#source vs. #froms
206
+ # if Arel::Table === core.from
207
+ # core.from
208
+ # elsif Arel::Nodes::SqlLiteral === core.from
209
+ # Arel::Table.new(core.from, @engine)
210
+ # elsif Arel::Nodes::JoinSource === core.source
211
+ # Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left
212
+ # end
213
+ table_finder = lambda { |x|
214
+ case x
215
+ when Arel::Table
216
+ x
217
+ when Arel::Nodes::SqlLiteral
218
+ Arel::Table.new(x, @engine)
219
+ when Arel::Nodes::Join
220
+ table_finder.call(x.left)
221
+ end
222
+ }
223
+ table_finder.call(core.froms)
224
+ end
225
+
226
+ def single_distinct_select_statement?(o)
227
+ projections = o.cores.first.projections
228
+ p1 = projections.first
229
+ projections.size == 1 &&
230
+ ((p1.respond_to?(:distinct) && p1.distinct) ||
231
+ p1.respond_to?(:include?) && p1.include?('DISTINCT'))
232
+ end
233
+
234
+ def all_projections_aliased_in_select_statement?(o)
235
+ projections = o.cores.first.projections
236
+ projections.all? do |x|
237
+ x.split(',').all? { |y| y.include?(' AS ') }
238
+ end
239
+ end
240
+
241
+ def function_select_statement?(o)
242
+ core = o.cores.first
243
+ core.projections.any? { |x| Arel::Nodes::Function === x }
244
+ end
245
+
246
+ def eager_limiting_select_statement?(o)
247
+ core = o.cores.first
248
+ single_distinct_select_statement?(o) && (o.limit && !o.offset) && core.groups.empty?
249
+ end
250
+
251
+ def join_in_select_statement?(o)
252
+ core = o.cores.first
253
+ # TODO: [ARel 2.2] Use #from/#source vs. #froms
254
+ # core.source.right.any? { |x| Arel::Nodes::Join === x }
255
+ Arel::Nodes::Join === core.froms
256
+ end
257
+
258
+ def complex_count_sql?(o)
259
+ core = o.cores.first
260
+ core.projections.size == 1 &&
261
+ Arel::Nodes::Count === core.projections.first &&
262
+ (o.limit || !core.wheres.empty?) &&
263
+ !join_in_select_statement?(o)
264
+ end
265
+
266
+ def find_and_fix_uncorrelated_joins_in_select_statement(o)
267
+ core = o.cores.first
268
+ # TODO: [ARel 2.2] Use #from/#source vs. #froms
269
+ # return if !join_in_select_statement?(o) || core.source.right.size != 2
270
+ # j1 = core.source.right.first
271
+ # j2 = core.source.right.second
272
+ # return unless Arel::Nodes::OuterJoin === j1 && Arel::Nodes::StringJoin === j2
273
+ # j1_tn = j1.left.name
274
+ # j2_tn = j2.left.match(/JOIN \[(.*)\].*ON/).try(:[],1)
275
+ # return unless j1_tn == j2_tn
276
+ # crltd_tn = "#{j1_tn}_crltd"
277
+ # j1.left.table_alias = crltd_tn
278
+ # j1.right.expr.left.relation.table_alias = crltd_tn
279
+ return if !join_in_select_statement?(o) || !(Arel::Nodes::StringJoin === core.froms)
280
+ j1 = core.froms.left
281
+ j2 = core.froms.right
282
+ return unless Arel::Nodes::OuterJoin === j1 && Arel::Nodes::SqlLiteral === j2 && j2.include?('JOIN ')
283
+ j1_tn = j1.right.name
284
+ j2_tn = j2.match(/JOIN \[(.*)\].*ON/).try(:[],1)
285
+ return unless j1_tn == j2_tn
286
+ on_index = j2.index(' ON ')
287
+ j2.insert on_index, " AS [#{j2_tn}_crltd]"
288
+ j2.sub! "[#{j2_tn}].", "[#{j2_tn}_crltd]."
289
+ end
290
+
291
+ def rowtable_projections(o)
292
+ core = o.cores.first
293
+ if single_distinct_select_statement?(o)
294
+ tn = table_from_select_statement(o).name
295
+ core.projections.map do |x|
296
+ x.dup.tap do |p|
297
+ p.sub! 'DISTINCT', "DISTINCT #{visit(o.limit)}".strip if o.limit
298
+ p.sub! /\[#{tn}\]\./, '[__rnt].'
299
+ p.strip!
300
+ end
301
+ end
302
+ elsif join_in_select_statement?(o) && all_projections_aliased_in_select_statement?(o)
303
+ core.projections.map do |x|
304
+ Arel::Nodes::SqlLiteral.new x.split(',').map{ |y| y.split(' AS ').last.strip }.join(', ')
305
+ end
306
+ elsif function_select_statement?(o)
307
+ # TODO: [ARel 2.2] Use Arel.star
308
+ [Arel::Nodes::SqlLiteral.new '*']
309
+ else
310
+ tn = table_from_select_statement(o).name
311
+ core.projections.map { |x| x.gsub /\[#{tn}\]\./, '[__rnt].' }
312
+ end
313
+ end
314
+
315
+ def rowtable_orders(o)
316
+ core = o.cores.first
317
+ if !o.orders.empty?
318
+ o.orders
319
+ elsif join_in_select_statement?(o)
320
+ [table_from_select_statement(o).primary_key.asc]
321
+ else
322
+ [table_from_select_statement(o).primary_key.asc]
323
+ end.uniq
324
+ end
325
+
326
+ # TODO: We use this for grouping too, maybe make Grouping objects vs SqlLiteral.
327
+ def projection_without_expression(projection)
328
+ Arel::Nodes::SqlLiteral.new(projection.split(',').map do |x|
329
+ x.strip!
330
+ x.sub!(/^(COUNT|SUM|MAX|MIN|AVG)\s*(\((.*)\))?/,'\3')
331
+ x.sub!(/^DISTINCT\s*/,'')
332
+ x.sub!(/TOP\s*\(\d+\)\s*/i,'')
333
+ x.strip
334
+ end.join(', '))
335
+ end
336
+
337
+ end
338
+ end
339
+
340
+ end
341
+
342
+ Arel::Visitors::VISITORS['sqlserver'] = Arel::Visitors::SQLServer
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-sqlserver-adapter
3
3
  version: !ruby/object:Gem::Version
4
- hash: 9
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 3
8
8
  - 0
9
- - 7
10
- version: 3.0.7
9
+ - 8
10
+ version: 3.0.8
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ken Collins
@@ -19,7 +19,7 @@ autorequire:
19
19
  bindir: bin
20
20
  cert_chain: []
21
21
 
22
- date: 2011-01-01 00:00:00 -05:00
22
+ date: 2011-01-16 00:00:00 -05:00
23
23
  default_executable:
24
24
  dependencies:
25
25
  - !ruby/object:Gem::Dependency
@@ -30,12 +30,12 @@ dependencies:
30
30
  requirements:
31
31
  - - ~>
32
32
  - !ruby/object:Gem::Version
33
- hash: 7
33
+ hash: 1
34
34
  segments:
35
35
  - 3
36
36
  - 0
37
- - 0
38
- version: 3.0.0
37
+ - 3
38
+ version: 3.0.3
39
39
  type: :runtime
40
40
  version_requirements: *id001
41
41
  - !ruby/object:Gem::Dependency
@@ -46,12 +46,12 @@ dependencies:
46
46
  requirements:
47
47
  - - ~>
48
48
  - !ruby/object:Gem::Version
49
- hash: 23
49
+ hash: 1
50
50
  segments:
51
- - 1
52
- - 0
51
+ - 2
53
52
  - 0
54
- version: 1.0.0
53
+ - 7
54
+ version: 2.0.7
55
55
  type: :runtime
56
56
  version_requirements: *id002
57
57
  description: SQL Server 2005 and 2008 Adapter For ActiveRecord
@@ -76,7 +76,7 @@ files:
76
76
  - lib/active_record/connection_adapters/sqlserver/schema_statements.rb
77
77
  - lib/active_record/connection_adapters/sqlserver_adapter.rb
78
78
  - lib/activerecord-sqlserver-adapter.rb
79
- - lib/arel/engines/sql/compilers/sqlserver_compiler.rb
79
+ - lib/arel/visitors/sqlserver.rb
80
80
  has_rdoc: true
81
81
  homepage: http://github.com/rails-sqlserver/activerecord-sqlserver-adapter
82
82
  licenses: []
@@ -1,267 +0,0 @@
1
- module Arel
2
- class Lock < Compound
3
- def initialize(relation, locked)
4
- super(relation)
5
- @locked = true == locked ? "WITH(HOLDLOCK, ROWLOCK)" : locked
6
- end
7
- end
8
- end
9
-
10
- module Arel
11
- module SqlCompiler
12
- class SQLServerCompiler < GenericCompiler
13
-
14
- def select_sql
15
- if complex_count_sql?
16
- select_sql_with_complex_count
17
- elsif relation.skipped
18
- select_sql_with_skipped
19
- else
20
- select_sql_without_skipped
21
- end
22
- end
23
-
24
- def delete_sql
25
- build_query \
26
- "DELETE #{taken_clause if relation.taken.present?}".strip,
27
- "FROM #{relation.table_sql}",
28
- ("WHERE #{relation.wheres.collect(&:to_sql).join(' AND ')}" unless relation.wheres.blank? )
29
- end
30
-
31
-
32
- protected
33
-
34
- def complex_count_sql?
35
- projections = relation.projections
36
- projections.first.is_a?(Arel::Count) && projections.size == 1 &&
37
- (relation.taken.present? || relation.wheres.present?) && relation.joins(self).blank?
38
- end
39
-
40
- def taken_only?
41
- relation.taken.present? && relation.skipped.blank?
42
- end
43
-
44
- def taken_clause
45
- "TOP (#{relation.taken.to_i}) "
46
- end
47
-
48
- def expression_select?
49
- relation.select_clauses.any? { |sc| sc.match /(COUNT|SUM|MAX|MIN|AVG)\s*\(.*\)/ }
50
- end
51
-
52
- def eager_limiting_select?
53
- single_distinct_select? && taken_only? && relation.group_clauses.blank?
54
- end
55
-
56
- def single_distinct_select?
57
- relation.select_clauses.size == 1 && relation.select_clauses.first.to_s.include?('DISTINCT')
58
- end
59
-
60
- def all_select_clauses_aliased?
61
- relation.select_clauses.all? do |sc|
62
- sc.split(',').all? { |c| c.include?(' AS ') }
63
- end
64
- end
65
-
66
- def select_sql_with_complex_count
67
- joins = correlated_safe_joins
68
- wheres = relation.where_clauses
69
- groups = relation.group_clauses
70
- havings = relation.having_clauses
71
- orders = relation.order_clauses
72
- taken = relation.taken.to_i
73
- skipped = relation.skipped.to_i
74
- top_clause = "TOP (#{taken+skipped}) " if relation.taken.present?
75
- build_query \
76
- "SELECT COUNT([count]) AS [count_id]",
77
- "FROM (",
78
- "SELECT #{top_clause}ROW_NUMBER() OVER (ORDER BY #{unique_orders(rowtable_order_clauses).join(', ')}) AS [__rn],",
79
- "1 AS [count]",
80
- "FROM #{relation.from_clauses}",
81
- (locked unless locked.blank?),
82
- (joins unless joins.blank?),
83
- ("WHERE #{wheres.join(' AND ')}" unless wheres.blank?),
84
- ("GROUP BY #{groups.join(', ')}" unless groups.blank?),
85
- ("HAVING #{havings.join(' AND ')}" unless havings.blank?),
86
- ("ORDER BY #{unique_orders(orders).join(', ')}" unless orders.blank?),
87
- ") AS [__rnt]",
88
- "WHERE [__rnt].[__rn] > #{relation.skipped.to_i}"
89
- end
90
-
91
- def select_sql_without_skipped(windowed=false)
92
- selects = relation.select_clauses
93
- joins = correlated_safe_joins
94
- wheres = relation.where_clauses
95
- groups = relation.group_clauses
96
- havings = relation.having_clauses
97
- orders = relation.order_clauses
98
- if windowed
99
- selects = expression_select? ? selects : selects.map{ |sc| clause_without_expression(sc) }
100
- elsif eager_limiting_select?
101
- groups = selects.map { |sc| clause_without_expression(sc) }
102
- selects = selects.map { |sc| "#{taken_clause}#{clause_without_expression(sc)}" }
103
- orders = orders.map do |oc|
104
- oc.split(',').reject(&:blank?).map do |c|
105
- max = c =~ /desc\s*/i
106
- c = clause_without_expression(c).sub(/(asc|desc)/i,'').strip
107
- max ? "MAX(#{c})" : "MIN(#{c})"
108
- end.join(', ')
109
- end
110
- elsif taken_only?
111
- fsc = "#{taken_clause}#{selects.first}"
112
- selects = selects.tap { |sc| sc.shift ; sc.unshift(fsc) }
113
- end
114
- build_query(
115
- (windowed ? selects.join(', ') : "SELECT #{selects.join(', ')}"),
116
- "FROM #{relation.from_clauses}",
117
- (locked unless locked.blank?),
118
- (joins unless joins.blank?),
119
- ("WHERE #{wheres.join(' AND ')}" unless wheres.blank?),
120
- ("GROUP BY #{groups.join(', ')}" unless groups.blank?),
121
- ("HAVING #{havings.join(' AND ')}" unless havings.blank?),
122
- ("ORDER BY #{unique_orders(orders).join(', ')}" if orders.present? && !windowed))
123
- end
124
-
125
- def select_sql_with_skipped
126
- tc = taken_clause if relation.taken.present? && !single_distinct_select?
127
- build_query \
128
- "SELECT #{tc}#{rowtable_select_clauses.join(', ')}",
129
- "FROM (",
130
- "SELECT ROW_NUMBER() OVER (ORDER BY #{unique_orders(rowtable_order_clauses).join(', ')}) AS [__rn],",
131
- select_sql_without_skipped(true),
132
- ") AS [__rnt]",
133
- "WHERE [__rnt].[__rn] > #{relation.skipped.to_i}"
134
- end
135
-
136
- def rowtable_select_clauses
137
- if single_distinct_select?
138
- ::Array.wrap(relation.select_clauses.first.dup.tap do |sc|
139
- sc.sub! 'DISTINCT', "DISTINCT #{taken_clause if relation.taken.present?}".strip
140
- sc.sub! table_name_from_select_clause(sc), '__rnt'
141
- sc.strip!
142
- end)
143
- elsif relation.join? && all_select_clauses_aliased?
144
- relation.select_clauses.map do |sc|
145
- sc.split(',').map { |c| c.split(' AS ').last.strip }.join(', ')
146
- end
147
- elsif expression_select?
148
- ['*']
149
- else
150
- relation.select_clauses.map do |sc|
151
- sc.gsub /\[#{relation.table.name}\]\./, '[__rnt].'
152
- end
153
- end
154
- end
155
-
156
- def rowtable_order_clauses
157
- orders = relation.order_clauses
158
- if orders.present?
159
- orders
160
- elsif relation.join?
161
- table_names_from_select_clauses.map { |tn| quote("#{tn}.#{pk_for_table(tn)}") }
162
- else
163
- [quote("#{relation.table.name}.#{relation.primary_key}")]
164
- end
165
- end
166
-
167
- def limited_update_conditions(conditions,taken)
168
- quoted_primary_key = engine.connection.quote_column_name(relation.primary_key)
169
- conditions = " #{conditions}".strip
170
- build_query \
171
- "WHERE #{quoted_primary_key} IN",
172
- "(SELECT #{taken_clause if relation.taken.present?}#{quoted_primary_key} FROM #{engine.connection.quote_table_name(relation.table.name)}#{conditions})"
173
- end
174
-
175
- def quote(value)
176
- engine.connection.quote_column_name(value)
177
- end
178
-
179
- def pk_for_table(table_name)
180
- engine.connection.primary_key(table_name)
181
- end
182
-
183
- def clause_without_expression(clause)
184
- clause.to_s.split(',').map do |c|
185
- c.strip!
186
- c.sub!(/^(COUNT|SUM|MAX|MIN|AVG)\s*(\((.*)\))?/,'\3')
187
- c.sub!(/^DISTINCT\s*/,'')
188
- c.sub!(/TOP\s*\(\d+\)\s*/i,'')
189
- c.strip
190
- end.join(', ')
191
- end
192
-
193
- def unqualify_table_name(table_name)
194
- table_name.to_s.split('.').last.tr('[]','')
195
- end
196
-
197
- def table_name_from_select_clause(sc)
198
- parts = clause_without_expression(sc).split('.')
199
- tn = parts.third ? parts.second : (parts.second ? parts.first : nil)
200
- tn ? tn.tr('[]','') : nil
201
- end
202
-
203
- def table_names_from_select_clauses
204
- relation.select_clauses.map do |sc|
205
- sc.split(',').map { table_name_from_select_clause(sc) }
206
- end.flatten.compact.uniq
207
- end
208
-
209
- def unique_orders(orders)
210
- existing_columns = {}
211
- orders.inject([]) do |queued_orders, order|
212
- table_column, dir = clause_without_expression(order).split
213
- table_column = table_column.tr('[]','').split('.')
214
- table, column = table_column.size == 2 ? table_column : table_column.unshift('')
215
- existing_columns[table] ||= []
216
- unless existing_columns[table].include?(column)
217
- existing_columns[table] << column
218
- queued_orders << order
219
- end
220
- queued_orders
221
- end
222
- end
223
-
224
- def correlated_safe_joins
225
- joins = relation.joins(self)
226
- if joins.present?
227
- find_and_fix_uncorrelated_joins
228
- relation.joins(self)
229
- else
230
- joins
231
- end
232
- end
233
-
234
- def find_and_fix_uncorrelated_joins
235
- join_relation = relation
236
- while join_relation.present?
237
- return join_relation if uncorrelated_inner_join_relation?(join_relation)
238
- join_relation = join_relation.relation rescue nil
239
- end
240
- end
241
-
242
- def uncorrelated_inner_join_relation?(r)
243
- if r.is_a?(Arel::StringJoin) && r.relation1.is_a?(Arel::OuterJoin) &&
244
- r.relation2.is_a?(String) && r.relation2.starts_with?('INNER JOIN')
245
- outter_join_table1 = r.relation1.relation1.table
246
- outter_join_table2 = r.relation1.relation2.table
247
- string_join_table_info = r.relation2.split(' ON ').first.sub('INNER JOIN ','')
248
- return nil if string_join_table_info.include?(' AS ') # Assume someone did something right.
249
- string_join_table_name = unqualify_table_name(string_join_table_info)
250
- uncorrelated_table1 = string_join_table_name == outter_join_table1.name && string_join_table_name == outter_join_table1.alias.name
251
- uncorrelated_table2 = string_join_table_name == outter_join_table2.name && string_join_table_name == outter_join_table2.alias.name
252
- if uncorrelated_table1 || uncorrelated_table2
253
- on_index = r.relation2.index(' ON ')
254
- r.relation2.insert on_index, " AS [#{string_join_table_name}_crltd]"
255
- r.relation2.sub! "[#{string_join_table_name}].", "[#{string_join_table_name}_crltd]."
256
- return r
257
- else
258
- return nil
259
- end
260
- end
261
- rescue
262
- nil
263
- end
264
-
265
- end
266
- end
267
- end