activerecord-sqlserver-adapter 2.3.24 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/CHANGELOG +5 -108
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +33 -61
  4. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +57 -0
  5. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +57 -0
  6. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
  7. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +336 -0
  8. data/lib/active_record/connection_adapters/sqlserver/errors.rb +33 -0
  9. data/lib/active_record/connection_adapters/sqlserver/query_cache.rb +17 -0
  10. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +61 -0
  11. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +373 -0
  12. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +131 -1121
  13. data/lib/arel/engines/sql/compilers/sqlserver_compiler.rb +267 -0
  14. metadata +26 -76
  15. data/RUNNING_UNIT_TESTS +0 -31
  16. data/Rakefile +0 -60
  17. data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/active_record.rb +0 -151
  18. data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/odbc.rb +0 -40
  19. data/test/cases/aaaa_create_tables_test_sqlserver.rb +0 -19
  20. data/test/cases/adapter_test_sqlserver.rb +0 -755
  21. data/test/cases/attribute_methods_test_sqlserver.rb +0 -33
  22. data/test/cases/basics_test_sqlserver.rb +0 -86
  23. data/test/cases/calculations_test_sqlserver.rb +0 -20
  24. data/test/cases/column_test_sqlserver.rb +0 -354
  25. data/test/cases/connection_test_sqlserver.rb +0 -148
  26. data/test/cases/eager_association_test_sqlserver.rb +0 -42
  27. data/test/cases/execute_procedure_test_sqlserver.rb +0 -35
  28. data/test/cases/inheritance_test_sqlserver.rb +0 -28
  29. data/test/cases/method_scoping_test_sqlserver.rb +0 -28
  30. data/test/cases/migration_test_sqlserver.rb +0 -108
  31. data/test/cases/named_scope_test_sqlserver.rb +0 -21
  32. data/test/cases/offset_and_limit_test_sqlserver.rb +0 -108
  33. data/test/cases/pessimistic_locking_test_sqlserver.rb +0 -125
  34. data/test/cases/query_cache_test_sqlserver.rb +0 -24
  35. data/test/cases/schema_dumper_test_sqlserver.rb +0 -72
  36. data/test/cases/specific_schema_test_sqlserver.rb +0 -154
  37. data/test/cases/sqlserver_helper.rb +0 -140
  38. data/test/cases/table_name_test_sqlserver.rb +0 -38
  39. data/test/cases/transaction_test_sqlserver.rb +0 -93
  40. data/test/cases/unicode_test_sqlserver.rb +0 -54
  41. data/test/cases/validations_test_sqlserver.rb +0 -18
  42. data/test/connections/native_sqlserver/connection.rb +0 -26
  43. data/test/connections/native_sqlserver_odbc/connection.rb +0 -28
  44. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +0 -11
  45. data/test/schema/sqlserver_specific_schema.rb +0 -113
@@ -1,148 +0,0 @@
1
- require 'cases/sqlserver_helper'
2
- require 'models/reply'
3
-
4
- class ConnectionTestSqlserver < ActiveRecord::TestCase
5
-
6
- self.use_transactional_fixtures = false
7
-
8
- fixtures :topics, :accounts
9
-
10
- def setup
11
- @connection = ActiveRecord::Base.connection
12
- end
13
-
14
-
15
- should 'affect rows' do
16
- topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
17
- updated = Topic.update(topic_data.keys, topic_data.values)
18
- assert_equal 2, updated.size
19
- assert_equal "1 updated", Topic.find(1).content
20
- assert_equal "2 updated", Topic.find(2).content
21
- assert_equal 2, Topic.delete([1, 2])
22
- end
23
-
24
- should 'allow usage of :database connection option to remove setting from dsn' do
25
- assert_equal 'activerecord_unittest', @connection.current_database
26
- begin
27
- @connection.use_database('activerecord_unittest2')
28
- assert_equal 'activerecord_unittest2', @connection.current_database
29
- ensure
30
- @connection.use_database
31
- assert_equal 'activerecord_unittest', @connection.current_database, 'Would default back to connection options'
32
- end
33
- end
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
-
90
- context 'Connection management' do
91
-
92
- setup do
93
- assert @connection.active?
94
- end
95
-
96
- should 'be able to disconnect and reconnect at will' do
97
- @connection.disconnect!
98
- assert !@connection.active?
99
- @connection.reconnect!
100
- assert @connection.active?
101
- end
102
-
103
- should 'auto reconnect when setting is on' do
104
- with_auto_connect(true) do
105
- @connection.disconnect!
106
- assert_nothing_raised() { Topic.count }
107
- assert @connection.active?
108
- end
109
- end
110
-
111
- should 'not auto reconnect when setting is off' do
112
- with_auto_connect(false) do
113
- @connection.disconnect!
114
- assert_raise(ActiveRecord::StatementInvalid) { Topic.count }
115
- end
116
- end
117
-
118
- end
119
-
120
-
121
-
122
- private
123
-
124
- def assert_all_odbc_statements_used_are_closed(&block)
125
- odbc = @connection.raw_connection.class.parent
126
- existing_handles = []
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
131
- yield
132
- used_handles = []
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"
136
- ensure
137
- GC.enable
138
- end
139
-
140
- def with_auto_connect(boolean)
141
- existing = ActiveRecord::ConnectionAdapters::SQLServerAdapter.auto_connect
142
- ActiveRecord::ConnectionAdapters::SQLServerAdapter.auto_connect = boolean
143
- yield
144
- ensure
145
- ActiveRecord::ConnectionAdapters::SQLServerAdapter.auto_connect = existing
146
- end
147
-
148
- end
@@ -1,42 +0,0 @@
1
- require 'cases/sqlserver_helper'
2
- require 'models/post'
3
- require 'models/author'
4
- require 'models/comment'
5
-
6
- class EagerAssociationTestSqlserver < ActiveRecord::TestCase
7
- end
8
-
9
- class EagerAssociationTest < ActiveRecord::TestCase
10
-
11
- COERCED_TESTS = [
12
- :test_count_with_include,
13
- :test_eager_with_has_many_and_limit_and_high_offset_and_multiple_array_conditions,
14
- :test_eager_with_has_many_and_limit_and_high_offset_and_multiple_hash_conditions
15
- ]
16
-
17
- include SqlserverCoercedTest
18
-
19
- fixtures :authors, :posts, :comments
20
-
21
- def test_coerced_test_count_with_include
22
- assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15")
23
- end
24
-
25
- def test_coerced_eager_with_has_many_and_limit_and_high_offset_and_multiple_array_conditions
26
- assert_queries(2) do
27
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10,
28
- :conditions => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ])
29
- assert_equal 0, posts.size
30
- end
31
- end
32
-
33
- def test_coerced_eager_with_has_many_and_limit_and_high_offset_and_multiple_hash_conditions
34
- assert_queries(2) do
35
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10,
36
- :conditions => { 'authors.name' => 'David', 'comments.body' => 'go crazy' })
37
- assert_equal 0, posts.size
38
- end
39
- end
40
-
41
-
42
- end
@@ -1,35 +0,0 @@
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 tables.first.respond_to?(:keys)
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 (ENV['ARUNIT_DB_NAME'] || '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 '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 results1.first.respond_to?(:keys)
27
- assert results1.first['Object Name']
28
- assert_instance_of Array, results2
29
- assert results2.first.respond_to?(:keys)
30
- assert results2.first['constraint_name']
31
- assert results2.first['constraint_type']
32
- end
33
-
34
-
35
- end
@@ -1,28 +0,0 @@
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
@@ -1,28 +0,0 @@
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
-
@@ -1,108 +0,0 @@
1
- require 'cases/sqlserver_helper'
2
- require 'models/person'
3
-
4
- class MigrationTestSqlserver < ActiveRecord::TestCase
5
-
6
- def setup
7
- @connection = ActiveRecord::Base.connection
8
- end
9
-
10
- context 'For transactions' do
11
-
12
- setup do
13
- @trans_test_table1 = 'sqlserver_trans_table1'
14
- @trans_test_table2 = 'sqlserver_trans_table2'
15
- @trans_tables = [@trans_test_table1,@trans_test_table2]
16
- end
17
-
18
- teardown do
19
- @trans_tables.each do |table_name|
20
- ActiveRecord::Migration.drop_table(table_name) if @connection.tables.include?(table_name)
21
- end
22
- end
23
-
24
- should 'not create a tables if error in migrations' do
25
- begin
26
- ActiveRecord::Migrator.up(SQLSERVER_MIGRATIONS_ROOT+'/transaction_table')
27
- rescue Exception => e
28
- assert_match %r|this and all later migrations canceled|, e.message
29
- end
30
- assert_does_not_contain @trans_test_table1, @connection.tables
31
- assert_does_not_contain @trans_test_table2, @connection.tables
32
- end
33
-
34
- end
35
-
36
- context 'For changing column' do
37
-
38
- should 'not raise exception when column contains default constraint' do
39
- lock_version_column = Person.columns_hash['lock_version']
40
- assert_equal :integer, lock_version_column.type
41
- assert lock_version_column.default.present?
42
- assert_nothing_raised { @connection.change_column 'people', 'lock_version', :string }
43
- Person.reset_column_information
44
- lock_version_column = Person.columns_hash['lock_version']
45
- assert_equal :string, lock_version_column.type
46
- assert lock_version_column.default.nil?
47
- end
48
-
49
- should 'not drop the default contraint if just renaming' do
50
- find_default = lambda do
51
- @connection.select_all("EXEC sp_helpconstraint 'defaults','nomsg'").select do |row|
52
- row['constraint_type'] == "DEFAULT on column decimal_number"
53
- end.last
54
- end
55
- default_before = find_default.call
56
- @connection.change_column :defaults, :decimal_number, :decimal, :precision => 4
57
- default_after = find_default.call
58
- assert default_after
59
- assert_equal default_before['constraint_keys'], default_after['constraint_keys']
60
- end
61
-
62
- end
63
-
64
- end
65
-
66
-
67
- class MigrationTest < ActiveRecord::TestCase
68
-
69
- COERCED_TESTS = [:test_add_column_not_null_without_default]
70
-
71
- include SqlserverCoercedTest
72
-
73
- def test_coerced_test_add_column_not_null_without_default
74
- Person.connection.create_table :testings do |t|
75
- t.column :foo, :string
76
- t.column :bar, :string, :null => false
77
- end
78
- assert_raises(ActiveRecord::StatementInvalid) do
79
- Person.connection.execute "INSERT INTO [testings] ([foo], [bar]) VALUES ('hello', NULL)"
80
- end
81
- ensure
82
- Person.connection.drop_table :testings rescue nil
83
- end
84
-
85
- end
86
-
87
-
88
- class ChangeTableMigrationsTest < ActiveRecord::TestCase
89
-
90
- COERCED_TESTS = [:test_string_creates_string_column]
91
-
92
- include SqlserverCoercedTest
93
-
94
- def test_coerced_string_creates_string_column
95
- with_change_table do |t|
96
- @connection.expects(:add_column).with(:delete_me, :foo, coerced_string_column, {})
97
- @connection.expects(:add_column).with(:delete_me, :bar, coerced_string_column, {})
98
- t.string :foo, :bar
99
- end
100
- end
101
-
102
- def coerced_string_column
103
- "#{Person.connection.native_string_database_type}(255)"
104
- end
105
-
106
- end
107
-
108
-
@@ -1,21 +0,0 @@
1
- require 'cases/sqlserver_helper'
2
-
3
- class NamedScopeTestSqlserver < ActiveRecord::TestCase
4
- end
5
-
6
- class NamedScopeTest < ActiveRecord::TestCase
7
-
8
- COERCED_TESTS = [:test_named_scopes_honor_current_scopes_from_when_defined]
9
-
10
- include SqlserverCoercedTest
11
-
12
- # See: http://github.com/rails/rails/commit/0dd2f96f5c90f8abacb0fe0757ef7e5db4a4d501#comment_37025
13
- # The orig test is a little brittle and fails on other adapters that do not explicitly fall back to a secondary
14
- # sort of id ASC. Since there are duplicate records with comments_count equal to one another. I have found that
15
- # named_scope :ranked_by_comments, :order => "comments_count DESC, id ASC" fixes the ambiguity.
16
- def test_coerced_test_named_scopes_honor_current_scopes_from_when_defined
17
- assert true
18
- end
19
-
20
-
21
- end
@@ -1,108 +0,0 @@
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
- should 'add locks to deepest sub select in limit offset sql that has a limited tally' do
74
- options = { :limit => 3, :offset => 5, :lock => 'WITH (NOLOCK)' }
75
- expected_sql = "SELECT * FROM (SELECT TOP 3 * FROM (SELECT TOP 8 * FROM books WITH (NOLOCK)) AS tmp1) AS tmp2"
76
- @connection.add_limit_offset! @select_sql, options
77
- assert_equal expected_sql, @connection.add_lock!(@select_sql,options)
78
- end
79
-
80
- # Not really sure what an offset sql injection might look like
81
- should 'not allow sql injection via offset' do
82
- options = { :limit => 10, :offset => '1 * FROM schema; DELETE * FROM table; SELECT TOP 10 *'}
83
- assert_raise(ArgumentError) { @connection.add_limit_offset!(@select_sql, options) }
84
- end
85
-
86
- should 'not create invalid SQL with subquery SELECTs with TOP' do
87
- options = { :limit => 5, :offset => 1 }
88
- 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"
89
- assert_equal expected_sql, @connection.add_limit_offset!(@subquery_select_sql,options)
90
- end
91
-
92
- should 'add lock hints to tally sql if :lock option is present' do
93
- assert_sql %r|SELECT TOP 1000000000 \* FROM \[people\] WITH \(NOLOCK\)| do
94
- Person.all :limit => 5, :offset => 1, :lock => 'WITH (NOLOCK)'
95
- end
96
- end
97
-
98
- should 'not add lock hints to tally sql if there is no :lock option' do
99
- assert_sql %r|\(SELECT TOP 1000000000 \* FROM \[people\] \)| do
100
- Person.all :limit => 5, :offset => 1
101
- end
102
- end
103
-
104
- end
105
-
106
-
107
- end
108
-