activerecord-sqlserver-adapter 2.3.10 → 2.3.11

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,4 +1,9 @@
1
1
 
2
+ * 2.3.11
3
+
4
+ * Add TinyTds/dblib connection mode. [Ken Collins]
5
+
6
+
2
7
  * 2.3.10
3
8
 
4
9
  * Fix DSN'less code. [Erik Bryn]
data/Rakefile CHANGED
@@ -22,7 +22,7 @@ task :test => ['test:odbc']
22
22
 
23
23
  namespace :test do
24
24
 
25
- ['odbc','adonet'].each do |mode|
25
+ ['dblib','odbc','adonet'].each do |mode|
26
26
 
27
27
  Rake::TestTask.new(mode) do |t|
28
28
  t.libs = test_libs(mode)
@@ -44,7 +44,7 @@ end
44
44
 
45
45
  namespace :profile do
46
46
 
47
- ['odbc','adonet'].each do |mode|
47
+ ['dblib','odbc','adonet'].each do |mode|
48
48
  namespace mode.to_sym do
49
49
 
50
50
  Dir.glob("test/profile/*_profile_case.rb").sort.each do |test_file|
@@ -12,6 +12,9 @@ module ActiveRecord
12
12
  config.reverse_merge! :mode => :odbc, :host => 'localhost', :username => 'sa', :password => ''
13
13
  mode = config[:mode].to_s.downcase.underscore.to_sym
14
14
  case mode
15
+ when :dblib
16
+ raise ArgumentError, 'Missing :dataserver configuration.' unless config.has_key?(:dataserver)
17
+ require_library_or_gem 'tiny_tds'
15
18
  when :odbc
16
19
  require_library_or_gem 'odbc' unless defined?(ODBC)
17
20
  require 'active_record/connection_adapters/sqlserver_adapter/core_ext/odbc'
@@ -173,16 +176,18 @@ module ActiveRecord
173
176
  class SQLServerAdapter < AbstractAdapter
174
177
 
175
178
  ADAPTER_NAME = 'SQLServer'.freeze
176
- VERSION = '2.3.10'.freeze
179
+ VERSION = '2.3.11'.freeze
177
180
  DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+(\d{4})/
178
181
  SUPPORTED_VERSIONS = [2000,2005,2008].freeze
179
182
  LIMITABLE_TYPES = ['string','integer','float','char','nchar','varchar','nvarchar'].to_set.freeze
180
183
  QUOTED_TRUE, QUOTED_FALSE = '1', '0'
181
184
  LOST_CONNECTION_EXCEPTIONS = {
185
+ :dblib => ['TinyTds::Error'],
182
186
  :odbc => ['ODBC::Error'],
183
187
  :adonet => ['TypeError','System::Data::SqlClient::SqlException']
184
188
  }
185
189
  LOST_CONNECTION_MESSAGES = {
190
+ :dblib => [/closed connection/],
186
191
  :odbc => [/link failure/, /server failed/, /connection was already closed/, /invalid handle/i],
187
192
  :adonet => [/current state is closed/, /network-related/]
188
193
  }
@@ -348,6 +353,15 @@ module ActiveRecord
348
353
  # CONNECTION MANAGEMENT ====================================#
349
354
 
350
355
  def active?
356
+ connected = case @connection_options[:mode]
357
+ when :dblib
358
+ !@connection.closed?
359
+ when :odbc
360
+ true
361
+ else :adonet
362
+ true
363
+ end
364
+ return false if !connected
351
365
  raw_connection_do("SELECT 1")
352
366
  true
353
367
  rescue *lost_connection_exceptions
@@ -362,6 +376,8 @@ module ActiveRecord
362
376
 
363
377
  def disconnect!
364
378
  case @connection_options[:mode]
379
+ when :dblib
380
+ @connection.close rescue nil
365
381
  when :odbc
366
382
  @connection.disconnect rescue nil
367
383
  else :adonet
@@ -414,8 +430,8 @@ module ActiveRecord
414
430
  end
415
431
 
416
432
  def execute(sql, name = nil, skip_logging = false)
417
- if table_name = query_requires_identity_insert?(sql)
418
- with_identity_insert_enabled(table_name) { do_execute(sql,name) }
433
+ if id_insert_table_name = query_requires_identity_insert?(sql)
434
+ with_identity_insert_enabled(id_insert_table_name) { do_execute(sql,name) }
419
435
  else
420
436
  do_execute(sql,name)
421
437
  end
@@ -424,19 +440,27 @@ module ActiveRecord
424
440
  def execute_procedure(proc_name, *variables)
425
441
  vars = variables.map{ |v| quote(v) }.join(', ')
426
442
  sql = "EXEC #{proc_name} #{vars}".strip
443
+ name = 'Execute Procedure'
427
444
  results = []
428
- log(sql,'Execute Procedure') do
429
- raw_connection_run(sql) do |handle|
430
- get_rows = lambda {
431
- rows = handle_to_names_and_values handle, :fetch => :all
432
- rows.each_with_index { |r,i| rows[i] = r.with_indifferent_access }
433
- results << rows
434
- }
435
- get_rows.call
436
- while handle_more_results?(handle)
445
+ case @connection_options[:mode]
446
+ when :dblib
447
+ results << select(sql, name).map { |r| r.with_indifferent_access }
448
+ when :odbc
449
+ log(sql, name) do
450
+ raw_connection_run(sql) do |handle|
451
+ get_rows = lambda {
452
+ rows = handle_to_names_and_values handle, :fetch => :all
453
+ rows.each_with_index { |r,i| rows[i] = r.with_indifferent_access }
454
+ results << rows
455
+ }
437
456
  get_rows.call
457
+ while handle_more_results?(handle)
458
+ get_rows.call
459
+ end
438
460
  end
439
461
  end
462
+ when :adonet
463
+ results << select(sql, name).map { |r| r.with_indifferent_access }
440
464
  end
441
465
  results.many? ? results : results.first
442
466
  end
@@ -813,6 +837,23 @@ module ActiveRecord
813
837
  def connect
814
838
  config = @connection_options
815
839
  @connection = case @connection_options[:mode]
840
+ when :dblib
841
+ appname = config[:appname] || Rails.application.class.name.split('::').first rescue nil
842
+ encoding = config[:encoding].present? ? config[:encoding] : nil
843
+ TinyTds::Client.new({
844
+ :dataserver => config[:dataserver],
845
+ :username => config[:username],
846
+ :password => config[:password],
847
+ :database => config[:database],
848
+ :appname => appname,
849
+ :login_timeout => config[:dblib_login_timeout],
850
+ :timeout => config[:dblib_timeout],
851
+ :encoding => encoding
852
+ }).tap do |client|
853
+ client.execute("SET ANSI_DEFAULTS ON").do
854
+ client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
855
+ client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
856
+ end
816
857
  when :odbc
817
858
  if config[:dsn].include?(';')
818
859
  driver = ODBC::Driver.new.tap do |d|
@@ -889,6 +930,8 @@ module ActiveRecord
889
930
  def raw_connection_run(sql)
890
931
  with_auto_reconnect do
891
932
  case @connection_options[:mode]
933
+ when :dblib
934
+ @connection.execute(sql)
892
935
  when :odbc
893
936
  block_given? ? @connection.run_block(sql) { |handle| yield(handle) } : @connection.run(sql)
894
937
  else :adonet
@@ -899,15 +942,21 @@ module ActiveRecord
899
942
 
900
943
  def raw_connection_do(sql)
901
944
  case @connection_options[:mode]
945
+ when :dblib
946
+ @insert_sql ? @connection.execute(sql).insert : @connection.execute(sql).do
902
947
  when :odbc
903
948
  @connection.do(sql)
904
949
  else :adonet
905
950
  @connection.create_command.tap{ |cmd| cmd.command_text = sql }.execute_non_query
906
951
  end
952
+ ensure
953
+ @insert_sql = false
954
+ @update_sql = false
907
955
  end
908
956
 
909
957
  def finish_statement_handle(handle)
910
958
  case @connection_options[:mode]
959
+ when :dblib
911
960
  when :odbc
912
961
  handle.drop if handle && handle.respond_to?(:drop) && !handle.finished?
913
962
  when :adonet
@@ -924,12 +973,24 @@ module ActiveRecord
924
973
  end
925
974
 
926
975
  def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
927
- super || select_value("SELECT SCOPE_IDENTITY() AS Ident")
976
+ @insert_sql = true
977
+ case @connection_options[:mode]
978
+ when :dblib
979
+ execute(sql, name) || id_value
980
+ else
981
+ super || select_value("SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident")
982
+ end
928
983
  end
929
984
 
930
985
  def update_sql(sql, name = nil)
931
- execute(sql, name)
932
- select_value('SELECT @@ROWCOUNT AS AffectedRows')
986
+ @update_sql = true
987
+ case @connection_options[:mode]
988
+ when :dblib
989
+ execute(sql, name)
990
+ else
991
+ execute(sql, name)
992
+ select_value('SELECT @@ROWCOUNT AS AffectedRows')
993
+ end
933
994
  end
934
995
 
935
996
  def info_schema_query
@@ -955,6 +1016,7 @@ module ActiveRecord
955
1016
 
956
1017
  def handle_more_results?(handle)
957
1018
  case @connection_options[:mode]
1019
+ when :dblib
958
1020
  when :odbc
959
1021
  handle.more_results
960
1022
  when :adonet
@@ -964,12 +1026,23 @@ module ActiveRecord
964
1026
 
965
1027
  def handle_to_names_and_values(handle, options={})
966
1028
  case @connection_options[:mode]
1029
+ when :dblib
1030
+ handle_to_names_and_values_dblib(handle, options)
967
1031
  when :odbc
968
1032
  handle_to_names_and_values_odbc(handle, options)
969
1033
  when :adonet
970
1034
  handle_to_names_and_values_adonet(handle, options)
971
1035
  end
972
1036
  end
1037
+
1038
+ def handle_to_names_and_values_dblib(handle, options={})
1039
+ query_options = {}.tap do |qo|
1040
+ qo[:timezone] = ActiveRecord::Base.default_timezone || :utc
1041
+ qo[:first] = true if options[:fetch] == :one
1042
+ qo[:as] = options[:fetch] == :rows ? :array : :hash
1043
+ end
1044
+ handle.each(query_options)
1045
+ end
973
1046
 
974
1047
  def handle_to_names_and_values_odbc(handle, options={})
975
1048
  @connection.use_utc = ActiveRecord::Base.default_timezone == :utc if @connection_supports_native_types
@@ -12,24 +12,6 @@ class ConnectionTestSqlserver < ActiveRecord::TestCase
12
12
  end
13
13
 
14
14
 
15
- should 'return finished ODBC statment handle from #execute without block' do
16
- assert_all_statements_used_are_closed do
17
- @connection.execute('SELECT * FROM [topics]')
18
- end
19
- end
20
-
21
- should 'finish ODBC statment handle from #execute with block' do
22
- assert_all_statements_used_are_closed do
23
- @connection.execute('SELECT * FROM [topics]') { }
24
- end
25
- end
26
-
27
- should 'finish connection from #raw_select' do
28
- assert_all_statements_used_are_closed do
29
- @connection.send(:raw_select,'SELECT * FROM [topics]')
30
- end
31
- end
32
-
33
15
  should 'affect rows' do
34
16
  topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
35
17
  updated = Topic.update(topic_data.keys, topic_data.values)
@@ -39,38 +21,6 @@ class ConnectionTestSqlserver < ActiveRecord::TestCase
39
21
  assert_equal 2, Topic.delete([1, 2])
40
22
  end
41
23
 
42
- should 'execute without block closes statement' do
43
- assert_all_statements_used_are_closed do
44
- @connection.execute("SELECT 1")
45
- end
46
- end
47
-
48
- should 'execute with block closes statement' do
49
- assert_all_statements_used_are_closed do
50
- @connection.execute("SELECT 1") do |sth|
51
- assert !sth.finished?, "Statement should still be alive within block"
52
- end
53
- end
54
- end
55
-
56
- should 'insert with identity closes statement' do
57
- assert_all_statements_used_are_closed do
58
- @connection.insert("INSERT INTO accounts ([id], [firm_id],[credit_limit]) values (999, 1, 50)")
59
- end
60
- end
61
-
62
- should 'insert without identity closes statement' do
63
- assert_all_statements_used_are_closed do
64
- @connection.insert("INSERT INTO accounts ([firm_id],[credit_limit]) values (1, 50)")
65
- end
66
- end
67
-
68
- should 'active closes statement' do
69
- assert_all_statements_used_are_closed do
70
- @connection.active?
71
- end
72
- end
73
-
74
24
  should 'allow usage of :database connection option to remove setting from dsn' do
75
25
  assert_equal 'activerecord_unittest', @connection.current_database
76
26
  begin
@@ -82,6 +32,61 @@ class ConnectionTestSqlserver < ActiveRecord::TestCase
82
32
  end
83
33
  end
84
34
 
35
+ context 'ODBC connection management' do
36
+
37
+ should 'return finished ODBC statement handle from #execute without block' do
38
+ assert_all_odbc_statements_used_are_closed do
39
+ @connection.execute('SELECT * FROM [topics]')
40
+ end
41
+ end
42
+
43
+ should 'finish ODBC statement handle from #execute with block' do
44
+ assert_all_odbc_statements_used_are_closed do
45
+ @connection.execute('SELECT * FROM [topics]') { }
46
+ end
47
+ end
48
+
49
+ should 'finish connection from #raw_select' do
50
+ assert_all_odbc_statements_used_are_closed do
51
+ @connection.send(:raw_select,'SELECT * FROM [topics]')
52
+ end
53
+ end
54
+
55
+ should 'execute without block closes statement' do
56
+ assert_all_odbc_statements_used_are_closed do
57
+ @connection.execute("SELECT 1")
58
+ end
59
+ end
60
+
61
+ should 'execute with block closes statement' do
62
+ assert_all_odbc_statements_used_are_closed do
63
+ @connection.execute("SELECT 1") do |sth|
64
+ assert !sth.finished?, "Statement should still be alive within block"
65
+ end
66
+ end
67
+ end
68
+
69
+ should 'insert with identity closes statement' do
70
+ assert_all_odbc_statements_used_are_closed do
71
+ @connection.insert("INSERT INTO accounts ([id], [firm_id],[credit_limit]) values (999, 1, 50)")
72
+ end
73
+ end
74
+
75
+ should 'insert without identity closes statement' do
76
+ assert_all_odbc_statements_used_are_closed do
77
+ @connection.insert("INSERT INTO accounts ([firm_id],[credit_limit]) values (1, 50)")
78
+ end
79
+ end
80
+
81
+ should 'active closes statement' do
82
+ assert_all_odbc_statements_used_are_closed do
83
+ @connection.active?
84
+ end
85
+ end
86
+
87
+ end if connection_mode_odbc?
88
+
89
+
85
90
  context 'Connection management' do
86
91
 
87
92
  setup do
@@ -116,21 +121,18 @@ class ConnectionTestSqlserver < ActiveRecord::TestCase
116
121
 
117
122
  private
118
123
 
119
- def assert_all_statements_used_are_closed(&block)
124
+ def assert_all_odbc_statements_used_are_closed(&block)
125
+ odbc = @connection.raw_connection.class.parent
120
126
  existing_handles = []
121
- ObjectSpace.each_object(ODBC::Statement) {|handle| existing_handles << handle}
122
- GC.disable
127
+ ObjectSpace.each_object(odbc::Statement) { |h| existing_handles << h }
128
+ existing_handle_ids = existing_handles.map(&:object_id)
129
+ assert existing_handles.all?(&:finished?), "Somewhere before the block some statements were not closed"
130
+ GC.disable
123
131
  yield
124
132
  used_handles = []
125
- ObjectSpace.each_object(ODBC::Statement) {|handle| used_handles << handle unless existing_handles.include? handle}
126
- assert_block "No statements were used within given block" do
127
- used_handles.size > 0
128
- end
129
- ObjectSpace.each_object(ODBC::Statement) do |handle|
130
- assert_block "Statement should have been closed within given block" do
131
- handle.finished?
132
- end
133
- end
133
+ ObjectSpace.each_object(odbc::Statement) { |h| used_handles << h unless existing_handle_ids.include?(h.object_id) }
134
+ assert used_handles.size > 0, "No statements were used within given block"
135
+ assert used_handles.all?(&:finished?), "Statement should have been closed within given block"
134
136
  ensure
135
137
  GC.enable
136
138
  end
@@ -20,15 +20,23 @@ class ExecuteProcedureTestSqlserver < ActiveRecord::TestCase
20
20
  assert_equal 'TABLE', table_info[:TABLE_TYPE], "Table Info: #{table_info.inspect}"
21
21
  end
22
22
 
23
- should 'allow multiple result sets to be returned' do
24
- results1, results2 = @klass.execute_procedure('sp_helpconstraint','accounts')
25
- assert_instance_of Array, results1
26
- assert_instance_of HashWithIndifferentAccess, results1.first
27
- assert results1.first['Object Name']
28
- assert_instance_of Array, results2
29
- assert_instance_of HashWithIndifferentAccess, results2.first
30
- assert results2.first['constraint_name']
31
- assert results2.first['constraint_type']
23
+ if connection_mode_odbc?
24
+
25
+ should 'allow multiple result sets to be returned' do
26
+ results1, results2 = @klass.execute_procedure('sp_helpconstraint','accounts')
27
+ assert_instance_of Array, results1
28
+ assert_instance_of HashWithIndifferentAccess, results1.first
29
+ assert results1.first['Object Name']
30
+ assert_instance_of Array, results2
31
+ assert_instance_of HashWithIndifferentAccess, results2.first
32
+ assert results2.first['constraint_name']
33
+ assert results2.first['constraint_type']
34
+ end
35
+
36
+ else
37
+
38
+ should 'allow multiple result sets to be returned'
39
+
32
40
  end
33
41
 
34
42
 
@@ -104,6 +104,7 @@ end
104
104
  module ActiveRecord
105
105
  class TestCase < ActiveSupport::TestCase
106
106
  class << self
107
+ def connection_mode_dblib? ; ActiveRecord::Base.connection.instance_variable_get(:@connection_options)[:mode] == :dblib ; end
107
108
  def connection_mode_odbc? ; ActiveRecord::Base.connection.instance_variable_get(:@connection_options)[:mode] == :odbc ; end
108
109
  def sqlserver_2000? ; ActiveRecord::Base.connection.sqlserver_2000? ; end
109
110
  def sqlserver_2005? ; ActiveRecord::Base.connection.sqlserver_2005? ; end
@@ -122,6 +123,8 @@ module ActiveRecord
122
123
  end
123
124
  assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found in:\n#{$queries_executed.inspect}"
124
125
  end
126
+ def connection_mode_dblib? ; self.class.connection_mode_dblib? ; end
127
+ def connection_mode_odbc? ; self.class.connection_mode_odbc? ; end
125
128
  def sqlserver_2000? ; self.class.sqlserver_2000? ; end
126
129
  def sqlserver_2005? ; self.class.sqlserver_2005? ; end
127
130
  def sqlserver_2008? ; self.class.sqlserver_2008? ; end
@@ -2,7 +2,8 @@ print "Using SQLServer via ADONET\n"
2
2
  require_dependency 'models/course'
3
3
  require 'logger'
4
4
 
5
- ActiveRecord::Base.logger = Logger.new("debug.log")
5
+ ActiveRecord::Base.logger = Logger.new(File.expand_path(File.join(SQLSERVER_TEST_ROOT,'debug.log')))
6
+ ActiveRecord::Base.logger.level = 0
6
7
 
7
8
  ActiveRecord::Base.configurations = {
8
9
  'arunit' => {
@@ -2,7 +2,8 @@ print "Using SQLServer via ODBC\n"
2
2
  require_dependency 'models/course'
3
3
  require 'logger'
4
4
 
5
- ActiveRecord::Base.logger = Logger.new("debug.log")
5
+ ActiveRecord::Base.logger = Logger.new(File.expand_path(File.join(SQLSERVER_TEST_ROOT,'debug.log')))
6
+ ActiveRecord::Base.logger.level = 0
6
7
 
7
8
  ActiveRecord::Base.configurations = {
8
9
  'arunit' => {
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-sqlserver-adapter
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 21
5
5
  prerelease: false
6
6
  segments:
7
7
  - 2
8
8
  - 3
9
- - 10
10
- version: 2.3.10
9
+ - 11
10
+ version: 2.3.11
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ken Collins
@@ -19,7 +19,7 @@ autorequire:
19
19
  bindir: bin
20
20
  cert_chain: []
21
21
 
22
- date: 2010-09-21 00:00:00 -04:00
22
+ date: 2010-10-17 00:00:00 -04:00
23
23
  default_executable:
24
24
  dependencies:
25
25
  - !ruby/object:Gem::Dependency