rainux-2000-2005-adapter 2.2.15

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 (38) hide show
  1. data/CHANGELOG +142 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +157 -0
  4. data/RUNNING_UNIT_TESTS +60 -0
  5. data/Rakefile +52 -0
  6. data/autotest/discover.rb +4 -0
  7. data/autotest/railssqlserver.rb +16 -0
  8. data/autotest/sqlserver.rb +54 -0
  9. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1017 -0
  10. data/lib/core_ext/active_record.rb +150 -0
  11. data/lib/core_ext/dbi.rb +85 -0
  12. data/lib/rails-sqlserver-2000-2005-adapter.rb +1 -0
  13. data/test/cases/aaaa_create_tables_test_sqlserver.rb +19 -0
  14. data/test/cases/adapter_test_sqlserver.rb +627 -0
  15. data/test/cases/attribute_methods_test_sqlserver.rb +33 -0
  16. data/test/cases/basics_test_sqlserver.rb +21 -0
  17. data/test/cases/calculations_test_sqlserver.rb +20 -0
  18. data/test/cases/column_test_sqlserver.rb +264 -0
  19. data/test/cases/connection_test_sqlserver.rb +103 -0
  20. data/test/cases/eager_association_test_sqlserver.rb +42 -0
  21. data/test/cases/execute_procedure_test_sqlserver.rb +33 -0
  22. data/test/cases/inheritance_test_sqlserver.rb +28 -0
  23. data/test/cases/method_scoping_test_sqlserver.rb +28 -0
  24. data/test/cases/migration_test_sqlserver.rb +93 -0
  25. data/test/cases/offset_and_limit_test_sqlserver.rb +89 -0
  26. data/test/cases/pessimistic_locking_test_sqlserver.rb +125 -0
  27. data/test/cases/query_cache_test_sqlserver.rb +24 -0
  28. data/test/cases/schema_dumper_test_sqlserver.rb +61 -0
  29. data/test/cases/specific_schema_test_sqlserver.rb +26 -0
  30. data/test/cases/sqlserver_helper.rb +119 -0
  31. data/test/cases/table_name_test_sqlserver.rb +22 -0
  32. data/test/cases/transaction_test_sqlserver.rb +93 -0
  33. data/test/cases/unicode_test_sqlserver.rb +44 -0
  34. data/test/connections/native_sqlserver/connection.rb +23 -0
  35. data/test/connections/native_sqlserver_odbc/connection.rb +25 -0
  36. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
  37. data/test/schema/sqlserver_specific_schema.rb +88 -0
  38. metadata +96 -0
@@ -0,0 +1,33 @@
1
+ require 'cases/sqlserver_helper'
2
+
3
+ class ExecuteProcedureTestSqlserver < ActiveRecord::TestCase
4
+
5
+ def setup
6
+ @klass = ActiveRecord::Base
7
+ end
8
+
9
+ should 'execute a simple procedure' do
10
+ tables = @klass.execute_procedure :sp_tables
11
+ assert_instance_of Array, tables
12
+ assert_instance_of HashWithIndifferentAccess, tables.first
13
+ end
14
+
15
+ should 'take parameter arguments' do
16
+ tables = @klass.execute_procedure :sp_tables, 'sql_server_chronics'
17
+ table_info = tables.first
18
+ assert_equal 1, tables.size
19
+ assert_equal 'activerecord_unittest', table_info[:TABLE_QUALIFIER], "Table Info: #{table_info.inspect}"
20
+ assert_equal 'TABLE', table_info[:TABLE_TYPE], "Table Info: #{table_info.inspect}"
21
+ end
22
+
23
+ should 'quote bind vars correctly' do
24
+ assert_sql(/EXEC sp_tables '%sql_server%', NULL, NULL, NULL, 1/) do
25
+ @klass.execute_procedure :sp_tables, '%sql_server%', nil, nil, nil, true
26
+ end if sqlserver_2005?
27
+ assert_sql(/EXEC sp_tables '%sql_server%', NULL, NULL, NULL/) do
28
+ @klass.execute_procedure :sp_tables, '%sql_server%', nil, nil, nil
29
+ end if sqlserver_2000?
30
+ end
31
+
32
+
33
+ end
@@ -0,0 +1,28 @@
1
+ require 'cases/sqlserver_helper'
2
+ require 'models/company'
3
+
4
+ class InheritanceTestSqlserver < ActiveRecord::TestCase
5
+ end
6
+
7
+ class InheritanceTest < ActiveRecord::TestCase
8
+
9
+ COERCED_TESTS = [
10
+ :test_eager_load_belongs_to_primary_key_quoting,
11
+ :test_a_bad_type_column
12
+ ]
13
+
14
+ include SqlserverCoercedTest
15
+
16
+ def test_coerced_test_eager_load_belongs_to_primary_key_quoting
17
+ assert_sql(/\(\[companies\].\[id\] = 1\)/) do
18
+ Account.find(1, :include => :firm)
19
+ end
20
+ end
21
+
22
+ def test_coerced_test_a_bad_type_column
23
+ Company.connection.insert "INSERT INTO [companies] ([id], #{QUOTED_TYPE}, [name]) VALUES(100, 'bad_class!', 'Not happening')"
24
+ assert_raises(ActiveRecord::SubclassNotFound) { Company.find(100) }
25
+ end
26
+
27
+
28
+ end
@@ -0,0 +1,28 @@
1
+ require 'cases/sqlserver_helper'
2
+ require 'models/developer'
3
+
4
+ class MethodScopingTestSqlServer < ActiveRecord::TestCase
5
+ end
6
+
7
+ class NestedScopingTest < ActiveRecord::TestCase
8
+
9
+ COERCED_TESTS = [:test_merged_scoped_find]
10
+
11
+ include SqlserverCoercedTest
12
+
13
+ fixtures :developers
14
+
15
+ def test_coerced_test_merged_scoped_find
16
+ poor_jamis = developers(:poor_jamis)
17
+ Developer.with_scope(:find => { :conditions => "salary < 100000" }) do
18
+ Developer.with_scope(:find => { :offset => 1, :order => 'id asc' }) do
19
+ assert_sql /ORDER BY id ASC/ do
20
+ assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc'))
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+
@@ -0,0 +1,93 @@
1
+ require 'cases/sqlserver_helper'
2
+ require 'models/person'
3
+
4
+ class MigrationTestSqlserver < ActiveRecord::TestCase
5
+
6
+ context 'For transactions' do
7
+
8
+ setup do
9
+ @connection = ActiveRecord::Base.connection
10
+ @trans_test_table1 = 'sqlserver_trans_table1'
11
+ @trans_test_table2 = 'sqlserver_trans_table2'
12
+ @trans_tables = [@trans_test_table1,@trans_test_table2]
13
+ end
14
+
15
+ teardown do
16
+ @trans_tables.each do |table_name|
17
+ ActiveRecord::Migration.drop_table(table_name) if @connection.tables.include?(table_name)
18
+ end
19
+ end
20
+
21
+ should 'not create a tables if error in migrations' do
22
+ begin
23
+ ActiveRecord::Migrator.up(SQLSERVER_MIGRATIONS_ROOT+'/transaction_table')
24
+ rescue Exception => e
25
+ assert_match %r|this and all later migrations canceled|, e.message
26
+ end
27
+ assert_does_not_contain @trans_test_table1, @connection.tables
28
+ assert_does_not_contain @trans_test_table2, @connection.tables
29
+ end
30
+
31
+ end
32
+
33
+
34
+ end
35
+
36
+
37
+ class MigrationTest < ActiveRecord::TestCase
38
+
39
+ COERCED_TESTS = [:test_add_column_not_null_without_default]
40
+
41
+ include SqlserverCoercedTest
42
+
43
+ def test_coerced_test_add_column_not_null_without_default
44
+ Person.connection.create_table :testings do |t|
45
+ t.column :foo, :string
46
+ t.column :bar, :string, :null => false
47
+ end
48
+ assert_raises(ActiveRecord::StatementInvalid) do
49
+ Person.connection.execute "INSERT INTO [testings] ([foo], [bar]) VALUES ('hello', NULL)"
50
+ end
51
+ ensure
52
+ Person.connection.drop_table :testings rescue nil
53
+ end
54
+
55
+ end
56
+
57
+ class ChangeTableMigrationsTest < ActiveRecord::TestCase
58
+
59
+ COERCED_TESTS = [:test_string_creates_string_column]
60
+
61
+ include SqlserverCoercedTest
62
+
63
+ def setup
64
+ @connection = Person.connection
65
+ @connection.create_table :delete_me, :force => true do |t|
66
+ end
67
+ end
68
+
69
+ def teardown
70
+ @connection.drop_table :delete_me rescue nil
71
+ end
72
+
73
+ def test_coerced_string_creates_string_column
74
+ with_sqlserver_change_table do |t|
75
+ @connection.expects(:add_column).with(:delete_me, :foo, sqlserver_string_column, {})
76
+ @connection.expects(:add_column).with(:delete_me, :bar, sqlserver_string_column, {})
77
+ t.string :foo, :bar
78
+ end
79
+ end
80
+
81
+ protected
82
+
83
+ def with_sqlserver_change_table
84
+ @connection.change_table :delete_me do |t|
85
+ yield t
86
+ end
87
+ end
88
+
89
+ def sqlserver_string_column
90
+ "#{@connection.native_string_database_type}(255)"
91
+ end
92
+
93
+ end
@@ -0,0 +1,89 @@
1
+ require 'cases/sqlserver_helper'
2
+ require 'models/book'
3
+
4
+ class OffsetAndLimitTestSqlserver < ActiveRecord::TestCase
5
+
6
+ class Account < ActiveRecord::Base; end
7
+
8
+ def setup
9
+ @connection = ActiveRecord::Base.connection
10
+ end
11
+
12
+ context 'When selecting with limit' do
13
+
14
+ setup do
15
+ @select_sql = 'SELECT * FROM schema'
16
+ end
17
+
18
+ should 'alter SQL to limit number of records returned' do
19
+ options = { :limit => 10 }
20
+ assert_equal('SELECT TOP 10 * FROM schema', @connection.add_limit_offset!(@select_sql, options))
21
+ end
22
+
23
+ should 'only allow integers for limit' do
24
+ options = { :limit => 'ten' }
25
+ assert_raise(ArgumentError) {@connection.add_limit_offset!(@select_sql, options) }
26
+ end
27
+
28
+ should 'convert strings which look like integers to integers' do
29
+ options = { :limit => '42' }
30
+ assert_nothing_raised(ArgumentError) {@connection.add_limit_offset!(@select_sql, options)}
31
+ end
32
+
33
+ should 'not allow sql injection via limit' do
34
+ options = { :limit => '1 * FROM schema; DELETE * FROM table; SELECT TOP 10 *'}
35
+ assert_raise(ArgumentError) { @connection.add_limit_offset!(@select_sql, options) }
36
+ end
37
+
38
+ end
39
+
40
+ context 'When selecting with limit and offset' do
41
+
42
+ setup do
43
+ @select_sql = 'SELECT * FROM books'
44
+ @subquery_select_sql = 'SELECT *, (SELECT TOP 1 id FROM books) AS book_id FROM books'
45
+ @books = (1..10).map {|i| Book.create!}
46
+ end
47
+
48
+ teardown do
49
+ @books.each {|b| b.destroy}
50
+ end
51
+
52
+ should 'have limit if offset is passed' do
53
+ options = { :offset => 1 }
54
+ assert_raise(ArgumentError) { @connection.add_limit_offset!(@select_sql, options) }
55
+ end
56
+
57
+ should 'only allow integers for offset' do
58
+ options = { :limit => 10, :offset => 'five' }
59
+ assert_raise(ArgumentError) { @connection.add_limit_offset!(@select_sql, options)}
60
+ end
61
+
62
+ should 'convert strings which look like integers to integers' do
63
+ options = { :limit => 10, :offset => '5' }
64
+ assert_nothing_raised(ArgumentError) {@connection.add_limit_offset!(@select_sql, options)}
65
+ end
66
+
67
+ should 'alter SQL to limit number of records returned offset by specified amount' do
68
+ options = { :limit => 3, :offset => 5 }
69
+ expected_sql = %&SELECT * FROM (SELECT TOP 3 * FROM (SELECT TOP 8 * FROM books) AS tmp1) AS tmp2&
70
+ assert_equal(expected_sql, @connection.add_limit_offset!(@select_sql, options))
71
+ end
72
+
73
+ # Not really sure what an offset sql injection might look like
74
+ should 'not allow sql injection via offset' do
75
+ options = { :limit => 10, :offset => '1 * FROM schema; DELETE * FROM table; SELECT TOP 10 *'}
76
+ assert_raise(ArgumentError) { @connection.add_limit_offset!(@select_sql, options) }
77
+ end
78
+
79
+ should 'not create invalid SQL with subquery SELECTs with TOP' do
80
+ options = { :limit => 5, :offset => 1 }
81
+ expected_sql = "SELECT * FROM (SELECT TOP 5 * FROM (SELECT TOP 6 *, (SELECT TOP 1 id FROM books) AS book_id FROM books) AS tmp1) AS tmp2"
82
+ assert_equal expected_sql, @connection.add_limit_offset!(@subquery_select_sql,options)
83
+ end
84
+
85
+ end
86
+
87
+
88
+ end
89
+
@@ -0,0 +1,125 @@
1
+ require 'cases/sqlserver_helper'
2
+ require 'models/person'
3
+ require 'models/reader'
4
+
5
+ class PessimisticLockingTestSqlserver < ActiveRecord::TestCase
6
+
7
+ self.use_transactional_fixtures = false
8
+ fixtures :people, :readers
9
+
10
+ def setup
11
+ Person.columns; Reader.columns # Avoid introspection queries during tests.
12
+ end
13
+
14
+ context 'For simple finds with default lock option' do
15
+
16
+ should 'lock with simple find' do
17
+ assert_nothing_raised do
18
+ Person.transaction do
19
+ Person.find 1, :lock => true
20
+ end
21
+ end
22
+ end
23
+
24
+ should 'lock with scoped find' do
25
+ assert_nothing_raised do
26
+ Person.transaction do
27
+ Person.with_scope(:find => { :lock => true }) do
28
+ Person.find 1
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ should 'lock with eager find' do
35
+ assert_nothing_raised do
36
+ Person.transaction do
37
+ Person.find 1, :include => :readers, :lock => true
38
+ end
39
+ end
40
+ end
41
+
42
+ should 'reload with lock when #lock! called' do
43
+ assert_nothing_raised do
44
+ Person.transaction do
45
+ person = Person.find 1
46
+ old, person.first_name = person.first_name, 'fooman'
47
+ person.lock!
48
+ assert_equal old, person.first_name
49
+ end
50
+ end
51
+ end
52
+
53
+ should 'simply add lock to find all' do
54
+ assert_sql %r|SELECT \* FROM \[people\] WITH \(NOLOCK\)| do
55
+ Person.all(:lock => 'WITH (NOLOCK)')
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ context 'For paginated finds' do
62
+
63
+ setup do
64
+ 20.times { |n| Person.create!(:first_name => "Thing_#{n}") }
65
+ end
66
+
67
+ should 'cope with un-locked paginated results' do
68
+ tally_not_locked = %r|SELECT count\(\*\) as TotalRows from \(SELECT TOP 1000000000 \* FROM \[people\]\s+WITH \(NOLOCK\) \) tally|
69
+ inner_tmp_not_locked = %r|SELECT TOP 15 \* FROM \[people\] WITH \(NOLOCK\)|
70
+ # Currently association limiting is not locked like the parent.
71
+ association_limiting_not_locked = %r|SELECT \[readers\]\.\* FROM \[readers\] WITH \(NOLOCK\) WHERE \(\[readers\]\.person_id IN \(1,2,3,4,5\)\)|
72
+ assert_sql(tally_not_locked,inner_tmp_not_locked) do
73
+ Person.all(:include => :readers, :lock => 'WITH (NOLOCK)', :limit => 5, :offset => 10)
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+
80
+ context 'For dueling concurrent connections' do
81
+
82
+ use_concurrent_connections
83
+
84
+ should 'no locks does not wait' do
85
+ first, second = duel { Person.find 1 }
86
+ assert first.end > second.end
87
+ end
88
+
89
+ should 'that second lock waits' do
90
+ assert [0.2, 1, 5].any? { |zzz|
91
+ first, second = duel(zzz) { Person.find 1, :lock => true }
92
+ second.end > first.end
93
+ }
94
+ end
95
+
96
+ end
97
+
98
+
99
+ protected
100
+
101
+ def duel(zzz = 5)
102
+ t0, t1, t2, t3 = nil, nil, nil, nil
103
+ a = Thread.new do
104
+ t0 = Time.now
105
+ Person.transaction do
106
+ yield
107
+ sleep zzz # block thread 2 for zzz seconds
108
+ end
109
+ t1 = Time.now
110
+ end
111
+ b = Thread.new do
112
+ sleep zzz / 2.0 # ensure thread 1 tx starts first
113
+ t2 = Time.now
114
+ Person.transaction { yield }
115
+ t3 = Time.now
116
+ end
117
+ a.join
118
+ b.join
119
+ assert t1 > t0 + zzz
120
+ assert t2 > t0
121
+ assert t3 > t2
122
+ [t0.to_f..t1.to_f, t2.to_f..t3.to_f]
123
+ end
124
+
125
+ end
@@ -0,0 +1,24 @@
1
+ require 'cases/sqlserver_helper'
2
+ require 'models/task'
3
+
4
+ class QueryCacheTestSqlserver < ActiveRecord::TestCase
5
+ end
6
+
7
+ class QueryCacheTest < ActiveRecord::TestCase
8
+
9
+ COERCED_TESTS = [:test_cache_does_not_wrap_string_results_in_arrays]
10
+
11
+ include SqlserverCoercedTest
12
+
13
+ fixtures :tasks
14
+
15
+
16
+ def test_coerced_test_cache_does_not_wrap_string_results_in_arrays
17
+ Task.cache do
18
+ assert_instance_of Fixnum, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+
@@ -0,0 +1,61 @@
1
+ require 'cases/sqlserver_helper'
2
+
3
+ class SchemaDumperTestSqlserver < ActiveRecord::TestCase
4
+
5
+ setup :find_all_tables
6
+
7
+ context 'For primary keys' do
8
+
9
+ should 'honor nonstandards' do
10
+ table_dump('movies') do |output|
11
+ match = output.match(%r{create_table "movies"(.*)do})
12
+ assert_not_nil(match, "nonstandardpk table not found")
13
+ assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved"
14
+ end
15
+ end
16
+
17
+ end
18
+
19
+ context 'For integers' do
20
+
21
+ should 'include limit constraint that match logic for smallint and bigint in #extract_limit' do
22
+ table_dump('integer_limits') do |output|
23
+ assert_match %r{c_int_1.*:limit => 2}, output
24
+ assert_match %r{c_int_2.*:limit => 2}, output
25
+ assert_match %r{c_int_3.*}, output
26
+ assert_match %r{c_int_4.*}, output
27
+ assert_no_match %r{c_int_3.*:limit}, output
28
+ assert_no_match %r{c_int_4.*:limit}, output
29
+ assert_match %r{c_int_5.*:limit => 8}, output
30
+ assert_match %r{c_int_6.*:limit => 8}, output
31
+ assert_match %r{c_int_7.*:limit => 8}, output
32
+ assert_match %r{c_int_8.*:limit => 8}, output
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+
39
+
40
+ private
41
+
42
+ def find_all_tables
43
+ @all_tables ||= ActiveRecord::Base.connection.tables
44
+ end
45
+
46
+ def standard_dump(ignore_tables = [])
47
+ stream = StringIO.new
48
+ ActiveRecord::SchemaDumper.ignore_tables = [*ignore_tables]
49
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
50
+ stream.string
51
+ end
52
+
53
+ def table_dump(*table_names)
54
+ stream = StringIO.new
55
+ ActiveRecord::SchemaDumper.ignore_tables = @all_tables-table_names
56
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
57
+ yield stream.string
58
+ stream.string
59
+ end
60
+
61
+ end