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.
- data/CHANGELOG +400 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +176 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +58 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +57 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +414 -0
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +35 -0
- data/lib/active_record/connection_adapters/sqlserver/query_cache.rb +17 -0
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +55 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +403 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +470 -0
- data/lib/activerecord-sqlserver-adapter.rb +1 -0
- data/lib/arel/visitors/sqlserver.rb +330 -0
- metadata +103 -0
@@ -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
|
+
|