activerecord-sqlserver-adapter 2.3.24 → 3.0.0

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 (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
-