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,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.
|
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:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 3
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 3.0.
|
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-
|
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:
|
33
|
+
hash: 1
|
34
34
|
segments:
|
35
35
|
- 3
|
36
36
|
- 0
|
37
|
-
-
|
38
|
-
version: 3.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:
|
49
|
+
hash: 1
|
50
50
|
segments:
|
51
|
-
-
|
52
|
-
- 0
|
51
|
+
- 2
|
53
52
|
- 0
|
54
|
-
|
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/
|
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
|