activerecord-sqlserver-adapter 3.0.19 → 3.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -27
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +14 -23
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +4 -23
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +83 -89
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +11 -1
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +14 -15
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +25 -37
- data/lib/arel/visitors/sqlserver.rb +45 -32
- metadata +33 -61
- data/lib/active_record/connection_adapters/sqlserver/query_cache.rb +0 -17
data/CHANGELOG
CHANGED
@@ -1,32 +1,7 @@
|
|
1
1
|
|
2
|
-
* 3.0.
|
2
|
+
* 3.1.0.beta1 *
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
* Create a #configure_application_name method that can be overridden for unique TinyTDS app names
|
7
|
-
to show up in SQL Server's activity monitor.
|
8
|
-
|
9
|
-
* Fixed the #finish_statement_handle to cancel the TinyTDS connection if needed.
|
10
|
-
|
11
|
-
* Fix the #indexes method to fail gracefully if sp_helptext permissions fail.
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
* 3.0.18 *
|
16
|
-
|
17
|
-
* Make #rollback_db_transaction smarter.
|
18
|
-
|
19
|
-
|
20
|
-
* 3.0.17 *
|
21
|
-
|
22
|
-
* Allow :limit/:offset to be used with fully qualified table and column in :select.
|
23
|
-
|
24
|
-
* Fix test_count_explicit_columns.
|
25
|
-
|
26
|
-
|
27
|
-
* 3.0.16 *
|
28
|
-
|
29
|
-
* Allow complex order objects to not be molested by our visitor overrides. Fixes #99
|
4
|
+
It just works! Uses "EXEC sp_executesql ..." for just about everything now!
|
30
5
|
|
31
6
|
|
32
7
|
* 3.0.15 *
|
@@ -1,21 +1,25 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'active_record/base'
|
1
3
|
require 'active_record/version'
|
2
|
-
require 'active_support/
|
4
|
+
require 'active_support/concern'
|
5
|
+
require 'active_support/core_ext/class/attribute'
|
3
6
|
|
4
7
|
module ActiveRecord
|
5
8
|
module ConnectionAdapters
|
6
9
|
module Sqlserver
|
7
10
|
module CoreExt
|
8
11
|
module ActiveRecord
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
|
13
|
+
extend ActiveSupport::Concern
|
14
|
+
|
15
|
+
included do
|
16
|
+
class_attribute :coerced_sqlserver_date_columns, :coerced_sqlserver_time_columns
|
17
|
+
self.coerced_sqlserver_date_columns = Set.new
|
18
|
+
self.coerced_sqlserver_time_columns = Set.new
|
15
19
|
end
|
16
20
|
|
17
21
|
module ClassMethods
|
18
|
-
|
22
|
+
|
19
23
|
def execute_procedure(proc_name, *variables)
|
20
24
|
if connection.respond_to?(:execute_procedure)
|
21
25
|
connection.execute_procedure(proc_name,*variables)
|
@@ -25,24 +29,11 @@ module ActiveRecord
|
|
25
29
|
end
|
26
30
|
|
27
31
|
def coerce_sqlserver_date(*attributes)
|
28
|
-
|
32
|
+
self.coerced_sqlserver_date_columns += attributes.map(&:to_s)
|
29
33
|
end
|
30
34
|
|
31
35
|
def coerce_sqlserver_time(*attributes)
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
def coerced_sqlserver_date_columns
|
36
|
-
read_inheritable_attribute(:coerced_sqlserver_date_columns) || []
|
37
|
-
end
|
38
|
-
|
39
|
-
def coerced_sqlserver_time_columns
|
40
|
-
read_inheritable_attribute(:coerced_sqlserver_time_columns) || []
|
41
|
-
end
|
42
|
-
|
43
|
-
def reset_column_information_with_sqlserver_cache_support
|
44
|
-
connection.send(:initialize_sqlserver_caches) if connection.respond_to?(:sqlserver?)
|
45
|
-
reset_column_information_without_sqlserver_cache_support
|
36
|
+
self.coerced_sqlserver_time_columns += attributes.map(&:to_s)
|
46
37
|
end
|
47
38
|
|
48
39
|
end
|
@@ -4,22 +4,13 @@ module ActiveRecord
|
|
4
4
|
module CoreExt
|
5
5
|
module ODBC
|
6
6
|
|
7
|
-
module TimeStamp
|
8
|
-
|
9
|
-
def to_sqlserver_string
|
10
|
-
date, time, nanoseconds = to_s.split(' ')
|
11
|
-
"#{date} #{time}.#{sprintf("%03d",nanoseconds.to_i/1000000)}"
|
12
|
-
end
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
7
|
module Statement
|
17
8
|
|
18
9
|
def finished?
|
19
10
|
begin
|
20
11
|
connected?
|
21
12
|
false
|
22
|
-
rescue
|
13
|
+
rescue ::ODBC::Error
|
23
14
|
true
|
24
15
|
end
|
25
16
|
end
|
@@ -28,14 +19,6 @@ module ActiveRecord
|
|
28
19
|
|
29
20
|
module Database
|
30
21
|
|
31
|
-
def self.parent_modules
|
32
|
-
@parent_module ||= ['ODBC','ODBC_UTF8','ODBC_NONE'].map{ |odbc_ns| odbc_ns.constantize rescue nil }.compact
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.parent_modules_error_exceptions
|
36
|
-
@parent_modules_error_exceptions ||= parent_modules.map { |odbc_ns| "::#{odbc_ns}::Error".constantize }
|
37
|
-
end
|
38
|
-
|
39
22
|
def run_block(*args)
|
40
23
|
yield sth = run(*args)
|
41
24
|
sth.drop
|
@@ -49,9 +32,7 @@ module ActiveRecord
|
|
49
32
|
end
|
50
33
|
end
|
51
34
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
ns::Database.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::ODBC::Database
|
56
|
-
end
|
35
|
+
|
36
|
+
ODBC::Statement.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::ODBC::Statement
|
37
|
+
ODBC::Database.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::ODBC::Database
|
57
38
|
|
@@ -3,26 +3,47 @@ module ActiveRecord
|
|
3
3
|
module Sqlserver
|
4
4
|
module DatabaseStatements
|
5
5
|
|
6
|
-
def select_one(sql, name = nil)
|
7
|
-
result = raw_select sql, name, :fetch => :one
|
8
|
-
(result && result.first.present?) ? result.first : nil
|
9
|
-
end
|
10
|
-
|
11
6
|
def select_rows(sql, name = nil)
|
12
|
-
raw_select sql, name, :fetch => :rows
|
7
|
+
raw_select sql, name, [], :fetch => :rows
|
13
8
|
end
|
14
9
|
|
15
|
-
def execute(sql, name = nil
|
10
|
+
def execute(sql, name = nil)
|
16
11
|
if id_insert_table_name = query_requires_identity_insert?(sql)
|
17
12
|
with_identity_insert_enabled(id_insert_table_name) { do_execute(sql,name) }
|
18
13
|
else
|
19
14
|
do_execute(sql,name)
|
20
15
|
end
|
21
16
|
end
|
17
|
+
|
18
|
+
def exec_query(sql, name = 'SQL', binds = [], sqlserver_options = {})
|
19
|
+
if id_insert_table_name = sqlserver_options[:insert] ? query_requires_identity_insert?(sql) : nil
|
20
|
+
with_identity_insert_enabled(id_insert_table_name) { do_exec_query(sql, name, binds) }
|
21
|
+
else
|
22
|
+
do_exec_query(sql, name, binds)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def exec_insert(sql, name, binds)
|
27
|
+
exec_query sql, name, binds, :insert => true
|
28
|
+
end
|
29
|
+
|
30
|
+
def exec_delete(sql, name, binds)
|
31
|
+
sql << "; SELECT @@ROWCOUNT AS AffectedRows"
|
32
|
+
super.rows.first.first
|
33
|
+
end
|
34
|
+
|
35
|
+
def exec_update(sql, name, binds)
|
36
|
+
sql << "; SELECT @@ROWCOUNT AS AffectedRows"
|
37
|
+
super.rows.first.first
|
38
|
+
end
|
22
39
|
|
23
40
|
def outside_transaction?
|
24
41
|
info_schema_query { select_value("SELECT @@TRANCOUNT") == 0 }
|
25
42
|
end
|
43
|
+
|
44
|
+
def supports_statement_cache?
|
45
|
+
true
|
46
|
+
end
|
26
47
|
|
27
48
|
def begin_db_transaction
|
28
49
|
do_execute "BEGIN TRANSACTION"
|
@@ -33,7 +54,7 @@ module ActiveRecord
|
|
33
54
|
end
|
34
55
|
|
35
56
|
def rollback_db_transaction
|
36
|
-
do_execute "
|
57
|
+
do_execute "ROLLBACK TRANSACTION" rescue nil
|
37
58
|
end
|
38
59
|
|
39
60
|
def create_savepoint
|
@@ -55,15 +76,8 @@ module ActiveRecord
|
|
55
76
|
"DEFAULT VALUES"
|
56
77
|
end
|
57
78
|
|
58
|
-
def
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
|
63
|
-
match_data = where_sql.match(/^(.*?[\]\) ])WHERE[\[\( ]/)
|
64
|
-
limit = match_data[1]
|
65
|
-
where_sql.sub!(limit,'')
|
66
|
-
"WHERE #{quoted_primary_key} IN (SELECT #{limit} #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
|
79
|
+
def case_sensitive_modifier(node)
|
80
|
+
node.acts_like?(:string) ? Arel::Nodes::Bin.new(node) : node
|
67
81
|
end
|
68
82
|
|
69
83
|
# === SQLServer Specific ======================================== #
|
@@ -190,29 +204,17 @@ module ActiveRecord
|
|
190
204
|
|
191
205
|
protected
|
192
206
|
|
193
|
-
def select(sql, name = nil)
|
194
|
-
|
207
|
+
def select(sql, name = nil, binds = [])
|
208
|
+
exec_query(sql, name, binds).to_a
|
195
209
|
end
|
196
210
|
|
197
|
-
def
|
198
|
-
|
199
|
-
|
200
|
-
when :dblib
|
201
|
-
execute(sql, name) || id_value
|
202
|
-
else
|
203
|
-
super || select_value("SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident")
|
204
|
-
end
|
211
|
+
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
|
212
|
+
sql = "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"# unless binds.empty?
|
213
|
+
super
|
205
214
|
end
|
206
|
-
|
207
|
-
def
|
208
|
-
|
209
|
-
case @connection_options[:mode]
|
210
|
-
when :dblib
|
211
|
-
execute(sql, name)
|
212
|
-
else
|
213
|
-
execute(sql, name)
|
214
|
-
select_value('SELECT @@ROWCOUNT AS AffectedRows')
|
215
|
-
end
|
215
|
+
|
216
|
+
def last_inserted_id(result)
|
217
|
+
super || select_value("SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident")
|
216
218
|
end
|
217
219
|
|
218
220
|
# === SQLServer Specific ======================================== #
|
@@ -230,24 +232,50 @@ module ActiveRecord
|
|
230
232
|
end
|
231
233
|
end
|
232
234
|
|
235
|
+
def do_exec_query(sql, name, binds)
|
236
|
+
statement = quote(sql)
|
237
|
+
names_and_types = []
|
238
|
+
params = []
|
239
|
+
binds.each_with_index do |(column,value),index|
|
240
|
+
ar_column = column.is_a?(ActiveRecord::ConnectionAdapters::Column)
|
241
|
+
next if ar_column && column.sql_type == 'timestamp'
|
242
|
+
v = value
|
243
|
+
names_and_types << if ar_column
|
244
|
+
v = value.to_i if column.is_integer?
|
245
|
+
"@#{index} #{column.sql_type_for_statement}"
|
246
|
+
elsif column.acts_like?(:string)
|
247
|
+
"@#{index} nvarchar(max)"
|
248
|
+
elsif column.is_a?(Fixnum)
|
249
|
+
v = value.to_i
|
250
|
+
"@#{index} int"
|
251
|
+
else
|
252
|
+
raise "Unknown bind columns. We can account for this."
|
253
|
+
end
|
254
|
+
quoted_value = ar_column ? quote(v,column) : quote(v,nil)
|
255
|
+
params << "@#{index} = #{quoted_value}"
|
256
|
+
end
|
257
|
+
sql = "EXEC sp_executesql #{statement}"
|
258
|
+
sql << ", #{quote(names_and_types.join(', '))}, #{params.join(', ')}" unless binds.empty?
|
259
|
+
raw_select sql, name, binds, :ar_result => true
|
260
|
+
end
|
261
|
+
|
233
262
|
def raw_connection_do(sql)
|
234
263
|
case @connection_options[:mode]
|
235
264
|
when :dblib
|
236
|
-
@
|
265
|
+
@connection.execute(sql).do
|
237
266
|
when :odbc
|
238
267
|
@connection.do(sql)
|
239
268
|
else :adonet
|
240
269
|
@connection.create_command.tap{ |cmd| cmd.command_text = sql }.execute_non_query
|
241
270
|
end
|
242
271
|
ensure
|
243
|
-
@insert_sql = false
|
244
272
|
@update_sql = false
|
245
273
|
end
|
246
274
|
|
247
275
|
# === SQLServer Specific (Selecting) ============================ #
|
248
276
|
|
249
|
-
def raw_select(sql, name=nil, options={})
|
250
|
-
log(sql,name) do
|
277
|
+
def raw_select(sql, name=nil, binds=[], options={})
|
278
|
+
log(sql,name,binds) do
|
251
279
|
begin
|
252
280
|
handle = raw_connection_run(sql)
|
253
281
|
handle_to_names_and_values(handle, options)
|
@@ -294,56 +322,25 @@ module ActiveRecord
|
|
294
322
|
def handle_to_names_and_values_dblib(handle, options={})
|
295
323
|
query_options = {}.tap do |qo|
|
296
324
|
qo[:timezone] = ActiveRecord::Base.default_timezone || :utc
|
297
|
-
qo[:
|
298
|
-
qo[:as] = options[:fetch] == :rows ? :array : :hash
|
325
|
+
qo[:as] = (options[:ar_result] || options[:fetch] == :rows) ? :array : :hash
|
299
326
|
end
|
300
|
-
handle.each(query_options)
|
327
|
+
results = handle.each(query_options)
|
328
|
+
options[:ar_result] ? ActiveRecord::Result.new(handle.fields, results) : results
|
301
329
|
end
|
302
330
|
|
303
331
|
def handle_to_names_and_values_odbc(handle, options={})
|
304
|
-
@connection.use_utc = ActiveRecord::Base.default_timezone == :utc
|
305
|
-
|
306
|
-
|
307
|
-
if @connection_supports_native_types
|
308
|
-
if options[:fetch] == :all
|
309
|
-
handle.each_hash || []
|
310
|
-
else
|
311
|
-
row = handle.fetch_hash
|
312
|
-
rows = row ? [row] : [[]]
|
313
|
-
end
|
314
|
-
else
|
315
|
-
rows = if options[:fetch] == :all
|
316
|
-
handle.fetch_all || []
|
317
|
-
else
|
318
|
-
row = handle.fetch
|
319
|
-
row ? [row] : [[]]
|
320
|
-
end
|
321
|
-
names = handle.columns(true).map{ |c| c.name }
|
322
|
-
names_and_values = []
|
323
|
-
rows.each do |row|
|
324
|
-
h = {}
|
325
|
-
i = 0
|
326
|
-
while i < row.size
|
327
|
-
v = row[i]
|
328
|
-
h[names[i]] = v.respond_to?(:to_sqlserver_string) ? v.to_sqlserver_string : v
|
329
|
-
i += 1
|
330
|
-
end
|
331
|
-
names_and_values << h
|
332
|
-
end
|
333
|
-
names_and_values
|
334
|
-
end
|
335
|
-
when :rows
|
332
|
+
@connection.use_utc = ActiveRecord::Base.default_timezone == :utc
|
333
|
+
if options[:ar_result]
|
334
|
+
columns = handle.columns(true).map { |c| c.name }
|
336
335
|
rows = handle.fetch_all || []
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
end
|
336
|
+
ActiveRecord::Result.new(columns, rows)
|
337
|
+
else
|
338
|
+
case options[:fetch]
|
339
|
+
when :all
|
340
|
+
handle.each_hash || []
|
341
|
+
when :rows
|
342
|
+
handle.fetch_all || []
|
345
343
|
end
|
346
|
-
rows
|
347
344
|
end
|
348
345
|
end
|
349
346
|
|
@@ -352,7 +349,6 @@ module ActiveRecord
|
|
352
349
|
names = []
|
353
350
|
rows = []
|
354
351
|
fields_named = options[:fetch] == :rows
|
355
|
-
one_row_only = options[:fetch] == :one
|
356
352
|
while handle.read
|
357
353
|
row = []
|
358
354
|
handle.visible_field_count.times do |row_index|
|
@@ -371,7 +367,6 @@ module ActiveRecord
|
|
371
367
|
end
|
372
368
|
row << value
|
373
369
|
names << handle.get_name(row_index).to_s unless fields_named
|
374
|
-
break if one_row_only
|
375
370
|
end
|
376
371
|
rows << row
|
377
372
|
fields_named = true
|
@@ -398,8 +393,7 @@ module ActiveRecord
|
|
398
393
|
|
399
394
|
def finish_statement_handle(handle)
|
400
395
|
case @connection_options[:mode]
|
401
|
-
when :dblib
|
402
|
-
handle.cancel if handle
|
396
|
+
when :dblib
|
403
397
|
when :odbc
|
404
398
|
handle.drop if handle && handle.respond_to?(:drop) && !handle.finished?
|
405
399
|
when :adonet
|
@@ -9,7 +9,7 @@ module ActiveRecord
|
|
9
9
|
|
10
10
|
LOST_CONNECTION_EXCEPTIONS = {
|
11
11
|
:dblib => ['TinyTds::Error'],
|
12
|
-
:odbc => ['ODBC::Error'
|
12
|
+
:odbc => ['ODBC::Error'],
|
13
13
|
:adonet => ['TypeError','System::Data::SqlClient::SqlException']
|
14
14
|
}.freeze
|
15
15
|
|
@@ -15,6 +15,8 @@ module ActiveRecord
|
|
15
15
|
else
|
16
16
|
super
|
17
17
|
end
|
18
|
+
when nil
|
19
|
+
column.respond_to?(:sql_type) && column.sql_type == 'timestamp' ? 'DEFAULT' : super
|
18
20
|
else
|
19
21
|
super
|
20
22
|
end
|
@@ -26,12 +28,20 @@ module ActiveRecord
|
|
26
28
|
|
27
29
|
def quote_column_name(name)
|
28
30
|
@sqlserver_quoted_column_and_table_names[name] ||=
|
29
|
-
name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n
|
31
|
+
name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n}]" }.join('.')
|
30
32
|
end
|
31
33
|
|
32
34
|
def quote_table_name(name)
|
33
35
|
quote_column_name(name)
|
34
36
|
end
|
37
|
+
|
38
|
+
def substitute_at(column, index)
|
39
|
+
if column.respond_to?(:sql_type) && column.sql_type == 'timestamp'
|
40
|
+
nil
|
41
|
+
else
|
42
|
+
Arel.sql "@#{index}"
|
43
|
+
end
|
44
|
+
end
|
35
45
|
|
36
46
|
def quoted_true
|
37
47
|
QUOTED_TRUE
|
@@ -20,8 +20,7 @@ module ActiveRecord
|
|
20
20
|
|
21
21
|
def indexes(table_name, name = nil)
|
22
22
|
unquoted_table_name = unqualify_table_name(table_name)
|
23
|
-
|
24
|
-
data.inject([]) do |indexes,index|
|
23
|
+
select("EXEC sp_helpindex #{quote_table_name(unquoted_table_name)}",name).inject([]) do |indexes,index|
|
25
24
|
index = index.with_indifferent_access
|
26
25
|
if index[:index_description] =~ /primary key/
|
27
26
|
indexes
|
@@ -40,8 +39,8 @@ module ActiveRecord
|
|
40
39
|
|
41
40
|
def columns(table_name, name = nil)
|
42
41
|
return [] if table_name.blank?
|
43
|
-
cache_key =
|
44
|
-
|
42
|
+
cache_key = unqualify_table_name(table_name)
|
43
|
+
column_definitions(table_name).collect do |ci|
|
45
44
|
sqlserver_options = ci.except(:name,:default_value,:type,:null).merge(:database_year=>database_year)
|
46
45
|
SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
|
47
46
|
end
|
@@ -199,11 +198,13 @@ module ActiveRecord
|
|
199
198
|
ELSE 1
|
200
199
|
END as is_identity
|
201
200
|
FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS columns
|
202
|
-
WHERE columns.TABLE_NAME =
|
203
|
-
AND columns.TABLE_SCHEMA = #{table_schema.
|
201
|
+
WHERE columns.TABLE_NAME = @0
|
202
|
+
AND columns.TABLE_SCHEMA = #{table_schema.blank? ? "schema_name()" : "@1"}
|
204
203
|
ORDER BY columns.ordinal_position
|
205
204
|
}.gsub(/[ \t\r\n]+/,' ')
|
206
|
-
|
205
|
+
binds = [['table_name', table_name]]
|
206
|
+
binds << ['table_schema',table_schema] unless table_schema.blank?
|
207
|
+
results = info_schema_query { do_exec_query(sql, 'InfoSchema::ColumnDefinitions', binds) }
|
207
208
|
results.collect do |ci|
|
208
209
|
ci = ci.symbolize_keys
|
209
210
|
ci[:type] = case ci[:type]
|
@@ -279,9 +280,9 @@ module ActiveRecord
|
|
279
280
|
end
|
280
281
|
|
281
282
|
def get_table_name(sql)
|
282
|
-
if sql =~ /^\s*
|
283
|
-
$
|
284
|
-
elsif sql =~ /
|
283
|
+
if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)\s+INTO\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
284
|
+
$2 || $3
|
285
|
+
elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
|
285
286
|
$1
|
286
287
|
else
|
287
288
|
nil
|
@@ -352,12 +353,10 @@ module ActiveRecord
|
|
352
353
|
|
353
354
|
def remove_sqlserver_columns_cache_for(table_name)
|
354
355
|
cache_key = unqualify_table_name(table_name)
|
355
|
-
@sqlserver_columns_cache[cache_key] = nil
|
356
356
|
initialize_sqlserver_caches(false)
|
357
357
|
end
|
358
358
|
|
359
359
|
def initialize_sqlserver_caches(reset_columns=true)
|
360
|
-
@sqlserver_columns_cache = {} if reset_columns
|
361
360
|
@sqlserver_views_cache = nil
|
362
361
|
@sqlserver_view_information_cache = {}
|
363
362
|
@sqlserver_quoted_column_and_table_names = {}
|
@@ -369,14 +368,14 @@ module ActiveRecord
|
|
369
368
|
if insert_sql?(sql)
|
370
369
|
table_name = get_table_name(sql)
|
371
370
|
id_column = identity_column(table_name)
|
372
|
-
id_column && sql =~ /^\s*INSERT[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
|
371
|
+
id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
|
373
372
|
else
|
374
373
|
false
|
375
374
|
end
|
376
375
|
end
|
377
376
|
|
378
377
|
def insert_sql?(sql)
|
379
|
-
!(sql =~ /^\s*INSERT/i).nil?
|
378
|
+
!(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil?
|
380
379
|
end
|
381
380
|
|
382
381
|
def with_identity_insert_enabled(table_name)
|
@@ -389,7 +388,7 @@ module ActiveRecord
|
|
389
388
|
|
390
389
|
def set_identity_insert(table_name, enable = true)
|
391
390
|
sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
392
|
-
do_execute
|
391
|
+
do_execute sql,'InfoSchema::SetIdentityInsert'
|
393
392
|
rescue Exception => e
|
394
393
|
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
|
395
394
|
end
|
@@ -5,7 +5,6 @@ require 'active_record/connection_adapters/sqlserver/core_ext/active_record'
|
|
5
5
|
require 'active_record/connection_adapters/sqlserver/database_limits'
|
6
6
|
require 'active_record/connection_adapters/sqlserver/database_statements'
|
7
7
|
require 'active_record/connection_adapters/sqlserver/errors'
|
8
|
-
require 'active_record/connection_adapters/sqlserver/query_cache'
|
9
8
|
require 'active_record/connection_adapters/sqlserver/schema_statements'
|
10
9
|
require 'active_record/connection_adapters/sqlserver/quoting'
|
11
10
|
require 'active_support/core_ext/kernel/requires'
|
@@ -26,16 +25,7 @@ module ActiveRecord
|
|
26
25
|
warn("TinyTds v0.4.3 or higher required. Using #{TinyTds::VERSION}") unless TinyTds::Client.instance_methods.map(&:to_s).include?("active?")
|
27
26
|
when :odbc
|
28
27
|
raise ArgumentError, 'Missing :dsn configuration.' unless config.has_key?(:dsn)
|
29
|
-
|
30
|
-
require_library_or_gem 'odbc'
|
31
|
-
else
|
32
|
-
begin
|
33
|
-
# TODO: [ODBC] Change this to 'odbc_utf8'
|
34
|
-
require_library_or_gem 'odbc'
|
35
|
-
rescue LoadError
|
36
|
-
require_library_or_gem 'odbc'
|
37
|
-
end
|
38
|
-
end unless ['::ODBC','::ODBC_UTF8','::ODBC_NONE'].any? { |odbc_ns| odbc_ns.constantize rescue nil }
|
28
|
+
require_library_or_gem 'odbc'
|
39
29
|
require 'active_record/connection_adapters/sqlserver/core_ext/odbc'
|
40
30
|
when :adonet
|
41
31
|
require 'System.Data'
|
@@ -61,7 +51,7 @@ module ActiveRecord
|
|
61
51
|
module ConnectionAdapters
|
62
52
|
|
63
53
|
class SQLServerColumn < Column
|
64
|
-
|
54
|
+
|
65
55
|
def initialize(name, default, sql_type = nil, null = true, sqlserver_options = {})
|
66
56
|
@sqlserver_options = sqlserver_options.symbolize_keys
|
67
57
|
super(name, default, sql_type, null)
|
@@ -87,6 +77,14 @@ module ActiveRecord
|
|
87
77
|
@sql_type =~ /nvarchar|ntext|nchar/i
|
88
78
|
end
|
89
79
|
|
80
|
+
def is_integer?
|
81
|
+
@sql_type =~ /int/i
|
82
|
+
end
|
83
|
+
|
84
|
+
def sql_type_for_statement
|
85
|
+
is_integer? ? sql_type.sub(/\(\d+\)/,'') : sql_type
|
86
|
+
end
|
87
|
+
|
90
88
|
def default_function
|
91
89
|
@sqlserver_options[:default_function]
|
92
90
|
end
|
@@ -160,11 +158,10 @@ module ActiveRecord
|
|
160
158
|
include Sqlserver::DatabaseStatements
|
161
159
|
include Sqlserver::SchemaStatements
|
162
160
|
include Sqlserver::DatabaseLimits
|
163
|
-
include Sqlserver::QueryCache
|
164
161
|
include Sqlserver::Errors
|
165
162
|
|
166
163
|
ADAPTER_NAME = 'SQLServer'.freeze
|
167
|
-
VERSION = '3.0.
|
164
|
+
VERSION = '3.1.0.beta1'.freeze
|
168
165
|
DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+"?(\d{4}|\w+)"?/
|
169
166
|
SUPPORTED_VERSIONS = [2005,2008,2010,2011].freeze
|
170
167
|
|
@@ -265,6 +262,11 @@ module ActiveRecord
|
|
265
262
|
remove_database_connections_and_rollback { }
|
266
263
|
end
|
267
264
|
|
265
|
+
def clear_cache!
|
266
|
+
# This requires db admin perms and I'm not even sure it is a good idea.
|
267
|
+
# raw_connection_do "DBCC FREEPROCCACHE WITH NO_INFOMSGS" rescue nil
|
268
|
+
end
|
269
|
+
|
268
270
|
# === Abstract Adapter (Misc Support) =========================== #
|
269
271
|
|
270
272
|
def pk_and_sequence_for(table_name)
|
@@ -331,7 +333,7 @@ module ActiveRecord
|
|
331
333
|
end
|
332
334
|
|
333
335
|
def cs_equality_operator
|
334
|
-
@@cs_equality_operator || 'COLLATE Latin1_General_CS_AS_WS
|
336
|
+
@@cs_equality_operator || 'COLLATE Latin1_General_CS_AS_WS'
|
335
337
|
end
|
336
338
|
|
337
339
|
|
@@ -358,7 +360,7 @@ module ActiveRecord
|
|
358
360
|
config = @connection_options
|
359
361
|
@connection = case @connection_options[:mode]
|
360
362
|
when :dblib
|
361
|
-
appname = config[:appname] ||
|
363
|
+
appname = config[:appname] || Rails.application.class.name.split('::').first rescue nil
|
362
364
|
login_timeout = config[:login_timeout].present? ? config[:login_timeout].to_i : nil
|
363
365
|
timeout = config[:timeout].present? ? config[:timeout].to_i/1000 : nil
|
364
366
|
encoding = config[:encoding].present? ? config[:encoding] : nil
|
@@ -390,20 +392,20 @@ module ActiveRecord
|
|
390
392
|
end
|
391
393
|
end
|
392
394
|
when :odbc
|
393
|
-
odbc = ['::ODBC','::ODBC_UTF8','::ODBC_NONE'].detect{ |odbc_ns| odbc_ns.constantize rescue nil }.constantize
|
394
395
|
if config[:dsn].include?(';')
|
395
|
-
driver =
|
396
|
+
driver = ODBC::Driver.new.tap do |d|
|
396
397
|
d.name = config[:dsn_name] || 'Driver1'
|
397
398
|
d.attrs = config[:dsn].split(';').map{ |atr| atr.split('=') }.reject{ |kv| kv.size != 2 }.inject({}){ |h,kv| k,v = kv ; h[k] = v ; h }
|
398
399
|
end
|
399
|
-
|
400
|
+
ODBC::Database.new.drvconnect(driver)
|
400
401
|
else
|
401
|
-
|
402
|
-
end.tap do |c|
|
403
|
-
|
402
|
+
ODBC.connect config[:dsn], config[:username], config[:password]
|
403
|
+
end.tap do |c|
|
404
|
+
begin
|
404
405
|
c.use_time = true
|
405
406
|
c.use_utc = ActiveRecord::Base.default_timezone == :utc
|
406
|
-
|
407
|
+
rescue Exception => e
|
408
|
+
warn "Ruby ODBC v0.99992 or higher is required."
|
407
409
|
end
|
408
410
|
end
|
409
411
|
when :adonet
|
@@ -423,24 +425,10 @@ module ActiveRecord
|
|
423
425
|
connection.open
|
424
426
|
end
|
425
427
|
end
|
426
|
-
configure_connection
|
427
428
|
rescue
|
428
429
|
raise unless @auto_connecting
|
429
430
|
end
|
430
431
|
|
431
|
-
# Override this method so every connection can be configured to your needs.
|
432
|
-
# For example:
|
433
|
-
# do_execute "SET TEXTSIZE #{64.megabytes}"
|
434
|
-
# do_execute "SET CONCAT_NULL_YIELDS_NULL ON"
|
435
|
-
def configure_connection
|
436
|
-
end
|
437
|
-
|
438
|
-
# Override this method so every connection can have a unique name. Max 30 characters. Used by TinyTDS only.
|
439
|
-
# For example:
|
440
|
-
# "myapp_#{$$}_#{Thread.current.object_id}".to(29)
|
441
|
-
def configure_application_name
|
442
|
-
end
|
443
|
-
|
444
432
|
def remove_database_connections_and_rollback(database=nil)
|
445
433
|
database ||= current_database
|
446
434
|
do_execute "ALTER DATABASE #{quote_table_name(database)} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
|
@@ -4,10 +4,6 @@ module Arel
|
|
4
4
|
|
5
5
|
module Nodes
|
6
6
|
|
7
|
-
# See the SelectManager#lock method on why this custom class is needed.
|
8
|
-
class LockWithSQLServer < Arel::Nodes::Unary
|
9
|
-
end
|
10
|
-
|
11
7
|
# Extending the Ordering class to be comparrison friendly which allows us to call #uniq on a
|
12
8
|
# collection of them. See SelectManager#order for more details.
|
13
9
|
class Ordering < Arel::Nodes::Binary
|
@@ -26,8 +22,6 @@ module Arel
|
|
26
22
|
|
27
23
|
class SelectManager < Arel::TreeManager
|
28
24
|
|
29
|
-
alias :lock_without_sqlserver :lock
|
30
|
-
|
31
25
|
# Getting real Ordering objects is very important for us. We need to be able to call #uniq on
|
32
26
|
# a colleciton of them reliably as well as using their true object attributes to mutate them
|
33
27
|
# to grouping objects for the inner sql during a select statment with an offset/rownumber. So this
|
@@ -41,8 +35,6 @@ module Arel
|
|
41
35
|
table = Arel::Table.new(x.relation.table_alias || x.relation.name)
|
42
36
|
expr = table[x.name]
|
43
37
|
Arel::Nodes::Ordering.new expr
|
44
|
-
when Arel::Nodes::Ordering
|
45
|
-
x
|
46
38
|
when String
|
47
39
|
x.split(',').map do |s|
|
48
40
|
expr, direction = s.split
|
@@ -60,9 +52,17 @@ module Arel
|
|
60
52
|
|
61
53
|
# A friendly over ride that allows us to put a special lock object that can have a default or pass
|
62
54
|
# custom string hints down. See the visit_Arel_Nodes_LockWithSQLServer delegation method.
|
55
|
+
alias :lock_without_sqlserver :lock
|
63
56
|
def lock(locking=true)
|
64
57
|
if Arel::Visitors::SQLServer === @visitor
|
65
|
-
|
58
|
+
case locking
|
59
|
+
when true
|
60
|
+
locking = Arel.sql('WITH(HOLDLOCK, ROWLOCK)')
|
61
|
+
when Arel::Nodes::SqlLiteral
|
62
|
+
when String
|
63
|
+
locking = Arel.sql locking
|
64
|
+
end
|
65
|
+
@ast.lock = Arel::Nodes::Lock.new(locking)
|
66
66
|
self
|
67
67
|
else
|
68
68
|
lock_without_sqlserver(locking)
|
@@ -96,22 +96,14 @@ module Arel
|
|
96
96
|
"TOP (#{visit o.expr})"
|
97
97
|
end
|
98
98
|
|
99
|
-
def visit_Arel_Nodes_Lock
|
100
|
-
|
99
|
+
def visit_Arel_Nodes_Lock(o)
|
100
|
+
visit o.expr
|
101
101
|
end
|
102
102
|
|
103
|
-
def
|
104
|
-
|
105
|
-
when TrueClass
|
106
|
-
"WITH(HOLDLOCK, ROWLOCK)"
|
107
|
-
when String
|
108
|
-
o.expr
|
109
|
-
else
|
110
|
-
""
|
111
|
-
end
|
103
|
+
def visit_Arel_Nodes_Bin(o)
|
104
|
+
"#{visit o.expr} #{@engine.connection.cs_equality_operator}"
|
112
105
|
end
|
113
106
|
|
114
|
-
|
115
107
|
# SQLServer ToSql/Visitor (Additions)
|
116
108
|
|
117
109
|
def visit_Arel_Nodes_SelectStatementWithOutOffset(o, windowed=false)
|
@@ -129,6 +121,8 @@ module Arel
|
|
129
121
|
expr = Arel.sql projection_without_expression(x.expr)
|
130
122
|
x.descending? ? Arel::Nodes::Max.new([expr]) : Arel::Nodes::Min.new([expr])
|
131
123
|
end
|
124
|
+
elsif top_one_everything_for_through_join?(o)
|
125
|
+
projections = projections.map { |x| projection_without_expression(x) }
|
132
126
|
end
|
133
127
|
[ ("SELECT" if !windowed),
|
134
128
|
(visit(o.limit) if o.limit && !windowed),
|
@@ -178,9 +172,8 @@ module Arel
|
|
178
172
|
# SQLServer Helpers
|
179
173
|
|
180
174
|
def source_with_lock_for_select_statement(o)
|
181
|
-
# TODO: [ARel 2.2] Use #from/#source vs. #froms
|
182
175
|
core = o.cores.first
|
183
|
-
source =
|
176
|
+
source = visit(core.source).strip if core.source
|
184
177
|
if source && o.lock
|
185
178
|
lock = visit o.lock
|
186
179
|
index = source.match(/FROM [\w\[\]\.]+/)[0].length
|
@@ -220,11 +213,21 @@ module Arel
|
|
220
213
|
((p1.respond_to?(:distinct) && p1.distinct) ||
|
221
214
|
p1.respond_to?(:include?) && p1.include?('DISTINCT'))
|
222
215
|
end
|
216
|
+
|
217
|
+
def single_distinct_select_everything_statement?(o)
|
218
|
+
single_distinct_select_statement?(o) && visit(o.cores.first.projections.first).ends_with?(".*")
|
219
|
+
end
|
220
|
+
|
221
|
+
def top_one_everything_for_through_join?(o)
|
222
|
+
single_distinct_select_everything_statement?(o) &&
|
223
|
+
(o.limit && !o.offset) &&
|
224
|
+
join_in_select_statement?(o)
|
225
|
+
end
|
223
226
|
|
224
227
|
def all_projections_aliased_in_select_statement?(o)
|
225
228
|
projections = o.cores.first.projections
|
226
229
|
projections.all? do |x|
|
227
|
-
x.split(',').all? { |y| y.include?(' AS ') }
|
230
|
+
visit(x).split(',').all? { |y| y.include?(' AS ') }
|
228
231
|
end
|
229
232
|
end
|
230
233
|
|
@@ -235,14 +238,15 @@ module Arel
|
|
235
238
|
|
236
239
|
def eager_limiting_select_statement?(o)
|
237
240
|
core = o.cores.first
|
238
|
-
single_distinct_select_statement?(o) &&
|
241
|
+
single_distinct_select_statement?(o) &&
|
242
|
+
(o.limit && !o.offset) &&
|
243
|
+
core.groups.empty? &&
|
244
|
+
!single_distinct_select_everything_statement?(o)
|
239
245
|
end
|
240
246
|
|
241
247
|
def join_in_select_statement?(o)
|
242
248
|
core = o.cores.first
|
243
|
-
|
244
|
-
# core.source.right.any? { |x| Arel::Nodes::Join === x }
|
245
|
-
Arel::Nodes::Join === core.froms
|
249
|
+
core.source.right.any? { |x| Arel::Nodes::Join === x }
|
246
250
|
end
|
247
251
|
|
248
252
|
def complex_count_sql?(o)
|
@@ -291,11 +295,20 @@ module Arel
|
|
291
295
|
end
|
292
296
|
elsif join_in_select_statement?(o) && all_projections_aliased_in_select_statement?(o)
|
293
297
|
core.projections.map do |x|
|
294
|
-
Arel.sql x.split(',').map{ |y| y.split(' AS ').last.strip }.join(', ')
|
298
|
+
Arel.sql visit(x).split(',').map{ |y| y.split(' AS ').last.strip }.join(', ')
|
295
299
|
end
|
300
|
+
elsif function_select_statement?(o)
|
301
|
+
[Arel.star]
|
296
302
|
else
|
297
|
-
|
298
|
-
|
303
|
+
core.projections.map do |x|
|
304
|
+
if x.respond_to?(:relation)
|
305
|
+
x = x.dup
|
306
|
+
x.relation = x.relation.dup
|
307
|
+
x.relation.instance_variable_set :@table_alias, Arel.sql('[__rnt].*')
|
308
|
+
else
|
309
|
+
x
|
310
|
+
end
|
311
|
+
end
|
299
312
|
end
|
300
313
|
end
|
301
314
|
|
@@ -312,7 +325,7 @@ module Arel
|
|
312
325
|
|
313
326
|
# TODO: We use this for grouping too, maybe make Grouping objects vs SqlLiteral.
|
314
327
|
def projection_without_expression(projection)
|
315
|
-
Arel.sql(projection.split(',').map do |x|
|
328
|
+
Arel.sql(visit(projection).split(',').map do |x|
|
316
329
|
x.strip!
|
317
330
|
x.sub!(/^(COUNT|SUM|MAX|MIN|AVG)\s*(\((.*)\))?/,'\3')
|
318
331
|
x.sub!(/^DISTINCT\s*/,'')
|
metadata
CHANGED
@@ -1,15 +1,10 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-sqlserver-adapter
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 3
|
8
|
-
- 0
|
9
|
-
- 19
|
10
|
-
version: 3.0.19
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 3.1.0.beta1
|
5
|
+
prerelease: 6
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Ken Collins
|
14
9
|
- Murray Steele
|
15
10
|
- Shawn Balestracci
|
@@ -18,51 +13,38 @@ authors:
|
|
18
13
|
autorequire:
|
19
14
|
bindir: bin
|
20
15
|
cert_chain: []
|
21
|
-
|
22
|
-
date: 2011-10-23 00:00:00 -04:00
|
16
|
+
date: 2011-05-06 00:00:00.000000000 -04:00
|
23
17
|
default_executable:
|
24
|
-
dependencies:
|
25
|
-
- !ruby/object:Gem::Dependency
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
26
20
|
name: activerecord
|
27
|
-
|
28
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
21
|
+
requirement: &2171145400 !ruby/object:Gem::Requirement
|
29
22
|
none: false
|
30
|
-
requirements:
|
23
|
+
requirements:
|
31
24
|
- - ~>
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
hash: 1
|
34
|
-
segments:
|
35
|
-
- 3
|
36
|
-
- 0
|
37
|
-
- 3
|
25
|
+
- !ruby/object:Gem::Version
|
38
26
|
version: 3.0.3
|
39
27
|
type: :runtime
|
40
|
-
version_requirements: *id001
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: arel
|
43
28
|
prerelease: false
|
44
|
-
|
29
|
+
version_requirements: *2171145400
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: arel
|
32
|
+
requirement: &2171144680 !ruby/object:Gem::Requirement
|
45
33
|
none: false
|
46
|
-
requirements:
|
34
|
+
requirements:
|
47
35
|
- - ~>
|
48
|
-
- !ruby/object:Gem::Version
|
49
|
-
hash: 1
|
50
|
-
segments:
|
51
|
-
- 2
|
52
|
-
- 0
|
53
|
-
- 7
|
36
|
+
- !ruby/object:Gem::Version
|
54
37
|
version: 2.0.7
|
55
38
|
type: :runtime
|
56
|
-
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: *2171144680
|
57
41
|
description: SQL Server 2005 and 2008 Adapter For ActiveRecord
|
58
42
|
email: ken@metaskills.net
|
59
43
|
executables: []
|
60
|
-
|
61
44
|
extensions: []
|
62
|
-
|
63
|
-
extra_rdoc_files:
|
45
|
+
extra_rdoc_files:
|
64
46
|
- README.rdoc
|
65
|
-
files:
|
47
|
+
files:
|
66
48
|
- CHANGELOG
|
67
49
|
- MIT-LICENSE
|
68
50
|
- README.rdoc
|
@@ -71,7 +53,6 @@ files:
|
|
71
53
|
- lib/active_record/connection_adapters/sqlserver/database_limits.rb
|
72
54
|
- lib/active_record/connection_adapters/sqlserver/database_statements.rb
|
73
55
|
- lib/active_record/connection_adapters/sqlserver/errors.rb
|
74
|
-
- lib/active_record/connection_adapters/sqlserver/query_cache.rb
|
75
56
|
- lib/active_record/connection_adapters/sqlserver/quoting.rb
|
76
57
|
- lib/active_record/connection_adapters/sqlserver/schema_statements.rb
|
77
58
|
- lib/active_record/connection_adapters/sqlserver_adapter.rb
|
@@ -80,37 +61,28 @@ files:
|
|
80
61
|
has_rdoc: true
|
81
62
|
homepage: http://github.com/rails-sqlserver/activerecord-sqlserver-adapter
|
82
63
|
licenses: []
|
83
|
-
|
84
64
|
post_install_message:
|
85
|
-
rdoc_options:
|
65
|
+
rdoc_options:
|
86
66
|
- --main
|
87
67
|
- README.rdoc
|
88
|
-
require_paths:
|
68
|
+
require_paths:
|
89
69
|
- lib
|
90
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
71
|
none: false
|
92
|
-
requirements:
|
93
|
-
- -
|
94
|
-
- !ruby/object:Gem::Version
|
95
|
-
|
96
|
-
|
97
|
-
- 0
|
98
|
-
version: "0"
|
99
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
77
|
none: false
|
101
|
-
requirements:
|
102
|
-
- -
|
103
|
-
- !ruby/object:Gem::Version
|
104
|
-
|
105
|
-
segments:
|
106
|
-
- 0
|
107
|
-
version: "0"
|
78
|
+
requirements:
|
79
|
+
- - ! '>'
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 1.3.1
|
108
82
|
requirements: []
|
109
|
-
|
110
83
|
rubyforge_project: activerecord-sqlserver-adapter
|
111
84
|
rubygems_version: 1.6.2
|
112
85
|
signing_key:
|
113
86
|
specification_version: 3
|
114
87
|
summary: SQL Server 2005 and 2008 Adapter For ActiveRecord.
|
115
88
|
test_files: []
|
116
|
-
|