sic-activerecord-sqlserver-adapter 4.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.
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: