rails-sqlserver-2000-2005-adapter 1.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.
- data/CHANGELOG +25 -0
- data/MIT-LICENSE +20 -0
- data/README.textile +0 -0
- data/RUNNING_UNIT_TESTS +60 -0
- data/Rakefile +95 -0
- data/autotest/discover.rb +4 -0
- data/autotest/railssqlserver.rb +16 -0
- data/autotest/sqlserver.rb +54 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +913 -0
- data/lib/core_ext/active_record.rb +71 -0
- data/lib/core_ext/dbi.rb +83 -0
- data/test/cases/aaaa_create_tables_test_sqlserver.rb +19 -0
- data/test/cases/adapter_test_sqlserver.rb +428 -0
- data/test/cases/basics_test_sqlserver.rb +21 -0
- data/test/cases/calculations_test_sqlserver.rb +20 -0
- data/test/cases/column_test_sqlserver.rb +66 -0
- data/test/cases/connection_test_sqlserver.rb +103 -0
- data/test/cases/eager_association_test_sqlserver.rb +22 -0
- data/test/cases/inheritance_test_sqlserver.rb +28 -0
- data/test/cases/migration_test_sqlserver.rb +57 -0
- data/test/cases/offset_and_limit_test_sqlserver.rb +82 -0
- data/test/cases/pessimistic_locking_test_sqlserver.rb +100 -0
- data/test/cases/query_cache_test_sqlserver.rb +24 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +40 -0
- data/test/cases/specific_schema_test_sqlserver.rb +25 -0
- data/test/cases/sqlserver_helper.rb +88 -0
- data/test/connections/native_sqlserver/connection.rb +23 -0
- data/test/connections/native_sqlserver_odbc/connection.rb +25 -0
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
- data/test/schema/sqlserver_specific_schema.rb +38 -0
- metadata +96 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'cases/sqlserver_helper'
|
2
|
+
require 'models/binary'
|
3
|
+
|
4
|
+
class ColumnTestSqlserver < ActiveRecord::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@column_klass = ActiveRecord::ConnectionAdapters::SQLServerColumn
|
8
|
+
end
|
9
|
+
|
10
|
+
should 'return real_number as float' do
|
11
|
+
assert_equal :float, TableWithRealColumn.columns_hash["real_number"].type
|
12
|
+
end
|
13
|
+
|
14
|
+
should 'know its #table_name and #table_klass' do
|
15
|
+
Topic.columns.each do |column|
|
16
|
+
assert_equal 'topics', column.table_name, "This column #{column.inspect} did not know it's #table_name"
|
17
|
+
assert_equal Topic, column.table_klass, "This column #{column.inspect} did not know it's #table_klass"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'For :binary columns' do
|
22
|
+
|
23
|
+
setup do
|
24
|
+
@binary_string = "GIF89a\001\000\001\000\200\000\000\377\377\377\000\000\000!\371\004\000\000\000\000\000,\000\000\000\000\001\000\001\000\000\002\002D\001\000;"
|
25
|
+
@saved_bdata = Binary.create!(:data => @binary_string)
|
26
|
+
end
|
27
|
+
|
28
|
+
should 'read and write binary data equally' do
|
29
|
+
assert_equal @binary_string, Binary.find(@saved_bdata).data
|
30
|
+
end
|
31
|
+
|
32
|
+
should 'quote data for sqlserver with literal 0x prefix' do
|
33
|
+
# See the output of the stored procedure: 'exec sp_datatype_info'
|
34
|
+
sqlserver_encoded_bdata = "0x47494638396101000100800000ffffff00000021f90400000000002c00000000010001000002024401003b"
|
35
|
+
assert_equal sqlserver_encoded_bdata, @column_klass.string_to_binary(@binary_string)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'For .columns method' do
|
41
|
+
|
42
|
+
should 'return correct scales and precisions for NumericData' do
|
43
|
+
bank_balance = NumericData.columns_hash['bank_balance']
|
44
|
+
big_bank_balance = NumericData.columns_hash['big_bank_balance']
|
45
|
+
world_population = NumericData.columns_hash['world_population']
|
46
|
+
my_house_population = NumericData.columns_hash['my_house_population']
|
47
|
+
assert_equal [2,10], [bank_balance.scale, bank_balance.precision]
|
48
|
+
assert_equal [2,15], [big_bank_balance.scale, big_bank_balance.precision]
|
49
|
+
assert_equal [0,10], [world_population.scale, world_population.precision]
|
50
|
+
assert_equal [0,2], [my_house_population.scale, my_house_population.precision]
|
51
|
+
end
|
52
|
+
|
53
|
+
should 'return correct null, limit, and default for Topic' do
|
54
|
+
tch = Topic.columns_hash
|
55
|
+
assert_equal false, tch['id'].null
|
56
|
+
assert_equal true, tch['title'].null
|
57
|
+
assert_equal 255, tch['author_name'].limit
|
58
|
+
assert_equal true, tch['approved'].default
|
59
|
+
assert_equal 0, tch['replies_count'].default
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,103 @@
|
|
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 'return finished DBI statment handle from #execute without block' do
|
16
|
+
handle = @connection.execute('SELECT * FROM [topics]')
|
17
|
+
assert_instance_of DBI::StatementHandle, handle
|
18
|
+
assert handle.finished?
|
19
|
+
end
|
20
|
+
|
21
|
+
should 'finish DBI 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 'return an unfinished DBI statement handler from #raw_execute' do
|
28
|
+
handle = @connection.send(:raw_execute,'SELECT * FROM [topics]')
|
29
|
+
assert_instance_of DBI::StatementHandle, handle
|
30
|
+
assert !handle.finished?
|
31
|
+
end
|
32
|
+
|
33
|
+
should 'finish connection from #raw_select' do
|
34
|
+
assert_all_statements_used_are_closed do
|
35
|
+
@connection.send(:raw_select,'SELECT * FROM [topics]')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
should 'affect rows' do
|
40
|
+
assert Topic.connection.instance_variable_get("@connection")["AutoCommit"]
|
41
|
+
topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
|
42
|
+
updated = Topic.update(topic_data.keys, topic_data.values)
|
43
|
+
assert_equal 2, updated.size
|
44
|
+
assert_equal "1 updated", Topic.find(1).content
|
45
|
+
assert_equal "2 updated", Topic.find(2).content
|
46
|
+
assert_equal 2, Topic.delete([1, 2])
|
47
|
+
end
|
48
|
+
|
49
|
+
should 'execute without block closes statement' do
|
50
|
+
assert_all_statements_used_are_closed do
|
51
|
+
@connection.execute("SELECT 1")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
should 'execute with block closes statement' do
|
56
|
+
assert_all_statements_used_are_closed do
|
57
|
+
@connection.execute("SELECT 1") do |sth|
|
58
|
+
assert !sth.finished?, "Statement should still be alive within block"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
should 'insert with identity closes statement' do
|
64
|
+
assert_all_statements_used_are_closed do
|
65
|
+
@connection.insert("INSERT INTO accounts ([id], [firm_id],[credit_limit]) values (999, 1, 50)")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
should 'insert without identity closes statement' do
|
70
|
+
assert_all_statements_used_are_closed do
|
71
|
+
@connection.insert("INSERT INTO accounts ([firm_id],[credit_limit]) values (1, 50)")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
should 'active closes statement' do
|
76
|
+
assert_all_statements_used_are_closed do
|
77
|
+
@connection.active?
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def assert_all_statements_used_are_closed(&block)
|
85
|
+
existing_handles = []
|
86
|
+
ObjectSpace.each_object(DBI::StatementHandle) {|handle| existing_handles << handle}
|
87
|
+
GC.disable
|
88
|
+
yield
|
89
|
+
used_handles = []
|
90
|
+
ObjectSpace.each_object(DBI::StatementHandle) {|handle| used_handles << handle unless existing_handles.include? handle}
|
91
|
+
assert_block "No statements were used within given block" do
|
92
|
+
used_handles.size > 0
|
93
|
+
end
|
94
|
+
ObjectSpace.each_object(DBI::StatementHandle) do |handle|
|
95
|
+
assert_block "Statement should have been closed within given block" do
|
96
|
+
handle.finished?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
ensure
|
100
|
+
GC.enable
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'cases/sqlserver_helper'
|
2
|
+
require 'models/author'
|
3
|
+
require 'models/post'
|
4
|
+
require 'models/comment'
|
5
|
+
|
6
|
+
class EagerAssociationTestSqlserver < ActiveRecord::TestCase
|
7
|
+
end
|
8
|
+
|
9
|
+
class EagerAssociationTest < ActiveRecord::TestCase
|
10
|
+
|
11
|
+
COERCED_TESTS = [:test_count_with_include]
|
12
|
+
|
13
|
+
include SqlserverCoercedTest
|
14
|
+
|
15
|
+
fixtures :authors, :posts, :comments
|
16
|
+
|
17
|
+
def test_coerced_test_count_with_include
|
18
|
+
assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15")
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
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,57 @@
|
|
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
|
+
|
44
|
+
def test_coerced_test_add_column_not_null_without_default
|
45
|
+
Person.connection.create_table :testings do |t|
|
46
|
+
t.column :foo, :string
|
47
|
+
t.column :bar, :string, :null => false
|
48
|
+
end
|
49
|
+
assert_raises(ActiveRecord::StatementInvalid) do
|
50
|
+
Person.connection.execute "INSERT INTO [testings] ([foo], [bar]) VALUES ('hello', NULL)"
|
51
|
+
end
|
52
|
+
ensure
|
53
|
+
Person.connection.drop_table :testings rescue nil
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,82 @@
|
|
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
|
+
@books = (1..10).map {|i| Book.create!}
|
45
|
+
end
|
46
|
+
|
47
|
+
teardown do
|
48
|
+
@books.each {|b| b.destroy}
|
49
|
+
end
|
50
|
+
|
51
|
+
should 'have limit if offset is passed' do
|
52
|
+
options = { :offset => 1 }
|
53
|
+
assert_raise(ArgumentError) { @connection.add_limit_offset!(@select_sql, options) }
|
54
|
+
end
|
55
|
+
|
56
|
+
should 'only allow integers for offset' do
|
57
|
+
options = { :limit => 10, :offset => 'five' }
|
58
|
+
assert_raise(ArgumentError) { @connection.add_limit_offset!(@select_sql, options)}
|
59
|
+
end
|
60
|
+
|
61
|
+
should 'convert strings which look like integers to integers' do
|
62
|
+
options = { :limit => 10, :offset => '5' }
|
63
|
+
assert_nothing_raised(ArgumentError) {@connection.add_limit_offset!(@select_sql, options)}
|
64
|
+
end
|
65
|
+
|
66
|
+
should 'alter SQL to limit number of records returned offset by specified amount' do
|
67
|
+
options = { :limit => 3, :offset => 5 }
|
68
|
+
expected_sql = %&SELECT * FROM (SELECT TOP 3 * FROM (SELECT TOP 8 * FROM books) AS tmp1) AS tmp2&
|
69
|
+
assert_equal(expected_sql, @connection.add_limit_offset!(@select_sql, options))
|
70
|
+
end
|
71
|
+
|
72
|
+
# Not really sure what an offset sql injection might look like
|
73
|
+
should 'not allow sql injection via offset' do
|
74
|
+
options = { :limit => 10, :offset => '1 * FROM schema; DELETE * FROM table; SELECT TOP 10 *'}
|
75
|
+
assert_raise(ArgumentError) { @connection.add_limit_offset!(@select_sql, options) }
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,100 @@
|
|
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
|
+
end
|
54
|
+
|
55
|
+
context 'For dueling concurrent connections' do
|
56
|
+
|
57
|
+
use_concurrent_connections
|
58
|
+
|
59
|
+
should 'no locks does not wait' do
|
60
|
+
first, second = duel { Person.find 1 }
|
61
|
+
assert first.end > second.end
|
62
|
+
end
|
63
|
+
|
64
|
+
should 'that second lock waits' do
|
65
|
+
assert [0.2, 1, 5].any? { |zzz|
|
66
|
+
first, second = duel(zzz) { Person.find 1, :lock => true }
|
67
|
+
second.end > first.end
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def duel(zzz = 5)
|
77
|
+
t0, t1, t2, t3 = nil, nil, nil, nil
|
78
|
+
a = Thread.new do
|
79
|
+
t0 = Time.now
|
80
|
+
Person.transaction do
|
81
|
+
yield
|
82
|
+
sleep zzz # block thread 2 for zzz seconds
|
83
|
+
end
|
84
|
+
t1 = Time.now
|
85
|
+
end
|
86
|
+
b = Thread.new do
|
87
|
+
sleep zzz / 2.0 # ensure thread 1 tx starts first
|
88
|
+
t2 = Time.now
|
89
|
+
Person.transaction { yield }
|
90
|
+
t3 = Time.now
|
91
|
+
end
|
92
|
+
a.join
|
93
|
+
b.join
|
94
|
+
assert t1 > t0 + zzz
|
95
|
+
assert t2 > t0
|
96
|
+
assert t3 > t2
|
97
|
+
[t0.to_f..t1.to_f, t2.to_f..t3.to_f]
|
98
|
+
end
|
99
|
+
|
100
|
+
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,40 @@
|
|
1
|
+
require 'cases/sqlserver_helper'
|
2
|
+
|
3
|
+
class SchemaDumperTestSqlserver < ActiveRecord::TestCase
|
4
|
+
|
5
|
+
context 'In schema dump' do
|
6
|
+
|
7
|
+
should 'include limit constraint for integer columns' do
|
8
|
+
output = standard_dump(/^(?!integer_limits)/)
|
9
|
+
assert_match %r{c_int_1.*:limit => 2}, output
|
10
|
+
assert_match %r{c_int_2.*:limit => 2}, output
|
11
|
+
assert_match %r{c_int_3.*}, output
|
12
|
+
assert_match %r{c_int_4.*}, output
|
13
|
+
assert_no_match %r{c_int_3.*:limit}, output
|
14
|
+
assert_no_match %r{c_int_4.*:limit}, output
|
15
|
+
assert_match %r{c_int_5.*:limit => 8}, output
|
16
|
+
assert_match %r{c_int_6.*:limit => 8}, output
|
17
|
+
assert_match %r{c_int_7.*:limit => 8}, output
|
18
|
+
assert_match %r{c_int_8.*:limit => 8}, output
|
19
|
+
end
|
20
|
+
|
21
|
+
should 'honor nonstandard primary keys' do
|
22
|
+
output = standard_dump
|
23
|
+
match = output.match(%r{create_table "movies"(.*)do})
|
24
|
+
assert_not_nil(match, "nonstandardpk table not found")
|
25
|
+
assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def standard_dump(ignore_tables = [])
|
34
|
+
stream = StringIO.new
|
35
|
+
ActiveRecord::SchemaDumper.ignore_tables = [*ignore_tables]
|
36
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
37
|
+
stream.string
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'cases/sqlserver_helper'
|
2
|
+
|
3
|
+
class StringDefault < ActiveRecord::Base; end;
|
4
|
+
|
5
|
+
class SpecificSchemaTestSqlserver < ActiveRecord::TestCase
|
6
|
+
|
7
|
+
should 'default strings before save' do
|
8
|
+
default = StringDefault.new
|
9
|
+
assert_equal nil, default.string_with_null_default
|
10
|
+
assert_equal 'null', default.string_with_pretend_null_one
|
11
|
+
assert_equal '(null)', default.string_with_pretend_null_two
|
12
|
+
assert_equal 'NULL', default.string_with_pretend_null_three
|
13
|
+
assert_equal '(NULL)', default.string_with_pretend_null_four
|
14
|
+
end
|
15
|
+
|
16
|
+
should 'default strings after save' do
|
17
|
+
default = StringDefault.create
|
18
|
+
assert_equal nil, default.string_with_null_default
|
19
|
+
assert_equal 'null', default.string_with_pretend_null_one
|
20
|
+
assert_equal '(null)', default.string_with_pretend_null_two
|
21
|
+
assert_equal 'NULL', default.string_with_pretend_null_three
|
22
|
+
assert_equal '(NULL)', default.string_with_pretend_null_four
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'shoulda'
|
3
|
+
require 'mocha'
|
4
|
+
require 'cases/helper'
|
5
|
+
require 'models/topic'
|
6
|
+
|
7
|
+
SQLSERVER_TEST_ROOT = File.expand_path(File.join(File.dirname(__FILE__),'..'))
|
8
|
+
SQLSERVER_ASSETS_ROOT = SQLSERVER_TEST_ROOT + "/assets"
|
9
|
+
SQLSERVER_FIXTURES_ROOT = SQLSERVER_TEST_ROOT + "/fixtures"
|
10
|
+
SQLSERVER_MIGRATIONS_ROOT = SQLSERVER_TEST_ROOT + "/migrations"
|
11
|
+
SQLSERVER_SCHEMA_ROOT = SQLSERVER_TEST_ROOT + "/schema"
|
12
|
+
ACTIVERECORD_TEST_ROOT = File.expand_path(SQLSERVER_TEST_ROOT + "/../../../../rails/activerecord/test/")
|
13
|
+
|
14
|
+
ActiveRecord::Migration.verbose = false
|
15
|
+
|
16
|
+
# Defining our classes in one place as well as soem core tests that need coercing date/time types.
|
17
|
+
|
18
|
+
class TableWithRealColumn < ActiveRecord::Base; end
|
19
|
+
class FkTestHasFk < ActiveRecord::Base ; end
|
20
|
+
class FkTestHasPk < ActiveRecord::Base ; end
|
21
|
+
class NumericData < ActiveRecord::Base ; self.table_name = 'numeric_data' ; end
|
22
|
+
class SqlServerChronic < ActiveRecord::Base
|
23
|
+
coerce_sqlserver_date :date
|
24
|
+
coerce_sqlserver_time :time
|
25
|
+
default_timezone = :utc
|
26
|
+
end
|
27
|
+
class Topic < ActiveRecord::Base
|
28
|
+
coerce_sqlserver_date :last_read
|
29
|
+
coerce_sqlserver_time :bonus_time
|
30
|
+
end
|
31
|
+
class Person < ActiveRecord::Base
|
32
|
+
coerce_sqlserver_date :favorite_day
|
33
|
+
end
|
34
|
+
|
35
|
+
# A module that we can include in classes where we want to override an active record test.
|
36
|
+
|
37
|
+
module SqlserverCoercedTest
|
38
|
+
def self.included(base)
|
39
|
+
base.extend ClassMethods
|
40
|
+
end
|
41
|
+
module ClassMethods
|
42
|
+
def coerced_tests
|
43
|
+
self.const_get(:COERCED_TESTS) rescue nil
|
44
|
+
end
|
45
|
+
def method_added(method)
|
46
|
+
undef_method(method) if coerced_tests && coerced_tests.include?(method)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Change the text database type to support ActiveRecord's tests for = on text columns which
|
52
|
+
# is not supported in SQL Server text columns, so use varchar(8000) instead.
|
53
|
+
|
54
|
+
if ActiveRecord::Base.connection.sqlserver_2000?
|
55
|
+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = 'varchar(8000)'
|
56
|
+
end
|
57
|
+
|
58
|
+
# Our changes/additions to ActiveRecord test helpers specific for SQL Server.
|
59
|
+
|
60
|
+
ActiveRecord::Base.connection.class.class_eval do
|
61
|
+
IGNORED_SQL << /SELECT SCOPE_IDENTITY/ << /INFORMATION_SCHEMA.TABLES/ << /INFORMATION_SCHEMA.COLUMNS/
|
62
|
+
end
|
63
|
+
|
64
|
+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.class_eval do
|
65
|
+
def raw_select_with_query_record(sql, name = nil)
|
66
|
+
$queries_executed ||= []
|
67
|
+
$queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
|
68
|
+
raw_select_without_query_record(sql,name)
|
69
|
+
end
|
70
|
+
alias_method_chain :raw_select, :query_record
|
71
|
+
end
|
72
|
+
|
73
|
+
module ActiveRecord
|
74
|
+
class TestCase < ActiveSupport::TestCase
|
75
|
+
def assert_sql(*patterns_to_match)
|
76
|
+
$queries_executed = []
|
77
|
+
yield
|
78
|
+
ensure
|
79
|
+
failed_patterns = []
|
80
|
+
patterns_to_match.each do |pattern|
|
81
|
+
failed_patterns << pattern unless $queries_executed.any?{ |sql| pattern === sql }
|
82
|
+
end
|
83
|
+
assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found in:\n#{$queries_executed.inspect}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
print "Using native SQLServer\n"
|
2
|
+
require_dependency 'models/course'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
ActiveRecord::Base.logger = Logger.new("debug.log")
|
6
|
+
|
7
|
+
ActiveRecord::Base.configurations = {
|
8
|
+
'arunit' => {
|
9
|
+
:adapter => 'sqlserver',
|
10
|
+
:host => 'localhost',
|
11
|
+
:username => 'rails',
|
12
|
+
:database => 'activerecord_unittest'
|
13
|
+
},
|
14
|
+
'arunit2' => {
|
15
|
+
:adapter => 'sqlserver',
|
16
|
+
:host => 'localhost',
|
17
|
+
:username => 'rails',
|
18
|
+
:database => 'activerecord_unittest2'
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
ActiveRecord::Base.establish_connection 'arunit'
|
23
|
+
Course.establish_connection 'arunit2'
|
@@ -0,0 +1,25 @@
|
|
1
|
+
print "Using native SQLServer via ODBC\n"
|
2
|
+
require_dependency 'models/course'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
ActiveRecord::Base.logger = Logger.new("debug.log")
|
6
|
+
|
7
|
+
ActiveRecord::Base.configurations = {
|
8
|
+
'arunit' => {
|
9
|
+
:adapter => 'sqlserver',
|
10
|
+
:mode => 'ODBC',
|
11
|
+
:host => 'localhost',
|
12
|
+
:username => 'rails',
|
13
|
+
:dsn => 'activerecord_unittest'
|
14
|
+
},
|
15
|
+
'arunit2' => {
|
16
|
+
:adapter => 'sqlserver',
|
17
|
+
:mode => 'ODBC',
|
18
|
+
:host => 'localhost',
|
19
|
+
:username => 'rails',
|
20
|
+
:dsn => 'activerecord_unittest2'
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
ActiveRecord::Base.establish_connection 'arunit'
|
25
|
+
Course.establish_connection 'arunit2'
|