activerecord-sqlserver-adapter-2000 3.0.15

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