asa-2000 1.0.0

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,386 @@
1
+ require 'arel'
2
+
3
+ module Arel
4
+
5
+ module Nodes
6
+
7
+ # Extending the Ordering class to be comparrison friendly which allows us to call #uniq on a
8
+ # collection of them. See SelectManager#order for more details.
9
+ class Ordering < Arel::Nodes::Unary
10
+ def hash
11
+ expr.hash
12
+ end
13
+ def ==(other)
14
+ other.is_a?(Arel::Nodes::Ordering) && self.expr == other.expr
15
+ end
16
+ def eql?(other)
17
+ self == other
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ class SelectManager < Arel::TreeManager
24
+
25
+ AR_CA_SQLSA_NAME = 'ActiveRecord::ConnectionAdapters::SQLServerAdapter'.freeze
26
+
27
+ # Getting real Ordering objects is very important for us. We need to be able to call #uniq on
28
+ # a colleciton of them reliably as well as using their true object attributes to mutate them
29
+ # to grouping objects for the inner sql during a select statment with an offset/rownumber. So this
30
+ # is here till ActiveRecord & ARel does this for us instead of using SqlLiteral objects.
31
+ alias :order_without_sqlserver :order
32
+ def order(*expr)
33
+ return order_without_sqlserver(*expr) unless engine_activerecord_sqlserver_adapter?
34
+ @ast.orders.concat(expr.map{ |x|
35
+ case x
36
+ when Arel::Attributes::Attribute
37
+ table = Arel::Table.new(x.relation.table_alias || x.relation.name)
38
+ e = table[x.name]
39
+ Arel::Nodes::Ascending.new e
40
+ when Arel::Nodes::Ordering
41
+ x
42
+ when String
43
+ x.split(',').map do |s|
44
+ s = x if x.strip =~ /\A\b\w+\b\(.*,.*\)(\s+(ASC|DESC))?\Z/i # Allow functions with comma(s) to pass thru.
45
+ s.strip!
46
+ d = s =~ /(ASC|DESC)\Z/i ? $1.upcase : nil
47
+ e = d.nil? ? s : s.mb_chars[0...-d.length].strip
48
+ e = Arel.sql(e)
49
+ d && d == "DESC" ? Arel::Nodes::Descending.new(e) : Arel::Nodes::Ascending.new(e)
50
+ end
51
+ else
52
+ e = Arel.sql(x.to_s)
53
+ Arel::Nodes::Ascending.new e
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
+ alias :lock_without_sqlserver :lock
62
+ def lock(locking=true)
63
+ if engine_activerecord_sqlserver_adapter?
64
+ case locking
65
+ when true
66
+ locking = Arel.sql('WITH(HOLDLOCK, ROWLOCK)')
67
+ when Arel::Nodes::SqlLiteral
68
+ when String
69
+ locking = Arel.sql locking
70
+ end
71
+ @ast.lock = Arel::Nodes::Lock.new(locking)
72
+ self
73
+ else
74
+ lock_without_sqlserver(locking)
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def engine_activerecord_sqlserver_adapter?
81
+ @engine.connection && @engine.connection.class.name == AR_CA_SQLSA_NAME
82
+ end
83
+
84
+ end
85
+
86
+ module Visitors
87
+ class SQLServer < Arel::Visitors::ToSql
88
+
89
+ private
90
+
91
+ # SQLServer ToSql/Visitor (Overides)
92
+
93
+ def visit_Arel_Nodes_SelectStatement(o)
94
+ if complex_count_sql?(o)
95
+ visit_Arel_Nodes_SelectStatementForComplexCount(o)
96
+ elsif o.offset
97
+ visit_Arel_Nodes_SelectStatementWithOffset(o)
98
+ else
99
+ visit_Arel_Nodes_SelectStatementWithOutOffset(o)
100
+ end
101
+ end
102
+
103
+ def visit_Arel_Nodes_UpdateStatement(o)
104
+ if o.orders.any? && o.limit.nil?
105
+ o.limit = Nodes::Limit.new(2147483647)
106
+ end
107
+ super
108
+ end
109
+
110
+ def visit_Arel_Nodes_Offset(o)
111
+ "WHERE [__rnt].[__rn] > (#{visit o.expr})"
112
+ end
113
+
114
+ def visit_Arel_Nodes_Limit(o)
115
+ "TOP #{visit o.expr}"
116
+ end
117
+
118
+ def visit_Arel_Nodes_Lock(o)
119
+ visit o.expr
120
+ end
121
+
122
+ def visit_Arel_Nodes_Ordering(o)
123
+ if o.respond_to?(:direction)
124
+ "#{visit o.expr} #{o.ascending? ? 'ASC' : 'DESC'}"
125
+ else
126
+ visit o.expr
127
+ end
128
+ end
129
+
130
+ def visit_Arel_Nodes_Bin(o)
131
+ "#{visit o.expr} #{@connection.cs_equality_operator}"
132
+ end
133
+
134
+ # SQLServer ToSql/Visitor (Additions)
135
+
136
+ def visit_Arel_Nodes_SelectStatementWithOutOffset(o, windowed=false)
137
+ find_and_fix_uncorrelated_joins_in_select_statement(o)
138
+ core = o.cores.first
139
+ projections = core.projections
140
+ groups = core.groups
141
+ orders = o.orders.uniq
142
+ if windowed
143
+ projections = function_select_statement?(o) ? projections : projections.map { |x| projection_without_expression(x) }
144
+ groups = projections.map { |x| projection_without_expression(x) } if windowed_single_distinct_select_statement?(o) && groups.empty?
145
+ groups += orders.map { |x| Arel.sql(x.expr) } if windowed_single_distinct_select_statement?(o)
146
+ elsif eager_limiting_select_statement?(o)
147
+ projections = projections.map { |x| projection_without_expression(x) }
148
+ groups = projections.map { |x| projection_without_expression(x) }
149
+ orders = orders.map do |x|
150
+ expr = Arel.sql projection_without_expression(x.expr)
151
+ x.descending? ? Arel::Nodes::Max.new([expr]) : Arel::Nodes::Min.new([expr])
152
+ end
153
+ elsif top_one_everything_for_through_join?(o)
154
+ projections = projections.map { |x| projection_without_expression(x) }
155
+ end
156
+ [ ("SELECT" if !windowed),
157
+ (visit(core.set_quantifier) if core.set_quantifier),
158
+ (visit(o.limit) if o.limit && !windowed),
159
+ (projections.map{ |x| visit(x) }.join(', ')),
160
+ (source_with_lock_for_select_statement(o)),
161
+ ("WHERE #{core.wheres.map{ |x| visit(x) }.join ' AND ' }" unless core.wheres.empty?),
162
+ ("GROUP BY #{groups.map { |x| visit x }.join ', ' }" unless groups.empty?),
163
+ (visit(core.having) if core.having),
164
+ ("ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}" if !orders.empty? && !windowed)
165
+ ].compact.join ' '
166
+ end
167
+
168
+ def visit_Arel_Nodes_SelectStatementWithOffset(o)
169
+ orders = rowtable_orders(o)
170
+ [ "SELECT",
171
+ (visit(o.limit) if o.limit && !windowed_single_distinct_select_statement?(o)),
172
+ (rowtable_projections(o).map{ |x| visit(x) }.join(', ')),
173
+ "FROM (",
174
+ "SELECT ROW_NUMBER() OVER (ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}) AS [__rn],",
175
+ visit_Arel_Nodes_SelectStatementWithOutOffset(o,true),
176
+ ") AS [__rnt]",
177
+ (visit(o.offset) if o.offset),
178
+ ].compact.join ' '
179
+ end
180
+
181
+ def visit_Arel_Nodes_SelectStatementForComplexCount(o)
182
+ core = o.cores.first
183
+ o.limit.expr = Arel.sql("#{o.limit.expr} + #{o.offset ? o.offset.expr : 0}") if o.limit
184
+ orders = rowtable_orders(o)
185
+ [ "SELECT COUNT([count]) AS [count_id]",
186
+ "FROM (",
187
+ "SELECT",
188
+ (visit(o.limit) if o.limit),
189
+ "ROW_NUMBER() OVER (ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}) AS [__rn],",
190
+ "1 AS [count]",
191
+ (source_with_lock_for_select_statement(o)),
192
+ ("WHERE #{core.wheres.map{ |x| visit(x) }.join ' AND ' }" unless core.wheres.empty?),
193
+ ("GROUP BY #{core.groups.map { |x| visit x }.join ', ' }" unless core.groups.empty?),
194
+ (visit(core.having) if core.having),
195
+ ("ORDER BY #{o.orders.map{ |x| visit(x) }.join(', ')}" if !o.orders.empty?),
196
+ ") AS [__rnt]",
197
+ (visit(o.offset) if o.offset)
198
+ ].compact.join ' '
199
+ end
200
+
201
+
202
+ # SQLServer Helpers
203
+
204
+ def source_with_lock_for_select_statement(o)
205
+ core = o.cores.first
206
+ source = "FROM #{visit(core.source).strip}" if core.source
207
+ if source && o.lock
208
+ lock = visit o.lock
209
+ index = source.match(/FROM [\w\[\]\.]+/)[0].mb_chars.length
210
+ source.insert index, " #{lock}"
211
+ else
212
+ source
213
+ end
214
+ end
215
+
216
+ def table_from_select_statement(o)
217
+ core = o.cores.first
218
+ # TODO: [ARel 2.2] Use #from/#source vs. #froms
219
+ # if Arel::Table === core.from
220
+ # core.from
221
+ # elsif Arel::Nodes::SqlLiteral === core.from
222
+ # Arel::Table.new(core.from, @engine)
223
+ # elsif Arel::Nodes::JoinSource === core.source
224
+ # Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left
225
+ # end
226
+ table_finder = lambda { |x|
227
+ case x
228
+ when Arel::Table
229
+ x
230
+ when Arel::Nodes::SqlLiteral
231
+ Arel::Table.new(x, @engine)
232
+ when Arel::Nodes::Join
233
+ table_finder.call(x.left)
234
+ end
235
+ }
236
+ table_finder.call(core.froms)
237
+ end
238
+
239
+ def single_distinct_select_statement?(o)
240
+ projections = o.cores.first.projections
241
+ p1 = projections.first
242
+ projections.size == 1 &&
243
+ ((p1.respond_to?(:distinct) && p1.distinct) ||
244
+ p1.respond_to?(:include?) && p1.include?('DISTINCT'))
245
+ end
246
+
247
+ def windowed_single_distinct_select_statement?(o)
248
+ o.limit && o.offset && single_distinct_select_statement?(o)
249
+ end
250
+
251
+ def single_distinct_select_everything_statement?(o)
252
+ single_distinct_select_statement?(o) && visit(o.cores.first.projections.first).ends_with?(".*")
253
+ end
254
+
255
+ def top_one_everything_for_through_join?(o)
256
+ single_distinct_select_everything_statement?(o) &&
257
+ (o.limit && !o.offset) &&
258
+ join_in_select_statement?(o)
259
+ end
260
+
261
+ def all_projections_aliased_in_select_statement?(o)
262
+ projections = o.cores.first.projections
263
+ projections.all? do |x|
264
+ visit(x).split(',').all? { |y| y.include?(' AS ') }
265
+ end
266
+ end
267
+
268
+ def function_select_statement?(o)
269
+ core = o.cores.first
270
+ core.projections.any? { |x| Arel::Nodes::Function === x }
271
+ end
272
+
273
+ def eager_limiting_select_statement?(o)
274
+ core = o.cores.first
275
+ single_distinct_select_statement?(o) &&
276
+ (o.limit && !o.offset) &&
277
+ core.groups.empty? &&
278
+ !single_distinct_select_everything_statement?(o)
279
+ end
280
+
281
+ def join_in_select_statement?(o)
282
+ core = o.cores.first
283
+ core.source.right.any? { |x| Arel::Nodes::Join === x }
284
+ end
285
+
286
+ def complex_count_sql?(o)
287
+ core = o.cores.first
288
+ core.projections.size == 1 &&
289
+ Arel::Nodes::Count === core.projections.first &&
290
+ o.limit &&
291
+ !join_in_select_statement?(o)
292
+ end
293
+
294
+ def select_primary_key_sql?(o)
295
+ core = o.cores.first
296
+ return false if core.projections.size != 1
297
+ p = core.projections.first
298
+ t = table_from_select_statement(o)
299
+ Arel::Attributes::Attribute === p && t.primary_key && t.primary_key.name == p.name
300
+ end
301
+
302
+ def find_and_fix_uncorrelated_joins_in_select_statement(o)
303
+ core = o.cores.first
304
+ # TODO: [ARel 2.2] Use #from/#source vs. #froms
305
+ # return if !join_in_select_statement?(o) || core.source.right.size != 2
306
+ # j1 = core.source.right.first
307
+ # j2 = core.source.right.second
308
+ # return unless Arel::Nodes::OuterJoin === j1 && Arel::Nodes::StringJoin === j2
309
+ # j1_tn = j1.left.name
310
+ # j2_tn = j2.left.match(/JOIN \[(.*)\].*ON/).try(:[],1)
311
+ # return unless j1_tn == j2_tn
312
+ # crltd_tn = "#{j1_tn}_crltd"
313
+ # j1.left.table_alias = crltd_tn
314
+ # j1.right.expr.left.relation.table_alias = crltd_tn
315
+ return if !join_in_select_statement?(o) || !(Arel::Nodes::StringJoin === core.froms)
316
+ j1 = core.froms.left
317
+ j2 = core.froms.right
318
+ return unless Arel::Nodes::OuterJoin === j1 && Arel::Nodes::SqlLiteral === j2 && j2.include?('JOIN ')
319
+ j1_tn = j1.right.name
320
+ j2_tn = j2.match(/JOIN \[(.*)\].*ON/).try(:[],1)
321
+ return unless j1_tn == j2_tn
322
+ on_index = j2.index(' ON ')
323
+ j2.insert on_index, " AS [#{j2_tn}_crltd]"
324
+ j2.sub! "[#{j2_tn}].", "[#{j2_tn}_crltd]."
325
+ end
326
+
327
+ def rowtable_projections(o)
328
+ core = o.cores.first
329
+ if windowed_single_distinct_select_statement?(o) && core.groups.blank?
330
+ tn = table_from_select_statement(o).name
331
+ core.projections.map do |x|
332
+ x.dup.tap do |p|
333
+ p.sub! 'DISTINCT', ''
334
+ p.insert 0, visit(o.limit) if o.limit
335
+ p.gsub! /\[?#{tn}\]?\./, '[__rnt].'
336
+ p.strip!
337
+ end
338
+ end
339
+ elsif single_distinct_select_statement?(o)
340
+ tn = table_from_select_statement(o).name
341
+ core.projections.map do |x|
342
+ x.dup.tap do |p|
343
+ p.sub! 'DISTINCT', "DISTINCT #{visit(o.limit)}".strip if o.limit
344
+ p.gsub! /\[?#{tn}\]?\./, '[__rnt].'
345
+ p.strip!
346
+ end
347
+ end
348
+ elsif join_in_select_statement?(o) && all_projections_aliased_in_select_statement?(o)
349
+ core.projections.map do |x|
350
+ Arel.sql visit(x).split(',').map{ |y| y.split(' AS ').last.strip }.join(', ')
351
+ end
352
+ elsif select_primary_key_sql?(o)
353
+ [Arel.sql("[__rnt].#{quote_column_name(core.projections.first.name)}")]
354
+ else
355
+ [Arel.sql('[__rnt].*')]
356
+ end
357
+ end
358
+
359
+ def rowtable_orders(o)
360
+ core = o.cores.first
361
+ if !o.orders.empty?
362
+ o.orders
363
+ else
364
+ t = table_from_select_statement(o)
365
+ c = t.primary_key || t.columns.first
366
+ [c.asc]
367
+ end.uniq
368
+ end
369
+
370
+ # TODO: We use this for grouping too, maybe make Grouping objects vs SqlLiteral.
371
+ def projection_without_expression(projection)
372
+ Arel.sql(visit(projection).split(',').map do |x|
373
+ x.strip!
374
+ x.sub!(/^(COUNT|SUM|MAX|MIN|AVG)\s*(\((.*)\))?/,'\3')
375
+ x.sub!(/^DISTINCT\s*/,'')
376
+ x.sub!(/TOP\s*\(\d+\)\s*/i,'')
377
+ x.strip
378
+ end.join(', '))
379
+ end
380
+
381
+ end
382
+ end
383
+
384
+ end
385
+
386
+ Arel::Visitors::VISITORS['sqlserver'] = Arel::Visitors::SQLServer
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: asa-2000
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ken Collins
9
+ - Murray Steele
10
+ - Shawn Balestracci
11
+ - Joe Rafaniello
12
+ - Tom Ward
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+ date: 2013-03-01 00:00:00.000000000 Z
17
+ dependencies:
18
+ - !ruby/object:Gem::Dependency
19
+ name: activerecord
20
+ requirement: !ruby/object:Gem::Requirement
21
+ none: false
22
+ requirements:
23
+ - - ! '>'
24
+ - !ruby/object:Gem::Version
25
+ version: 3.0.0
26
+ type: :runtime
27
+ prerelease: false
28
+ version_requirements: !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>'
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.0
34
+ description: Rip off of SQLServer 2000 hack for SQL Server 2005 and 2008 Adapter For
35
+ ActiveRecord
36
+ email: ken@metaskills.net
37
+ executables: []
38
+ extensions: []
39
+ extra_rdoc_files: []
40
+ files:
41
+ - CHANGELOG
42
+ - MIT-LICENSE
43
+ - lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb
44
+ - lib/active_record/connection_adapters/sqlserver/core_ext/database_statements.rb
45
+ - lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb
46
+ - lib/active_record/connection_adapters/sqlserver/database_limits.rb
47
+ - lib/active_record/connection_adapters/sqlserver/database_statements.rb
48
+ - lib/active_record/connection_adapters/sqlserver/errors.rb
49
+ - lib/active_record/connection_adapters/sqlserver/quoting.rb
50
+ - lib/active_record/connection_adapters/sqlserver/schema_cache.rb
51
+ - lib/active_record/connection_adapters/sqlserver/schema_statements.rb
52
+ - lib/active_record/connection_adapters/sqlserver/utils.rb
53
+ - lib/active_record/connection_adapters/sqlserver/version.rb
54
+ - lib/active_record/connection_adapters/sqlserver_adapter.rb
55
+ - lib/activerecord-sqlserver-adapter.rb
56
+ - lib/arel/visitors/sqlserver.rb
57
+ homepage: http://github.com/rails-sqlserver/activerecord-sqlserver-adapter
58
+ licenses: []
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project: activerecord-sqlserver-adapter
77
+ rubygems_version: 1.8.25
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: Rip off of SQLServer 2000 hack for SQL Server 2005 and 2008 Adapter For ActiveRecord.
81
+ test_files: []