activerecord-sqlserver-adapter 3.0.7 → 3.0.8

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.
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