activerecord-sqlserver-adapter 2.3.10 → 2.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG 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