activerecord-sqlserver-adapter-2000 3.0.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ require 'active_record/connection_adapters/sqlserver_adapter'
@@ -0,0 +1,330 @@
1
+ require 'arel'
2
+
3
+ module Arel
4
+
5
+ module Nodes
6
+
7
+ # See the SelectManager#lock method on why this custom class is needed.
8
+ class LockWithSQLServer < Arel::Nodes::Unary
9
+ end
10
+
11
+ # Extending the Ordering class to be comparrison friendly which allows us to call #uniq on a
12
+ # collection of them. See SelectManager#order for more details.
13
+ class Ordering < Arel::Nodes::Binary
14
+ def hash
15
+ expr.hash
16
+ end
17
+ def ==(other)
18
+ self.class == other.class && self.expr == other.expr
19
+ end
20
+ def eql?(other)
21
+ self == other
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ class SelectManager < Arel::TreeManager
28
+
29
+ alias :lock_without_sqlserver :lock
30
+
31
+ # Getting real Ordering objects is very important for us. We need to be able to call #uniq on
32
+ # a colleciton of them reliably as well as using their true object attributes to mutate them
33
+ # to grouping objects for the inner sql during a select statment with an offset/rownumber. So this
34
+ # is here till ActiveRecord & ARel does this for us instead of using SqlLiteral objects.
35
+ alias :order_without_sqlserver :order
36
+ def order(*exprs)
37
+ return order_without_sqlserver(*exprs) unless Arel::Visitors::SQLServer === @visitor
38
+ @ast.orders.concat(exprs.map{ |x|
39
+ case x
40
+ when Arel::Attributes::Attribute
41
+ table = Arel::Table.new(x.relation.table_alias || x.relation.name)
42
+ expr = table[x.name]
43
+ Arel::Nodes::Ordering.new expr
44
+ when String
45
+ x.split(',').map do |s|
46
+ expr, direction = s.split
47
+ expr = Arel.sql(expr)
48
+ direction = direction =~ /desc/i ? :desc : :asc
49
+ Arel::Nodes::Ordering.new expr, direction
50
+ end
51
+ else
52
+ expr = Arel.sql(x.to_s)
53
+ Arel::Nodes::Ordering.new expr
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
+ def lock(locking=true)
62
+ if Arel::Visitors::SQLServer === @visitor
63
+ @ast.lock = Arel::Nodes::LockWithSQLServer.new(locking)
64
+ self
65
+ else
66
+ lock_without_sqlserver(locking)
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ module Visitors
73
+ class SQLServer < Arel::Visitors::ToSql
74
+
75
+ private
76
+
77
+ # SQLServer ToSql/Visitor (Overides)
78
+
79
+ def visit_Arel_Nodes_SelectStatement(o)
80
+ if complex_count_sql?(o)
81
+ visit_Arel_Nodes_SelectStatementForComplexCount(o)
82
+ elsif o.offset
83
+ visit_Arel_Nodes_SelectStatementWithOffset(o)
84
+ else
85
+ visit_Arel_Nodes_SelectStatementWithOutOffset(o)
86
+ end
87
+ end
88
+
89
+ def visit_Arel_Nodes_Offset(o)
90
+ "WHERE [__rnt].[__rn] > (#{visit o.expr})"
91
+ end
92
+
93
+ def visit_Arel_Nodes_Limit(o)
94
+ "TOP (#{visit o.expr})"
95
+ end
96
+
97
+ def visit_Arel_Nodes_Lock o
98
+ "WITH(HOLDLOCK, ROWLOCK)"
99
+ end
100
+
101
+ def visit_Arel_Nodes_LockWithSQLServer o
102
+ case o.expr
103
+ when TrueClass
104
+ "WITH(HOLDLOCK, ROWLOCK)"
105
+ when String
106
+ o.expr
107
+ else
108
+ ""
109
+ end
110
+ end
111
+
112
+
113
+ # SQLServer ToSql/Visitor (Additions)
114
+
115
+ def visit_Arel_Nodes_SelectStatementWithOutOffset(o, windowed=false)
116
+ find_and_fix_uncorrelated_joins_in_select_statement(o)
117
+ core = o.cores.first
118
+ projections = core.projections
119
+ groups = core.groups
120
+ orders = o.orders.uniq
121
+ if windowed
122
+ projections = function_select_statement?(o) ? projections : projections.map { |x| projection_without_expression(x) }
123
+ elsif eager_limiting_select_statement?(o)
124
+ groups = projections.map { |x| projection_without_expression(x) }
125
+ projections = projections.map { |x| projection_without_expression(x) }
126
+ orders = orders.map do |x|
127
+ expr = Arel.sql projection_without_expression(x.expr)
128
+ x.descending? ? Arel::Nodes::Max.new([expr]) : Arel::Nodes::Min.new([expr])
129
+ end
130
+ end
131
+ [ ("SELECT" if !windowed),
132
+ (visit(o.limit) if o.limit && !windowed),
133
+ (projections.map{ |x| visit(x) }.join(', ')),
134
+ (source_with_lock_for_select_statement(o)),
135
+ ("WHERE #{core.wheres.map{ |x| visit(x) }.join ' AND ' }" unless core.wheres.empty?),
136
+ ("GROUP BY #{groups.map { |x| visit x }.join ', ' }" unless groups.empty?),
137
+ (visit(core.having) if core.having),
138
+ ("ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}" if !orders.empty? && !windowed)
139
+ ].compact.join ' '
140
+ end
141
+
142
+ def visit_Arel_Nodes_SelectStatementWithOffset(o)
143
+ orders = rowtable_orders(o)
144
+ [ "SELECT",
145
+ (visit(o.limit) if o.limit && !single_distinct_select_statement?(o)),
146
+ (rowtable_projections(o).map{ |x| visit(x) }.join(', ')),
147
+ "FROM (",
148
+ "SELECT ROW_NUMBER() OVER (ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}) AS [__rn],",
149
+ visit_Arel_Nodes_SelectStatementWithOutOffset(o,true),
150
+ ") AS [__rnt]",
151
+ (visit(o.offset) if o.offset),
152
+ ].compact.join ' '
153
+ end
154
+
155
+ def visit_Arel_Nodes_SelectStatementForComplexCount(o)
156
+ core = o.cores.first
157
+ o.limit.expr = Arel.sql("#{o.limit.expr} + #{o.offset ? o.offset.expr : 0}") if o.limit
158
+ orders = rowtable_orders(o)
159
+ [ "SELECT COUNT([count]) AS [count_id]",
160
+ "FROM (",
161
+ "SELECT",
162
+ (visit(o.limit) if o.limit),
163
+ "ROW_NUMBER() OVER (ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}) AS [__rn],",
164
+ "1 AS [count]",
165
+ (source_with_lock_for_select_statement(o)),
166
+ ("WHERE #{core.wheres.map{ |x| visit(x) }.join ' AND ' }" unless core.wheres.empty?),
167
+ ("GROUP BY #{core.groups.map { |x| visit x }.join ', ' }" unless core.groups.empty?),
168
+ (visit(core.having) if core.having),
169
+ ("ORDER BY #{o.orders.map{ |x| visit(x) }.join(', ')}" if !o.orders.empty?),
170
+ ") AS [__rnt]",
171
+ (visit(o.offset) if o.offset)
172
+ ].compact.join ' '
173
+ end
174
+
175
+
176
+ # SQLServer Helpers
177
+
178
+ def source_with_lock_for_select_statement(o)
179
+ # TODO: [ARel 2.2] Use #from/#source vs. #froms
180
+ core = o.cores.first
181
+ source = "FROM #{visit core.froms}" if core.froms
182
+ if source && o.lock
183
+ lock = visit o.lock
184
+ index = source.match(/FROM [\w\[\]\.]+/)[0].length
185
+ source.insert index, " #{lock}"
186
+ else
187
+ source
188
+ end
189
+ end
190
+
191
+ def table_from_select_statement(o)
192
+ core = o.cores.first
193
+ # TODO: [ARel 2.2] Use #from/#source vs. #froms
194
+ # if Arel::Table === core.from
195
+ # core.from
196
+ # elsif Arel::Nodes::SqlLiteral === core.from
197
+ # Arel::Table.new(core.from, @engine)
198
+ # elsif Arel::Nodes::JoinSource === core.source
199
+ # Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left
200
+ # end
201
+ table_finder = lambda { |x|
202
+ case x
203
+ when Arel::Table
204
+ x
205
+ when Arel::Nodes::SqlLiteral
206
+ Arel::Table.new(x, @engine)
207
+ when Arel::Nodes::Join
208
+ table_finder.call(x.left)
209
+ end
210
+ }
211
+ table_finder.call(core.froms)
212
+ end
213
+
214
+ def single_distinct_select_statement?(o)
215
+ projections = o.cores.first.projections
216
+ p1 = projections.first
217
+ projections.size == 1 &&
218
+ ((p1.respond_to?(:distinct) && p1.distinct) ||
219
+ p1.respond_to?(:include?) && p1.include?('DISTINCT'))
220
+ end
221
+
222
+ def all_projections_aliased_in_select_statement?(o)
223
+ projections = o.cores.first.projections
224
+ projections.all? do |x|
225
+ x.split(',').all? { |y| y.include?(' AS ') }
226
+ end
227
+ end
228
+
229
+ def function_select_statement?(o)
230
+ core = o.cores.first
231
+ core.projections.any? { |x| Arel::Nodes::Function === x }
232
+ end
233
+
234
+ def eager_limiting_select_statement?(o)
235
+ core = o.cores.first
236
+ single_distinct_select_statement?(o) && (o.limit && !o.offset) && core.groups.empty?
237
+ end
238
+
239
+ def join_in_select_statement?(o)
240
+ core = o.cores.first
241
+ # TODO: [ARel 2.2] Use #from/#source vs. #froms
242
+ # core.source.right.any? { |x| Arel::Nodes::Join === x }
243
+ Arel::Nodes::Join === core.froms
244
+ end
245
+
246
+ def complex_count_sql?(o)
247
+ core = o.cores.first
248
+ core.projections.size == 1 &&
249
+ Arel::Nodes::Count === core.projections.first &&
250
+ (o.limit || !core.wheres.empty?) &&
251
+ !join_in_select_statement?(o)
252
+ end
253
+
254
+ def find_and_fix_uncorrelated_joins_in_select_statement(o)
255
+ core = o.cores.first
256
+ # TODO: [ARel 2.2] Use #from/#source vs. #froms
257
+ # return if !join_in_select_statement?(o) || core.source.right.size != 2
258
+ # j1 = core.source.right.first
259
+ # j2 = core.source.right.second
260
+ # return unless Arel::Nodes::OuterJoin === j1 && Arel::Nodes::StringJoin === j2
261
+ # j1_tn = j1.left.name
262
+ # j2_tn = j2.left.match(/JOIN \[(.*)\].*ON/).try(:[],1)
263
+ # return unless j1_tn == j2_tn
264
+ # crltd_tn = "#{j1_tn}_crltd"
265
+ # j1.left.table_alias = crltd_tn
266
+ # j1.right.expr.left.relation.table_alias = crltd_tn
267
+ return if !join_in_select_statement?(o) || !(Arel::Nodes::StringJoin === core.froms)
268
+ j1 = core.froms.left
269
+ j2 = core.froms.right
270
+ return unless Arel::Nodes::OuterJoin === j1 && Arel::Nodes::SqlLiteral === j2 && j2.include?('JOIN ')
271
+ j1_tn = j1.right.name
272
+ j2_tn = j2.match(/JOIN \[(.*)\].*ON/).try(:[],1)
273
+ return unless j1_tn == j2_tn
274
+ on_index = j2.index(' ON ')
275
+ j2.insert on_index, " AS [#{j2_tn}_crltd]"
276
+ j2.sub! "[#{j2_tn}].", "[#{j2_tn}_crltd]."
277
+ end
278
+
279
+ def rowtable_projections(o)
280
+ core = o.cores.first
281
+ if single_distinct_select_statement?(o)
282
+ tn = table_from_select_statement(o).name
283
+ core.projections.map do |x|
284
+ x.dup.tap do |p|
285
+ p.sub! 'DISTINCT', "DISTINCT #{visit(o.limit)}".strip if o.limit
286
+ p.gsub! /\[?#{tn}\]?\./, '[__rnt].'
287
+ p.strip!
288
+ end
289
+ end
290
+ elsif join_in_select_statement?(o) && all_projections_aliased_in_select_statement?(o)
291
+ core.projections.map do |x|
292
+ Arel.sql x.split(',').map{ |y| y.split(' AS ').last.strip }.join(', ')
293
+ end
294
+ elsif function_select_statement?(o)
295
+ # TODO: [ARel 2.2] Use Arel.star
296
+ [Arel.sql('*')]
297
+ else
298
+ tn = table_from_select_statement(o).name
299
+ core.projections.map { |x| x.gsub /\[#{tn}\]\./, '[__rnt].' }
300
+ end
301
+ end
302
+
303
+ def rowtable_orders(o)
304
+ core = o.cores.first
305
+ if !o.orders.empty?
306
+ o.orders
307
+ else
308
+ t = table_from_select_statement(o)
309
+ c = t.primary_key || t.columns.first
310
+ [c.asc]
311
+ end.uniq
312
+ end
313
+
314
+ # TODO: We use this for grouping too, maybe make Grouping objects vs SqlLiteral.
315
+ def projection_without_expression(projection)
316
+ Arel.sql(projection.split(',').map do |x|
317
+ x.strip!
318
+ x.sub!(/^(COUNT|SUM|MAX|MIN|AVG)\s*(\((.*)\))?/,'\3')
319
+ x.sub!(/^DISTINCT\s*/,'')
320
+ x.sub!(/TOP\s*\(\d+\)\s*/i,'')
321
+ x.strip
322
+ end.join(', '))
323
+ end
324
+
325
+ end
326
+ end
327
+
328
+ end
329
+
330
+ Arel::Visitors::VISITORS['sqlserver'] = Arel::Visitors::SQLServer
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-sqlserver-adapter-2000
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 3
7
+ - 0
8
+ - 15
9
+ version: 3.0.15
10
+ platform: ruby
11
+ authors:
12
+ - Brian Eng
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-05-03 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activerecord
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 3
29
+ - 0
30
+ - 3
31
+ version: 3.0.3
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: arel
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 2
43
+ - 0
44
+ - 7
45
+ version: 2.0.7
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ description: Fork of SQL Server 2005 and 2008 Adapter For ActiveRecord. Adds support for SQL Server 2000.
49
+ email: beng336@gmail.com
50
+ executables: []
51
+
52
+ extensions: []
53
+
54
+ extra_rdoc_files:
55
+ - README.rdoc
56
+ files:
57
+ - CHANGELOG
58
+ - MIT-LICENSE
59
+ - README.rdoc
60
+ - lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb
61
+ - lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb
62
+ - lib/active_record/connection_adapters/sqlserver/database_limits.rb
63
+ - lib/active_record/connection_adapters/sqlserver/database_statements.rb
64
+ - lib/active_record/connection_adapters/sqlserver/errors.rb
65
+ - lib/active_record/connection_adapters/sqlserver/query_cache.rb
66
+ - lib/active_record/connection_adapters/sqlserver/quoting.rb
67
+ - lib/active_record/connection_adapters/sqlserver/schema_statements.rb
68
+ - lib/active_record/connection_adapters/sqlserver_adapter.rb
69
+ - lib/activerecord-sqlserver-adapter.rb
70
+ - lib/arel/visitors/sqlserver.rb
71
+ has_rdoc: true
72
+ homepage: https://github.com/beng336/activerecord-sqlserver-adapter-2000
73
+ licenses: []
74
+
75
+ post_install_message:
76
+ rdoc_options:
77
+ - --main
78
+ - README.rdoc
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ requirements: []
96
+
97
+ rubyforge_project: activerecord-sqlserver-adapter-2000
98
+ rubygems_version: 1.3.6
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: Fork of SQL Server 2005 and 2008 Adapter For ActiveRecord. Adds support for SQL Server 2000.
102
+ test_files: []
103
+