sic-activerecord-sqlserver-adapter 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (25) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +603 -0
  3. data/MIT-LICENSE +20 -0
  4. data/VERSION +1 -0
  5. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +42 -0
  6. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +41 -0
  7. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +4 -0
  8. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +38 -0
  9. data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +19 -0
  10. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
  11. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +458 -0
  12. data/lib/active_record/connection_adapters/sqlserver/errors.rb +36 -0
  13. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +109 -0
  14. data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +85 -0
  15. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +395 -0
  16. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +67 -0
  17. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +69 -0
  18. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +25 -0
  19. data/lib/active_record/connection_adapters/sqlserver/utils.rb +32 -0
  20. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +545 -0
  21. data/lib/active_record/sqlserver_test_case.rb +17 -0
  22. data/lib/activerecord-sqlserver-adapter.rb +1 -0
  23. data/lib/arel/select_manager_sqlserver.rb +64 -0
  24. data/lib/arel/visitors/sqlserver.rb +326 -0
  25. metadata +98 -0
@@ -0,0 +1,17 @@
1
+ require 'active_record/test_case.rb'
2
+
3
+ # TODO: I'm struggling to figure out how to unsubscribe from only one 'sql.active_record'
4
+ # This is a temporary hack until we can just get the sqlserver_ignored regex in rails
5
+ ActiveSupport::Notifications.notifier.listeners_for('sql.active_record').each do |listener|
6
+ if listener.inspect =~ /ActiveRecord::SQLCounter/
7
+ ActiveSupport::Notifications.unsubscribe(listener)
8
+ end
9
+ end
10
+
11
+ module ActiveRecord
12
+ class SQLCounter
13
+ sqlserver_ignored = [%r|SELECT SCOPE_IDENTITY|, %r{INFORMATION_SCHEMA\.(TABLES|VIEWS|COLUMNS)},%r|SELECT @@version|, %r|SELECT @@TRANCOUNT|, %r{(BEGIN|COMMIT|ROLLBACK|SAVE) TRANSACTION}]
14
+ ignored_sql.concat sqlserver_ignored
15
+ end
16
+ ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
17
+ end
@@ -0,0 +1 @@
1
+ require 'active_record/connection_adapters/sqlserver_adapter'
@@ -0,0 +1,64 @@
1
+ module Arel
2
+ class SelectManager < Arel::TreeManager
3
+
4
+ AR_CA_SQLSA_NAME = 'ActiveRecord::ConnectionAdapters::SQLServerAdapter'.freeze
5
+
6
+ # Getting real Ordering objects is very important for us. We need to be able to call #uniq on
7
+ # a colleciton of them reliably as well as using their true object attributes to mutate them
8
+ # to grouping objects for the inner sql during a select statment with an offset/rownumber. So this
9
+ # is here till ActiveRecord & ARel does this for us instead of using SqlLiteral objects.
10
+ alias :order_without_sqlserver :order
11
+ def order(*expr)
12
+ return order_without_sqlserver(*expr) unless engine_activerecord_sqlserver_adapter?
13
+ @ast.orders.concat(expr.map{ |x|
14
+ case x
15
+ when Arel::Attributes::Attribute
16
+ table = Arel::Table.new(x.relation.table_alias || x.relation.name)
17
+ e = table[x.name]
18
+ Arel::Nodes::Ascending.new e
19
+ when Arel::Nodes::Ordering
20
+ x
21
+ when String
22
+ x.split(',').map do |s|
23
+ s = x if x.strip =~ /\A\b\w+\b\(.*,.*\)(\s+(ASC|DESC))?\Z/i # Allow functions with comma(s) to pass thru.
24
+ s.strip!
25
+ d = s =~ /(ASC|DESC)\Z/i ? $1.upcase : nil
26
+ e = d.nil? ? s : s.mb_chars[0...-d.length].strip
27
+ e = Arel.sql(e)
28
+ d && d == "DESC" ? Arel::Nodes::Descending.new(e) : Arel::Nodes::Ascending.new(e)
29
+ end
30
+ else
31
+ e = Arel.sql(x.to_s)
32
+ Arel::Nodes::Ascending.new e
33
+ end
34
+ }.flatten)
35
+ self
36
+ end
37
+
38
+ # A friendly over ride that allows us to put a special lock object that can have a default or pass
39
+ # custom string hints down. See the visit_Arel_Nodes_LockWithSQLServer delegation method.
40
+ alias :lock_without_sqlserver :lock
41
+ def lock(locking=true)
42
+ if engine_activerecord_sqlserver_adapter?
43
+ case locking
44
+ when true
45
+ locking = Arel.sql('WITH(HOLDLOCK, ROWLOCK)')
46
+ when Arel::Nodes::SqlLiteral
47
+ when String
48
+ locking = Arel.sql locking
49
+ end
50
+ @ast.lock = Arel::Nodes::Lock.new(locking)
51
+ self
52
+ else
53
+ lock_without_sqlserver(locking)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def engine_activerecord_sqlserver_adapter?
60
+ @engine.connection && @engine.connection.class.name == AR_CA_SQLSA_NAME
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,326 @@
1
+ require 'arel'
2
+ require 'arel/select_manager_sqlserver'
3
+ module Arel
4
+
5
+ module Nodes
6
+
7
+ # Extending the Ordering class to be comparison 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
+ module Visitors
24
+ class SQLServer < Arel::Visitors::ToSql
25
+
26
+ private
27
+
28
+ # SQLServer ToSql/Visitor (Overides)
29
+
30
+ def visit_Arel_Nodes_SelectStatement(o, a)
31
+ if complex_count_sql?(o)
32
+ visit_Arel_Nodes_SelectStatementForComplexCount(o, a)
33
+ elsif o.offset
34
+ visit_Arel_Nodes_SelectStatementWithOffset(o, a)
35
+ else
36
+ visit_Arel_Nodes_SelectStatementWithOutOffset(o, a)
37
+ end
38
+ end
39
+
40
+ def visit_Arel_Nodes_UpdateStatement(o, a)
41
+ if o.orders.any? && o.limit.nil?
42
+ o.limit = Nodes::Limit.new(9223372036854775807)
43
+ end
44
+ super
45
+ end
46
+
47
+ def visit_Arel_Nodes_Offset(o, a)
48
+ "WHERE [__rnt].[__rn] > (#{visit o.expr})"
49
+ end
50
+
51
+ def visit_Arel_Nodes_Limit(o, a)
52
+ "TOP (#{visit o.expr})"
53
+ end
54
+
55
+ def visit_Arel_Nodes_Lock(o, a)
56
+ visit o.expr
57
+ end
58
+
59
+ def visit_Arel_Nodes_Ordering(o, a)
60
+ if o.respond_to?(:direction)
61
+ "#{visit o.expr} #{o.ascending? ? 'ASC' : 'DESC'}"
62
+ else
63
+ visit o.expr
64
+ end
65
+ end
66
+
67
+ def visit_Arel_Nodes_Bin(o, a)
68
+ "#{visit o.expr} #{@connection.cs_equality_operator}"
69
+ end
70
+
71
+ # SQLServer ToSql/Visitor (Additions)
72
+
73
+ def visit_Arel_Nodes_SelectStatementWithOutOffset(o, a, windowed = false)
74
+ find_and_fix_uncorrelated_joins_in_select_statement(o)
75
+ core = o.cores.first
76
+ projections = core.projections
77
+ groups = core.groups
78
+ orders = o.orders.uniq
79
+ if windowed
80
+ projections = function_select_statement?(o) ? projections : projections.map { |x| projection_without_expression(x) }
81
+ groups = projections.map { |x| projection_without_expression(x) } if windowed_single_distinct_select_statement?(o) && groups.empty?
82
+ groups += orders.map { |x| Arel.sql(x.expr) } if windowed_single_distinct_select_statement?(o)
83
+ elsif eager_limiting_select_statement?(o)
84
+ projections = projections.map { |x| projection_without_expression(x) }
85
+ groups = projections.map { |x| projection_without_expression(x) }
86
+ orders = orders.map do |x|
87
+ expr = Arel.sql projection_without_expression(x.expr)
88
+ x.descending? ? Arel::Nodes::Max.new([expr]) : Arel::Nodes::Min.new([expr])
89
+ end
90
+ elsif top_one_everything_for_through_join?(o)
91
+ projections = projections.map { |x| projection_without_expression(x) }
92
+ end
93
+ [ ("SELECT" if !windowed),
94
+ (visit(core.set_quantifier) if core.set_quantifier && !windowed),
95
+ (visit(o.limit) if o.limit && !windowed),
96
+ (projections.map{ |x| v = visit(x); v == "1" ? "1 AS [__wrp]" : v }.join(', ')),
97
+ (source_with_lock_for_select_statement(o)),
98
+ ("WHERE #{core.wheres.map{ |x| visit(x) }.join ' AND ' }" unless core.wheres.empty?),
99
+ ("GROUP BY #{groups.map { |x| visit(x) }.join ', ' }" unless groups.empty?),
100
+ (visit(core.having) if core.having),
101
+ ("ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}" if !orders.empty? && !windowed)
102
+ ].compact.join ' '
103
+ end
104
+
105
+ def visit_Arel_Nodes_SelectStatementWithOffset(o, a)
106
+ core = o.cores.first
107
+ o.limit ||= Arel::Nodes::Limit.new(9223372036854775807)
108
+ orders = rowtable_orders(o)
109
+ [ "SELECT",
110
+ (visit(o.limit) if o.limit && !windowed_single_distinct_select_statement?(o)),
111
+ (rowtable_projections(o).map{ |x| visit(x) }.join(', ')),
112
+ "FROM (",
113
+ "SELECT #{core.set_quantifier ? 'DISTINCT DENSE_RANK()' : 'ROW_NUMBER()'} OVER (ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}) AS [__rn],",
114
+ visit_Arel_Nodes_SelectStatementWithOutOffset(o, a, true),
115
+ ") AS [__rnt]",
116
+ (visit(o.offset) if o.offset),
117
+ "ORDER BY [__rnt].[__rn] ASC"
118
+ ].compact.join ' '
119
+ end
120
+
121
+ def visit_Arel_Nodes_SelectStatementForComplexCount(o, a)
122
+ core = o.cores.first
123
+ o.limit.expr = Arel.sql("#{o.limit.expr} + #{o.offset ? o.offset.expr : 0}") if o.limit
124
+ orders = rowtable_orders(o)
125
+ [ "SELECT COUNT([count]) AS [count_id]",
126
+ "FROM (",
127
+ "SELECT",
128
+ (visit(o.limit) if o.limit),
129
+ "ROW_NUMBER() OVER (ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}) AS [__rn],",
130
+ "1 AS [count]",
131
+ (source_with_lock_for_select_statement(o)),
132
+ ("WHERE #{core.wheres.map{ |x| visit(x) }.join ' AND ' }" unless core.wheres.empty?),
133
+ ("GROUP BY #{core.groups.map { |x| visit x }.join ', ' }" unless core.groups.empty?),
134
+ (visit(core.having) if core.having),
135
+ ("ORDER BY #{o.orders.map{ |x| visit(x) }.join(', ')}" if !o.orders.empty?),
136
+ ") AS [__rnt]",
137
+ (visit(o.offset) if o.offset)
138
+ ].compact.join ' '
139
+ end
140
+
141
+
142
+ # SQLServer Helpers
143
+
144
+ def source_with_lock_for_select_statement(o)
145
+ core = o.cores.first
146
+ source = "FROM #{visit(core.source).strip}" if core.source
147
+ if source && o.lock
148
+ lock = visit o.lock
149
+ index = source.match(/FROM [\w\[\]\.]+/)[0].mb_chars.length
150
+ source.insert index, " #{lock}"
151
+ else
152
+ source
153
+ end
154
+ end
155
+
156
+ def table_from_select_statement(o)
157
+ core = o.cores.first
158
+ # TODO: [ARel 2.2] Use #from/#source vs. #froms
159
+ # if Arel::Table === core.from
160
+ # core.from
161
+ # elsif Arel::Nodes::SqlLiteral === core.from
162
+ # Arel::Table.new(core.from, @engine)
163
+ # elsif Arel::Nodes::JoinSource === core.source
164
+ # Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left
165
+ # end
166
+ table_finder = lambda { |x|
167
+ case x
168
+ when Arel::Table
169
+ x
170
+ when Arel::Nodes::SqlLiteral
171
+ Arel::Table.new(x, @engine)
172
+ when Arel::Nodes::Join
173
+ table_finder.call(x.left)
174
+ end
175
+ }
176
+ table_finder.call(core.froms)
177
+ end
178
+
179
+ def single_distinct_select_statement?(o)
180
+ projections = o.cores.first.projections
181
+ p1 = projections.first
182
+ projections.size == 1 &&
183
+ ((p1.respond_to?(:distinct) && p1.distinct) ||
184
+ p1.respond_to?(:include?) && p1.include?('DISTINCT'))
185
+ end
186
+
187
+ def windowed_single_distinct_select_statement?(o)
188
+ o.limit && o.offset && single_distinct_select_statement?(o)
189
+ end
190
+
191
+ def single_distinct_select_everything_statement?(o)
192
+ single_distinct_select_statement?(o) && visit(o.cores.first.projections.first).ends_with?(".*")
193
+ end
194
+
195
+ def top_one_everything_for_through_join?(o)
196
+ single_distinct_select_everything_statement?(o) &&
197
+ (o.limit && !o.offset) &&
198
+ join_in_select_statement?(o)
199
+ end
200
+
201
+ def all_projections_aliased_in_select_statement?(o)
202
+ projections = o.cores.first.projections
203
+ projections.all? do |x|
204
+ visit(x).split(',').all? { |y| y.include?(' AS ') }
205
+ end
206
+ end
207
+
208
+ def function_select_statement?(o)
209
+ core = o.cores.first
210
+ core.projections.any? { |x| Arel::Nodes::Function === x }
211
+ end
212
+
213
+ def eager_limiting_select_statement?(o)
214
+ core = o.cores.first
215
+ single_distinct_select_statement?(o) &&
216
+ (o.limit && !o.offset) &&
217
+ core.groups.empty? &&
218
+ !single_distinct_select_everything_statement?(o)
219
+ end
220
+
221
+ def join_in_select_statement?(o)
222
+ core = o.cores.first
223
+ core.source.right.any? { |x| Arel::Nodes::Join === x }
224
+ end
225
+
226
+ def complex_count_sql?(o)
227
+ core = o.cores.first
228
+ core.projections.size == 1 &&
229
+ Arel::Nodes::Count === core.projections.first &&
230
+ o.limit &&
231
+ !join_in_select_statement?(o)
232
+ end
233
+
234
+ def select_primary_key_sql?(o)
235
+ core = o.cores.first
236
+ return false if core.projections.size != 1
237
+ p = core.projections.first
238
+ t = table_from_select_statement(o)
239
+ Arel::Attributes::Attribute === p && t.primary_key && t.primary_key.name == p.name
240
+ end
241
+
242
+ def find_and_fix_uncorrelated_joins_in_select_statement(o)
243
+ core = o.cores.first
244
+ # TODO: [ARel 2.2] Use #from/#source vs. #froms
245
+ # return if !join_in_select_statement?(o) || core.source.right.size != 2
246
+ # j1 = core.source.right.first
247
+ # j2 = core.source.right.second
248
+ # return unless Arel::Nodes::OuterJoin === j1 && Arel::Nodes::StringJoin === j2
249
+ # j1_tn = j1.left.name
250
+ # j2_tn = j2.left.match(/JOIN \[(.*)\].*ON/).try(:[],1)
251
+ # return unless j1_tn == j2_tn
252
+ # crltd_tn = "#{j1_tn}_crltd"
253
+ # j1.left.table_alias = crltd_tn
254
+ # j1.right.expr.left.relation.table_alias = crltd_tn
255
+ return if !join_in_select_statement?(o) || !(Arel::Nodes::StringJoin === core.froms)
256
+ j1 = core.froms.left
257
+ j2 = core.froms.right
258
+ return unless Arel::Nodes::OuterJoin === j1 && Arel::Nodes::SqlLiteral === j2 && j2.include?('JOIN ')
259
+ j1_tn = j1.right.name
260
+ j2_tn = j2.match(/JOIN \[(.*)\].*ON/).try(:[],1)
261
+ return unless j1_tn == j2_tn
262
+ on_index = j2.index(' ON ')
263
+ j2.insert on_index, " AS [#{j2_tn}_crltd]"
264
+ j2.sub! "[#{j2_tn}].", "[#{j2_tn}_crltd]."
265
+ end
266
+
267
+ def rowtable_projections(o)
268
+ core = o.cores.first
269
+ if windowed_single_distinct_select_statement?(o) && core.groups.blank?
270
+ tn = table_from_select_statement(o).name
271
+ core.projections.map do |x|
272
+ x.dup.tap do |p|
273
+ p.sub! 'DISTINCT', ''
274
+ p.insert 0, visit(o.limit) if o.limit
275
+ p.gsub! /\[?#{tn}\]?\./, '[__rnt].'
276
+ p.strip!
277
+ end
278
+ end
279
+ elsif single_distinct_select_statement?(o)
280
+ tn = table_from_select_statement(o).name
281
+ core.projections.map do |x|
282
+ x.dup.tap do |p|
283
+ p.sub! 'DISTINCT', "DISTINCT #{visit(o.limit)}".strip if o.limit
284
+ p.gsub! /\[?#{tn}\]?\./, '[__rnt].'
285
+ p.strip!
286
+ end
287
+ end
288
+ elsif join_in_select_statement?(o) && all_projections_aliased_in_select_statement?(o)
289
+ core.projections.map do |x|
290
+ Arel.sql visit(x).split(',').map{ |y| y.split(' AS ').last.strip }.join(', ')
291
+ end
292
+ elsif select_primary_key_sql?(o)
293
+ [Arel.sql("[__rnt].#{quote_column_name(core.projections.first.name)}")]
294
+ else
295
+ [Arel.sql('[__rnt].*')]
296
+ end
297
+ end
298
+
299
+ def rowtable_orders(o)
300
+ core = o.cores.first
301
+ if !o.orders.empty?
302
+ o.orders
303
+ else
304
+ t = table_from_select_statement(o)
305
+ c = t.primary_key || t.columns.first
306
+ [c.asc]
307
+ end.uniq
308
+ end
309
+
310
+ # TODO: We use this for grouping too, maybe make Grouping objects vs SqlLiteral.
311
+ def projection_without_expression(projection)
312
+ Arel.sql(visit(projection).split(',').map do |x|
313
+ x.strip!
314
+ x.sub!(/^(COUNT|SUM|MAX|MIN|AVG)\s*(\((.*)\))?/,'\3')
315
+ x.sub!(/^DISTINCT\s*/,'')
316
+ x.sub!(/TOP\s*\(\d+\)\s*/i,'')
317
+ x.strip
318
+ end.join(', '))
319
+ end
320
+
321
+ end
322
+ end
323
+
324
+ end
325
+
326
+ Arel::Visitors::VISITORS['sqlserver'] = Arel::Visitors::SQLServer
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sic-activerecord-sqlserver-adapter
3
+ version: !ruby/object:Gem::Version
4
+ version: 4.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ken Collins
8
+ - Murray Steele
9
+ - Shawn Balestracci
10
+ - Joe Rafaniello
11
+ - Tom Ward
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+ date: 2014-03-20 00:00:00.000000000 Z
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: activerecord
19
+ requirement: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - "~>"
22
+ - !ruby/object:Gem::Version
23
+ version: 4.0.0
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - "~>"
29
+ - !ruby/object:Gem::Version
30
+ version: 4.0.0
31
+ - !ruby/object:Gem::Dependency
32
+ name: arel
33
+ requirement: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - "~>"
36
+ - !ruby/object:Gem::Version
37
+ version: 4.0.1
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - "~>"
43
+ - !ruby/object:Gem::Version
44
+ version: 4.0.1
45
+ description: ActiveRecord SQL Server Adapter. For SQL Server 2005 And Higher.
46
+ email: ken@metaskills.net
47
+ executables: []
48
+ extensions: []
49
+ extra_rdoc_files: []
50
+ files:
51
+ - CHANGELOG
52
+ - MIT-LICENSE
53
+ - VERSION
54
+ - lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb
55
+ - lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb
56
+ - lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb
57
+ - lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb
58
+ - lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb
59
+ - lib/active_record/connection_adapters/sqlserver/database_limits.rb
60
+ - lib/active_record/connection_adapters/sqlserver/database_statements.rb
61
+ - lib/active_record/connection_adapters/sqlserver/errors.rb
62
+ - lib/active_record/connection_adapters/sqlserver/quoting.rb
63
+ - lib/active_record/connection_adapters/sqlserver/schema_cache.rb
64
+ - lib/active_record/connection_adapters/sqlserver/schema_statements.rb
65
+ - lib/active_record/connection_adapters/sqlserver/showplan.rb
66
+ - lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb
67
+ - lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb
68
+ - lib/active_record/connection_adapters/sqlserver/utils.rb
69
+ - lib/active_record/connection_adapters/sqlserver_adapter.rb
70
+ - lib/active_record/sqlserver_test_case.rb
71
+ - lib/activerecord-sqlserver-adapter.rb
72
+ - lib/arel/select_manager_sqlserver.rb
73
+ - lib/arel/visitors/sqlserver.rb
74
+ homepage: http://github.com/rails-sqlserver/activerecord-sqlserver-adapter
75
+ licenses: []
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project: activerecord-sqlserver-adapter
93
+ rubygems_version: 2.2.2
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: ActiveRecord SQL Server Adapter. For SQL Server 2005 And Higher.
97
+ test_files: []
98
+ has_rdoc: