activerecord-sqlserver-adapter 2.2.18
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +175 -0
- data/MIT-LICENSE +20 -0
- data/Manifest +36 -0
- data/README.rdoc +175 -0
- data/RUNNING_UNIT_TESTS +60 -0
- data/Rakefile +18 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1126 -0
- data/lib/activerecord-sqlserver-adapter.rb +1 -0
- data/lib/core_ext/active_record.rb +133 -0
- data/lib/core_ext/dbi.rb +85 -0
- data/tasks/sqlserver.rake +31 -0
- data/test/cases/aaaa_create_tables_test_sqlserver.rb +19 -0
- data/test/cases/adapter_test_sqlserver.rb +707 -0
- data/test/cases/attribute_methods_test_sqlserver.rb +33 -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 +264 -0
- data/test/cases/connection_test_sqlserver.rb +142 -0
- data/test/cases/eager_association_test_sqlserver.rb +42 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +33 -0
- data/test/cases/inheritance_test_sqlserver.rb +28 -0
- data/test/cases/method_scoping_test_sqlserver.rb +28 -0
- data/test/cases/migration_test_sqlserver.rb +93 -0
- data/test/cases/offset_and_limit_test_sqlserver.rb +108 -0
- data/test/cases/pessimistic_locking_test_sqlserver.rb +125 -0
- data/test/cases/query_cache_test_sqlserver.rb +24 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +72 -0
- data/test/cases/specific_schema_test_sqlserver.rb +57 -0
- data/test/cases/sqlserver_helper.rb +123 -0
- data/test/cases/table_name_test_sqlserver.rb +22 -0
- data/test/cases/transaction_test_sqlserver.rb +93 -0
- data/test/cases/unicode_test_sqlserver.rb +50 -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 +91 -0
- metadata +120 -0
@@ -0,0 +1 @@
|
|
1
|
+
require 'active_record/connection_adapters/sqlserver_adapter'
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'active_record/version'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module SQLServerActiveRecordExtensions
|
6
|
+
|
7
|
+
def self.included(klass)
|
8
|
+
klass.extend ClassMethods
|
9
|
+
class << klass
|
10
|
+
alias_method_chain :reset_column_information, :sqlserver_cache_support
|
11
|
+
alias_method_chain :add_order!, :sqlserver_unique_checking
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
def execute_procedure(proc_name, *variables)
|
18
|
+
if connection.respond_to?(:execute_procedure)
|
19
|
+
connection.execute_procedure(proc_name,*variables)
|
20
|
+
else
|
21
|
+
[]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def coerce_sqlserver_date(*attributes)
|
26
|
+
write_inheritable_attribute :coerced_sqlserver_date_columns, Set.new(attributes.map(&:to_s))
|
27
|
+
end
|
28
|
+
|
29
|
+
def coerce_sqlserver_time(*attributes)
|
30
|
+
write_inheritable_attribute :coerced_sqlserver_time_columns, Set.new(attributes.map(&:to_s))
|
31
|
+
end
|
32
|
+
|
33
|
+
def coerced_sqlserver_date_columns
|
34
|
+
read_inheritable_attribute(:coerced_sqlserver_date_columns) || []
|
35
|
+
end
|
36
|
+
|
37
|
+
def coerced_sqlserver_time_columns
|
38
|
+
read_inheritable_attribute(:coerced_sqlserver_time_columns) || []
|
39
|
+
end
|
40
|
+
|
41
|
+
def reset_column_information_with_sqlserver_cache_support
|
42
|
+
connection.send(:initialize_sqlserver_caches) if connection.respond_to?(:sqlserver?)
|
43
|
+
reset_column_information_without_sqlserver_cache_support
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def add_order_with_sqlserver_unique_checking!(sql, order, scope = :auto)
|
49
|
+
if connection.respond_to?(:sqlserver?)
|
50
|
+
order_sql = ''
|
51
|
+
add_order_without_sqlserver_unique_checking!(order_sql, order, scope)
|
52
|
+
unless order_sql.blank?
|
53
|
+
unique_order_hash = {}
|
54
|
+
select_table_name = connection.send(:get_table_name,sql)
|
55
|
+
select_table_name.tr!('[]','') if select_table_name
|
56
|
+
orders_and_dirs_set = connection.send(:orders_and_dirs_set,order_sql)
|
57
|
+
unique_order_sql = orders_and_dirs_set.inject([]) do |array,order_dir|
|
58
|
+
ord, dir = order_dir
|
59
|
+
ord_tn_and_cn = ord.to_s.split('.').map{|o|o.tr('[]','')}
|
60
|
+
ord_table_name, ord_column_name = if ord_tn_and_cn.size > 1
|
61
|
+
ord_tn_and_cn
|
62
|
+
else
|
63
|
+
[nil, ord_tn_and_cn.first]
|
64
|
+
end
|
65
|
+
if (ord_table_name && ord_table_name == select_table_name && unique_order_hash[ord_column_name]) || unique_order_hash[ord_column_name]
|
66
|
+
array
|
67
|
+
else
|
68
|
+
unique_order_hash[ord_column_name] = true
|
69
|
+
array << "#{ord} #{dir}".strip
|
70
|
+
end
|
71
|
+
end.join(', ')
|
72
|
+
sql << " ORDER BY #{unique_order_sql}"
|
73
|
+
end
|
74
|
+
else
|
75
|
+
add_order_without_sqlserver_unique_checking!(sql, order, scope)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
ActiveRecord::Base.send :include, ActiveRecord::ConnectionAdapters::SQLServerActiveRecordExtensions
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
if ActiveRecord::VERSION::MAJOR == 2 && ActiveRecord::VERSION::MINOR >= 3
|
91
|
+
|
92
|
+
require 'active_record/associations'
|
93
|
+
module ActiveRecord
|
94
|
+
module ConnectionAdapters
|
95
|
+
module SQLServerJoinAssociationChanges
|
96
|
+
|
97
|
+
def self.included(klass)
|
98
|
+
klass.class_eval do
|
99
|
+
include InstanceMethods
|
100
|
+
alias_method_chain :aliased_table_name_for, :sqlserver_support
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
module InstanceMethods
|
105
|
+
|
106
|
+
protected
|
107
|
+
|
108
|
+
# An exact copy, except this method has a Regexp escape on the quoted table name.
|
109
|
+
def aliased_table_name_for_with_sqlserver_support(name,suffix=nil)
|
110
|
+
if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{Regexp.escape(active_record.connection.quote_table_name(name.downcase))}\son}i
|
111
|
+
@join_dependency.table_aliases[name] += 1
|
112
|
+
end
|
113
|
+
unless @join_dependency.table_aliases[name].zero?
|
114
|
+
# if the table name has been used, then use an alias
|
115
|
+
name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
|
116
|
+
table_index = @join_dependency.table_aliases[name]
|
117
|
+
@join_dependency.table_aliases[name] += 1
|
118
|
+
name = name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
|
119
|
+
else
|
120
|
+
@join_dependency.table_aliases[name] += 1
|
121
|
+
end
|
122
|
+
name
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation.send :include, ActiveRecord::ConnectionAdapters::SQLServerJoinAssociationChanges
|
131
|
+
|
132
|
+
end
|
133
|
+
|
data/lib/core_ext/dbi.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
|
2
|
+
module SQLServerDBI
|
3
|
+
|
4
|
+
module Timestamp
|
5
|
+
# Deprecated DBI. See documentation for Type::SqlserverTimestamp which
|
6
|
+
# this method tries to mimic as ODBC is still going to convert SQL Server
|
7
|
+
# milliconds to whole number representation of nanoseconds.
|
8
|
+
def to_sqlserver_string
|
9
|
+
datetime, nanoseconds = to_s.split('.')
|
10
|
+
"#{datetime}.#{sprintf("%03d",nanoseconds.to_i/1000000)}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Type
|
15
|
+
|
16
|
+
# Make sure we get DBI::Type::Timestamp returning a string NOT a time object
|
17
|
+
# that represents what is in the DB before type casting while letting core
|
18
|
+
# ActiveRecord do the reset. It is assumed that DBI is using ODBC connections
|
19
|
+
# and that ODBC::Timestamp is taking the native milliseconds that SQL Server
|
20
|
+
# stores and returning them incorrect using ODBC::Timestamp#fraction which is
|
21
|
+
# nanoseconds. Below shows the incorrect ODBC::Timestamp represented by DBI
|
22
|
+
# and the conversion we expect to have in the DB before type casting.
|
23
|
+
#
|
24
|
+
# "1985-04-15 00:00:00 0" # => "1985-04-15 00:00:00.000"
|
25
|
+
# "2008-11-08 10:24:36 30000000" # => "2008-11-08 10:24:36.003"
|
26
|
+
# "2008-11-08 10:24:36 123000000" # => "2008-11-08 10:24:36.123"
|
27
|
+
class SqlserverTimestamp
|
28
|
+
def self.parse(obj)
|
29
|
+
return nil if ::DBI::Type::Null.parse(obj).nil?
|
30
|
+
date, time, nanoseconds = obj.split(' ')
|
31
|
+
"#{date} #{time}.#{sprintf("%03d",nanoseconds.to_i/1000000)}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# The adapter and rails will parse our floats, decimals, and money field correctly
|
36
|
+
# from a string. Do not let the DBI::Type classes create Float/BigDecimal objects
|
37
|
+
# for us. Trust rails .type_cast to do what it is built to do.
|
38
|
+
class SqlserverForcedString
|
39
|
+
def self.parse(obj)
|
40
|
+
return nil if ::DBI::Type::Null.parse(obj).nil?
|
41
|
+
obj.to_s
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
module TypeUtil
|
48
|
+
|
49
|
+
def self.included(klass)
|
50
|
+
klass.extend ClassMethods
|
51
|
+
class << klass
|
52
|
+
alias_method_chain :type_name_to_module, :sqlserver_types
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module ClassMethods
|
57
|
+
|
58
|
+
# Capture all types classes that we need to handle directly for SQL Server
|
59
|
+
# and allow normal processing for those that we do not.
|
60
|
+
def type_name_to_module_with_sqlserver_types(type_name)
|
61
|
+
case type_name
|
62
|
+
when /^timestamp$/i
|
63
|
+
DBI::Type::SqlserverTimestamp
|
64
|
+
when /^float|decimal|money$/i
|
65
|
+
DBI::Type::SqlserverForcedString
|
66
|
+
else
|
67
|
+
type_name_to_module_without_sqlserver_types(type_name)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
if defined?(DBI::TypeUtil)
|
80
|
+
DBI::Type.send :include, SQLServerDBI::Type
|
81
|
+
DBI::TypeUtil.send :include, SQLServerDBI::TypeUtil
|
82
|
+
elsif defined?(DBI::Timestamp) # DEPRECATED in DBI 0.4.0 and above. Remove when 0.2.2 and lower is no longer supported.
|
83
|
+
DBI::Timestamp.send :include, SQLServerDBI::Timestamp
|
84
|
+
end
|
85
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
namespace :sqlserver do
|
3
|
+
|
4
|
+
['sqlserver','sqlserver_odbc'].each do |adapter|
|
5
|
+
|
6
|
+
Rake::TestTask.new("test_#{adapter}") do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.libs << "test/connections/native_#{adapter}"
|
9
|
+
t.libs << "../../../rails/activerecord/test/"
|
10
|
+
t.test_files = (
|
11
|
+
Dir.glob("test/cases/**/*_test_sqlserver.rb").sort +
|
12
|
+
Dir.glob("../../../rails/activerecord/test/**/*_test.rb").sort )
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
namespace adapter do
|
17
|
+
task :test => "test_#{adapter}"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'Test with unicode types enabled.'
|
23
|
+
task :test_unicode_types do
|
24
|
+
ENV['ENABLE_DEFAULT_UNICODE_TYPES'] = 'true'
|
25
|
+
test = Rake::Task['test_sqlserver_odbc']
|
26
|
+
test.invoke
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# The filename begins with "aaaa" to ensure this is the first test.
|
2
|
+
require 'cases/sqlserver_helper'
|
3
|
+
|
4
|
+
class AAAACreateTablesTestSqlserver < ActiveRecord::TestCase
|
5
|
+
self.use_transactional_fixtures = false
|
6
|
+
|
7
|
+
should 'load activerecord schema' do
|
8
|
+
schema_file = "#{ACTIVERECORD_TEST_ROOT}/schema/schema.rb"
|
9
|
+
eval(File.read(schema_file))
|
10
|
+
assert true
|
11
|
+
end
|
12
|
+
|
13
|
+
should 'load sqlserver specific schema' do
|
14
|
+
sqlserver_specific_schema_file = "#{SQLSERVER_SCHEMA_ROOT}/sqlserver_specific_schema.rb"
|
15
|
+
eval(File.read(sqlserver_specific_schema_file))
|
16
|
+
assert true
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,707 @@
|
|
1
|
+
require 'cases/sqlserver_helper'
|
2
|
+
require 'models/task'
|
3
|
+
require 'models/reply'
|
4
|
+
require 'models/joke'
|
5
|
+
require 'models/subscriber'
|
6
|
+
|
7
|
+
class AdapterTestSqlserver < ActiveRecord::TestCase
|
8
|
+
|
9
|
+
fixtures :tasks
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@connection = ActiveRecord::Base.connection
|
13
|
+
@basic_insert_sql = "INSERT INTO [funny_jokes] ([name]) VALUES('Knock knock')"
|
14
|
+
@basic_update_sql = "UPDATE [customers] SET [address_street] = NULL WHERE [id] = 2"
|
15
|
+
@basic_select_sql = "SELECT * FROM [customers] WHERE ([customers].[id] = 1)"
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'For abstract behavior' do
|
19
|
+
|
20
|
+
should 'have a 128 max #table_alias_length' do
|
21
|
+
assert @connection.table_alias_length <= 128
|
22
|
+
end
|
23
|
+
|
24
|
+
should 'raise invalid statement error' do
|
25
|
+
assert_raise(ActiveRecord::StatementInvalid) { Topic.connection.update("UPDATE XXX") }
|
26
|
+
end
|
27
|
+
|
28
|
+
should 'be our adapter_name' do
|
29
|
+
assert_equal 'SQLServer', @connection.adapter_name
|
30
|
+
end
|
31
|
+
|
32
|
+
should 'include version in inspect' do
|
33
|
+
assert_match(/version\: \d.\d.\d/,@connection.inspect)
|
34
|
+
end
|
35
|
+
|
36
|
+
should 'support migrations' do
|
37
|
+
assert @connection.supports_migrations?
|
38
|
+
end
|
39
|
+
|
40
|
+
should 'support DDL in transactions' do
|
41
|
+
assert @connection.supports_ddl_transactions?
|
42
|
+
end
|
43
|
+
|
44
|
+
should 'allow owner table name prefixs like dbo. to still allow table_exists? to return true' do
|
45
|
+
begin
|
46
|
+
assert_equal 'tasks', Task.table_name
|
47
|
+
assert Task.table_exists?
|
48
|
+
Task.table_name = 'dbo.tasks'
|
49
|
+
assert Task.table_exists?, 'Tasks table name of dbo.tasks should return true for exists.'
|
50
|
+
ensure
|
51
|
+
Task.table_name = 'tasks'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'for database version' do
|
56
|
+
|
57
|
+
setup do
|
58
|
+
@version_regexp = ActiveRecord::ConnectionAdapters::SQLServerAdapter::DATABASE_VERSION_REGEXP
|
59
|
+
@supported_version = ActiveRecord::ConnectionAdapters::SQLServerAdapter::SUPPORTED_VERSIONS
|
60
|
+
@sqlserver_2000_string = "Microsoft SQL Server 2000 - 8.00.2039 (Intel X86)"
|
61
|
+
@sqlserver_2005_string = "Microsoft SQL Server 2005 - 9.00.3215.00 (Intel X86)"
|
62
|
+
@sqlserver_2008_string = "Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (Intel X86)"
|
63
|
+
end
|
64
|
+
|
65
|
+
should 'return a string from #database_version that matches class regexp' do
|
66
|
+
assert_match @version_regexp, @connection.database_version
|
67
|
+
end
|
68
|
+
|
69
|
+
should 'return a 4 digit year fixnum for #database_year' do
|
70
|
+
assert_instance_of Fixnum, @connection.database_year
|
71
|
+
assert_contains @supported_version, @connection.database_year
|
72
|
+
end
|
73
|
+
|
74
|
+
should 'return true to #sqlserver_2000?' do
|
75
|
+
@connection.stubs(:database_version).returns(@sqlserver_2000_string)
|
76
|
+
assert @connection.sqlserver_2000?
|
77
|
+
end
|
78
|
+
|
79
|
+
should 'return true to #sqlserver_2005?' do
|
80
|
+
@connection.stubs(:database_version).returns(@sqlserver_2005_string)
|
81
|
+
assert @connection.sqlserver_2005?
|
82
|
+
end
|
83
|
+
|
84
|
+
should 'return true to #sqlserver_2008?' do
|
85
|
+
@connection.stubs(:database_version).returns(@sqlserver_2008_string)
|
86
|
+
assert @connection.sqlserver_2008?
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'for #unqualify_table_name and #unqualify_db_name' do
|
92
|
+
|
93
|
+
setup do
|
94
|
+
@expected_table_name = 'baz'
|
95
|
+
@expected_db_name = 'foo'
|
96
|
+
@first_second_table_names = ['[baz]','baz','[bar].[baz]','bar.baz']
|
97
|
+
@third_table_names = ['[foo].[bar].[baz]','foo.bar.baz']
|
98
|
+
@qualifed_table_names = @first_second_table_names + @third_table_names
|
99
|
+
end
|
100
|
+
|
101
|
+
should 'return clean table_name from #unqualify_table_name' do
|
102
|
+
@qualifed_table_names.each do |qtn|
|
103
|
+
assert_equal @expected_table_name,
|
104
|
+
@connection.send(:unqualify_table_name,qtn),
|
105
|
+
"This qualifed_table_name #{qtn} did not unqualify correctly."
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
should 'return nil from #unqualify_db_name when table_name is less than 2 qualified' do
|
110
|
+
@first_second_table_names.each do |qtn|
|
111
|
+
assert_equal nil, @connection.send(:unqualify_db_name,qtn),
|
112
|
+
"This qualifed_table_name #{qtn} did not return nil."
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
should 'return clean db_name from #unqualify_db_name when table is thrid level qualified' do
|
117
|
+
@third_table_names.each do |qtn|
|
118
|
+
assert_equal @expected_db_name,
|
119
|
+
@connection.send(:unqualify_db_name,qtn),
|
120
|
+
"This qualifed_table_name #{qtn} did not unqualify the db_name correctly."
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
should 'return true to #insert_sql? for inserts only' do
|
127
|
+
assert @connection.send(:insert_sql?,'INSERT...')
|
128
|
+
assert !@connection.send(:insert_sql?,'UPDATE...')
|
129
|
+
assert !@connection.send(:insert_sql?,'SELECT...')
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'for #sql_for_association_limiting?' do
|
133
|
+
|
134
|
+
should 'return false for simple selects with no GROUP BY and ORDER BY' do
|
135
|
+
assert !sql_for_association_limiting?("SELECT * FROM [posts]")
|
136
|
+
end
|
137
|
+
|
138
|
+
should 'return true to single SELECT, ideally a table/primarykey, that also has a GROUP BY and ORDER BY' do
|
139
|
+
assert sql_for_association_limiting?("SELECT [posts].id FROM...GROUP BY [posts].id ORDER BY MIN(posts.id)")
|
140
|
+
end
|
141
|
+
|
142
|
+
should 'return false to single * wildcard SELECT that also has a GROUP BY and ORDER BY' do
|
143
|
+
assert !sql_for_association_limiting?("SELECT * FROM...GROUP BY [posts].id ORDER BY MIN(posts.id)")
|
144
|
+
end
|
145
|
+
|
146
|
+
should 'return false to multiple columns in the select even when GROUP BY and ORDER BY are present' do
|
147
|
+
sql = "SELECT [accounts].credit_limit, firm_id FROM...GROUP BY firm_id ORDER BY firm_id"
|
148
|
+
assert !sql_for_association_limiting?(sql)
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'for #get_table_name' do
|
154
|
+
|
155
|
+
should 'return quoted table name from basic INSERT, UPDATE and SELECT statements' do
|
156
|
+
assert_equal '[funny_jokes]', @connection.send(:get_table_name,@basic_insert_sql)
|
157
|
+
assert_equal '[customers]', @connection.send(:get_table_name,@basic_update_sql)
|
158
|
+
assert_equal '[customers]', @connection.send(:get_table_name,@basic_select_sql)
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'dealing with various orders SQL snippets' do
|
164
|
+
|
165
|
+
setup do
|
166
|
+
@single_order = 'comments.id'
|
167
|
+
@single_order_with_desc = 'comments.id DESC'
|
168
|
+
@two_orders = 'comments.id, comments.post_id'
|
169
|
+
@two_orders_with_asc = 'comments.id, comments.post_id ASC'
|
170
|
+
@two_orders_with_desc_and_asc = 'comments.id DESC, comments.post_id ASC'
|
171
|
+
@two_duplicate_order_with_dif_dir = "id, id DESC"
|
172
|
+
end
|
173
|
+
|
174
|
+
should 'convert to an 2D array of column/direction arrays using #orders_and_dirs_set' do
|
175
|
+
assert_equal [['comments.id',nil]], orders_and_dirs_set('ORDER BY comments.id'), 'Needs to remove ORDER BY'
|
176
|
+
assert_equal [['comments.id',nil]], orders_and_dirs_set(@single_order)
|
177
|
+
assert_equal [['comments.id',nil],['comments.post_id',nil]], orders_and_dirs_set(@two_orders)
|
178
|
+
assert_equal [['comments.id',nil],['comments.post_id','ASC']], orders_and_dirs_set(@two_orders_with_asc)
|
179
|
+
assert_equal [['id',nil],['id','DESC']], orders_and_dirs_set(@two_duplicate_order_with_dif_dir)
|
180
|
+
end
|
181
|
+
|
182
|
+
should 'remove duplicate or maintain the same order by statements giving precedence to first using #add_order! method chain extension' do
|
183
|
+
assert_equal ' ORDER BY comments.id', add_order!(@single_order)
|
184
|
+
assert_equal ' ORDER BY comments.id DESC', add_order!(@single_order_with_desc)
|
185
|
+
assert_equal ' ORDER BY comments.id, comments.post_id', add_order!(@two_orders)
|
186
|
+
assert_equal ' ORDER BY comments.id DESC, comments.post_id ASC', add_order!(@two_orders_with_desc_and_asc)
|
187
|
+
assert_equal 'SELECT * FROM [developers] ORDER BY id', add_order!('id, developers.id DESC','SELECT * FROM [developers]')
|
188
|
+
assert_equal 'SELECT * FROM [developers] ORDER BY [developers].[id] DESC', add_order!('[developers].[id] DESC, id','SELECT * FROM [developers]')
|
189
|
+
end
|
190
|
+
|
191
|
+
should 'take all types of order options and convert them to MIN functions using #order_to_min_set' do
|
192
|
+
assert_equal 'MIN(comments.id)', order_to_min_set(@single_order)
|
193
|
+
assert_equal 'MIN(comments.id), MIN(comments.post_id)', order_to_min_set(@two_orders)
|
194
|
+
assert_equal 'MIN(comments.id) DESC', order_to_min_set(@single_order_with_desc)
|
195
|
+
assert_equal 'MIN(comments.id), MIN(comments.post_id) ASC', order_to_min_set(@two_orders_with_asc)
|
196
|
+
assert_equal 'MIN(comments.id) DESC, MIN(comments.post_id) ASC', order_to_min_set(@two_orders_with_desc_and_asc)
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'with different language' do
|
202
|
+
|
203
|
+
teardown do
|
204
|
+
@connection.execute("SET LANGUAGE us_english") rescue nil
|
205
|
+
end
|
206
|
+
|
207
|
+
should_eventually 'do a date insertion when language is german' do
|
208
|
+
@connection.execute("SET LANGUAGE deutsch")
|
209
|
+
assert_nothing_raised do
|
210
|
+
Task.create(:starting => Time.utc(2000, 1, 31, 5, 42, 0), :ending => Date.new(2006, 12, 31))
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
context 'testing #enable_default_unicode_types configuration' do
|
217
|
+
|
218
|
+
should 'use non-unicode types when set to false' do
|
219
|
+
with_enable_default_unicode_types(false) do
|
220
|
+
if sqlserver_2000?
|
221
|
+
assert_equal 'varchar', @connection.native_string_database_type
|
222
|
+
assert_equal 'text', @connection.native_text_database_type
|
223
|
+
elsif sqlserver_2005?
|
224
|
+
assert_equal 'varchar', @connection.native_string_database_type
|
225
|
+
assert_equal 'varchar(max)', @connection.native_text_database_type
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
should 'use unicode types when set to true' do
|
231
|
+
with_enable_default_unicode_types(true) do
|
232
|
+
if sqlserver_2000?
|
233
|
+
assert_equal 'nvarchar', @connection.native_string_database_type
|
234
|
+
assert_equal 'ntext', @connection.native_text_database_type
|
235
|
+
elsif sqlserver_2005?
|
236
|
+
assert_equal 'nvarchar', @connection.native_string_database_type
|
237
|
+
assert_equal 'nvarchar(max)', @connection.native_text_database_type
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
end
|
246
|
+
|
247
|
+
context 'For chronic data types' do
|
248
|
+
|
249
|
+
context 'with a usec' do
|
250
|
+
|
251
|
+
setup do
|
252
|
+
@time = Time.now
|
253
|
+
@db_datetime_003 = '2012-11-08 10:24:36.003'
|
254
|
+
@db_datetime_123 = '2012-11-08 10:24:36.123'
|
255
|
+
@all_datetimes = [@db_datetime_003, @db_datetime_123]
|
256
|
+
@all_datetimes.each do |datetime|
|
257
|
+
@connection.execute("INSERT INTO [sql_server_chronics] ([datetime]) VALUES('#{datetime}')")
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
teardown do
|
262
|
+
@all_datetimes.each do |datetime|
|
263
|
+
@connection.execute("DELETE FROM [sql_server_chronics] WHERE [datetime] = '#{datetime}'")
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
context 'finding existing DB objects' do
|
268
|
+
|
269
|
+
should 'find 003 millisecond in the DB with before and after casting' do
|
270
|
+
existing_003 = SqlServerChronic.find_by_datetime!(@db_datetime_003)
|
271
|
+
assert_equal @db_datetime_003, existing_003.datetime_before_type_cast
|
272
|
+
assert_equal 3000, existing_003.datetime.usec, 'A 003 millisecond in SQL Server is 3000 microseconds'
|
273
|
+
end
|
274
|
+
|
275
|
+
should 'find 123 millisecond in the DB with before and after casting' do
|
276
|
+
existing_123 = SqlServerChronic.find_by_datetime!(@db_datetime_123)
|
277
|
+
assert_equal @db_datetime_123, existing_123.datetime_before_type_cast
|
278
|
+
assert_equal 123000, existing_123.datetime.usec, 'A 123 millisecond in SQL Server is 123000 microseconds'
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
282
|
+
|
283
|
+
context 'saving new datetime objects' do
|
284
|
+
|
285
|
+
should 'truncate 123456 usec to just 123 in the DB cast back to 123000' do
|
286
|
+
@time.stubs(:usec).returns(123456)
|
287
|
+
saved = SqlServerChronic.create!(:datetime => @time).reload
|
288
|
+
assert_equal '123', saved.datetime_before_type_cast.split('.')[1]
|
289
|
+
assert_equal 123000, saved.datetime.usec
|
290
|
+
end
|
291
|
+
|
292
|
+
should 'truncate 3001 usec to just 003 in the DB cast back to 3000' do
|
293
|
+
@time.stubs(:usec).returns(3001)
|
294
|
+
saved = SqlServerChronic.create!(:datetime => @time).reload
|
295
|
+
assert_equal '003', saved.datetime_before_type_cast.split('.')[1]
|
296
|
+
assert_equal 3000, saved.datetime.usec
|
297
|
+
end
|
298
|
+
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
302
|
+
|
303
|
+
end
|
304
|
+
|
305
|
+
context 'For identity inserts' do
|
306
|
+
|
307
|
+
setup do
|
308
|
+
@identity_insert_sql = "INSERT INTO [funny_jokes] ([id],[name]) VALUES(420,'Knock knock')"
|
309
|
+
end
|
310
|
+
|
311
|
+
should 'return quoted table_name to #query_requires_identity_insert? when INSERT sql contains id_column' do
|
312
|
+
assert_equal '[funny_jokes]', @connection.send(:query_requires_identity_insert?,@identity_insert_sql)
|
313
|
+
end
|
314
|
+
|
315
|
+
should 'return false to #query_requires_identity_insert? for normal SQL' do
|
316
|
+
[@basic_insert_sql, @basic_update_sql, @basic_select_sql].each do |sql|
|
317
|
+
assert !@connection.send(:query_requires_identity_insert?,sql), "SQL was #{sql}"
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
should 'find identity column using #identity_column' do
|
322
|
+
joke_id_column = Joke.columns.detect { |c| c.name == 'id' }
|
323
|
+
assert_equal joke_id_column, @connection.send(:identity_column,Joke.table_name)
|
324
|
+
end
|
325
|
+
|
326
|
+
should 'return nil when calling #identity_column for a table_name with no identity' do
|
327
|
+
assert_nil @connection.send(:identity_column,Subscriber.table_name)
|
328
|
+
end
|
329
|
+
|
330
|
+
end
|
331
|
+
|
332
|
+
context 'For Quoting' do
|
333
|
+
|
334
|
+
should 'return 1 for #quoted_true' do
|
335
|
+
assert_equal '1', @connection.quoted_true
|
336
|
+
end
|
337
|
+
|
338
|
+
should 'return 0 for #quoted_false' do
|
339
|
+
assert_equal '0', @connection.quoted_false
|
340
|
+
end
|
341
|
+
|
342
|
+
should 'not escape backslash characters like abstract adapter' do
|
343
|
+
string_with_backslashs = "\\n"
|
344
|
+
assert_equal string_with_backslashs, @connection.quote_string(string_with_backslashs)
|
345
|
+
end
|
346
|
+
|
347
|
+
should 'quote column names with brackets' do
|
348
|
+
assert_equal '[foo]', @connection.quote_column_name(:foo)
|
349
|
+
assert_equal '[foo]', @connection.quote_column_name('foo')
|
350
|
+
assert_equal '[foo].[bar]', @connection.quote_column_name('foo.bar')
|
351
|
+
end
|
352
|
+
|
353
|
+
should 'quote table names like columns' do
|
354
|
+
assert_equal '[foo].[bar]', @connection.quote_column_name('foo.bar')
|
355
|
+
assert_equal '[foo].[bar].[baz]', @connection.quote_column_name('foo.bar.baz')
|
356
|
+
end
|
357
|
+
|
358
|
+
end
|
359
|
+
|
360
|
+
context 'When disableing referential integrity' do
|
361
|
+
|
362
|
+
setup do
|
363
|
+
@parent = FkTestHasPk.create!
|
364
|
+
@member = FkTestHasFk.create!(:fk_id => @parent.id)
|
365
|
+
end
|
366
|
+
|
367
|
+
should 'NOT ALLOW by default the deletion of a referenced parent' do
|
368
|
+
FkTestHasPk.connection.disable_referential_integrity { }
|
369
|
+
assert_raise(ActiveRecord::StatementInvalid) { @parent.destroy }
|
370
|
+
end
|
371
|
+
|
372
|
+
should 'ALLOW deletion of referenced parent using #disable_referential_integrity block' do
|
373
|
+
FkTestHasPk.connection.disable_referential_integrity { @parent.destroy }
|
374
|
+
end
|
375
|
+
|
376
|
+
should 'again NOT ALLOW deletion of referenced parent after #disable_referential_integrity block' do
|
377
|
+
assert_raise(ActiveRecord::StatementInvalid) do
|
378
|
+
FkTestHasPk.connection.disable_referential_integrity { }
|
379
|
+
@parent.destroy
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
end
|
384
|
+
|
385
|
+
context 'For DatabaseStatements' do
|
386
|
+
|
387
|
+
context "finding out what user_options are available" do
|
388
|
+
|
389
|
+
should "run the database consistency checker useroptions command" do
|
390
|
+
@connection.expects(:select_rows).with(regexp_matches(/^dbcc\s+useroptions$/i)).returns []
|
391
|
+
@connection.user_options
|
392
|
+
end
|
393
|
+
|
394
|
+
should "return a underscored key hash with indifferent access of the results" do
|
395
|
+
@connection.expects(:select_rows).with(regexp_matches(/^dbcc\s+useroptions$/i)).returns [['some', 'thing'], ['isolation level', 'read uncommitted']]
|
396
|
+
uo = @connection.user_options
|
397
|
+
assert_equal 2, uo.keys.size
|
398
|
+
assert_equal 'thing', uo['some']
|
399
|
+
assert_equal 'thing', uo[:some]
|
400
|
+
assert_equal 'read uncommitted', uo['isolation_level']
|
401
|
+
assert_equal 'read uncommitted', uo[:isolation_level]
|
402
|
+
end
|
403
|
+
|
404
|
+
end
|
405
|
+
|
406
|
+
context "altering isolation levels" do
|
407
|
+
|
408
|
+
should "barf if the requested isolation level is not valid" do
|
409
|
+
assert_raise(ArgumentError) do
|
410
|
+
@connection.run_with_isolation_level 'INVALID ISOLATION LEVEL' do; end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
context "with a valid isolation level" do
|
415
|
+
|
416
|
+
setup do
|
417
|
+
@t1 = tasks(:first_task)
|
418
|
+
@t2 = tasks(:another_task)
|
419
|
+
assert @t1, 'Tasks :first_task should be in AR fixtures'
|
420
|
+
assert @t2, 'Tasks :another_task should be in AR fixtures'
|
421
|
+
good_isolation_level = @connection.user_options[:isolation_level].blank? || @connection.user_options[:isolation_level] =~ /read committed/i
|
422
|
+
assert good_isolation_level, "User isolation level is not at a happy starting place: #{@connection.user_options[:isolation_level].inspect}"
|
423
|
+
end
|
424
|
+
|
425
|
+
should 'allow #run_with_isolation_level to not take a block to set it' do
|
426
|
+
begin
|
427
|
+
@connection.run_with_isolation_level 'READ UNCOMMITTED'
|
428
|
+
assert_match %r|read uncommitted|i, @connection.user_options[:isolation_level]
|
429
|
+
ensure
|
430
|
+
@connection.run_with_isolation_level 'READ COMMITTED'
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
should 'return block value using #run_with_isolation_level' do
|
435
|
+
assert_same_elements Task.find(:all), @connection.run_with_isolation_level('READ UNCOMMITTED') { Task.find(:all) }
|
436
|
+
end
|
437
|
+
|
438
|
+
should 'pass a read uncommitted isolation level test' do
|
439
|
+
assert_nil @t2.starting, 'Fixture should have this empty.'
|
440
|
+
begin
|
441
|
+
Task.transaction do
|
442
|
+
@t2.starting = Time.now
|
443
|
+
@t2.save
|
444
|
+
@dirty_t2 = @connection.run_with_isolation_level('READ UNCOMMITTED') { Task.find(@t2.id) }
|
445
|
+
raise ActiveRecord::ActiveRecordError
|
446
|
+
end
|
447
|
+
rescue
|
448
|
+
'Do Nothing'
|
449
|
+
end
|
450
|
+
assert @dirty_t2, 'Should have a Task record from within block above.'
|
451
|
+
assert @dirty_t2.starting, 'Should have a dirty date.'
|
452
|
+
assert_nil Task.find(@t2.id).starting, 'Should be nil again from botched transaction above.'
|
453
|
+
end unless active_record_2_point_2? # Transactions in tests are a bit screwy in 2.2.
|
454
|
+
|
455
|
+
end
|
456
|
+
|
457
|
+
end
|
458
|
+
|
459
|
+
end
|
460
|
+
|
461
|
+
context 'For SchemaStatements' do
|
462
|
+
|
463
|
+
context 'returning from #type_to_sql' do
|
464
|
+
|
465
|
+
should 'create integers when no limit supplied' do
|
466
|
+
assert_equal 'integer', @connection.type_to_sql(:integer)
|
467
|
+
end
|
468
|
+
|
469
|
+
should 'create integers when limit is 4' do
|
470
|
+
assert_equal 'integer', @connection.type_to_sql(:integer, 4)
|
471
|
+
end
|
472
|
+
|
473
|
+
should 'create integers when limit is 3' do
|
474
|
+
assert_equal 'integer', @connection.type_to_sql(:integer, 3)
|
475
|
+
end
|
476
|
+
|
477
|
+
should 'create smallints when limit is less than 3' do
|
478
|
+
assert_equal 'smallint', @connection.type_to_sql(:integer, 2)
|
479
|
+
assert_equal 'smallint', @connection.type_to_sql(:integer, 1)
|
480
|
+
end
|
481
|
+
|
482
|
+
should 'create bigints when limit is greateer than 4' do
|
483
|
+
assert_equal 'bigint', @connection.type_to_sql(:integer, 5)
|
484
|
+
assert_equal 'bigint', @connection.type_to_sql(:integer, 6)
|
485
|
+
assert_equal 'bigint', @connection.type_to_sql(:integer, 7)
|
486
|
+
assert_equal 'bigint', @connection.type_to_sql(:integer, 8)
|
487
|
+
end
|
488
|
+
|
489
|
+
end
|
490
|
+
|
491
|
+
end
|
492
|
+
|
493
|
+
context 'For indexes' do
|
494
|
+
|
495
|
+
setup do
|
496
|
+
@desc_index_name = 'idx_credit_limit_test_desc'
|
497
|
+
@connection.execute "CREATE INDEX #{@desc_index_name} ON accounts (credit_limit DESC)"
|
498
|
+
end
|
499
|
+
|
500
|
+
teardown do
|
501
|
+
@connection.execute "DROP INDEX accounts.#{@desc_index_name}"
|
502
|
+
end
|
503
|
+
|
504
|
+
should 'have indexes with descending order' do
|
505
|
+
assert @connection.indexes('accounts').detect { |i| i.name == @desc_index_name }
|
506
|
+
end
|
507
|
+
|
508
|
+
end
|
509
|
+
|
510
|
+
context 'For views' do
|
511
|
+
|
512
|
+
context 'using @connection.views' do
|
513
|
+
|
514
|
+
should 'return an array' do
|
515
|
+
assert_instance_of Array, @connection.views
|
516
|
+
end
|
517
|
+
|
518
|
+
should 'find CustomersView table name' do
|
519
|
+
assert_contains @connection.views, 'customers_view'
|
520
|
+
end
|
521
|
+
|
522
|
+
should 'not contain system views' do
|
523
|
+
systables = ['sysconstraints','syssegments']
|
524
|
+
systables.each do |systable|
|
525
|
+
assert !@connection.views.include?(systable), "This systable #{systable} should not be in the views array."
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
should 'allow the connection.view_information method to return meta data on the view' do
|
530
|
+
view_info = @connection.view_information('customers_view')
|
531
|
+
assert_equal('customers_view', view_info['TABLE_NAME'])
|
532
|
+
assert_match(/CREATE VIEW customers_view/, view_info['VIEW_DEFINITION'])
|
533
|
+
end
|
534
|
+
|
535
|
+
should 'allow the connection.view_table_name method to return true table_name for the view' do
|
536
|
+
assert_equal 'customers', @connection.view_table_name('customers_view')
|
537
|
+
assert_equal 'topics', @connection.view_table_name('topics'), 'No view here, the same table name should come back.'
|
538
|
+
end
|
539
|
+
|
540
|
+
end
|
541
|
+
|
542
|
+
context 'used by a class for table_name' do
|
543
|
+
|
544
|
+
context 'with same column names' do
|
545
|
+
|
546
|
+
should 'have matching column objects' do
|
547
|
+
columns = ['id','name','balance']
|
548
|
+
assert !CustomersView.columns.blank?
|
549
|
+
assert_equal columns.size, CustomersView.columns.size
|
550
|
+
columns.each do |colname|
|
551
|
+
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
|
552
|
+
CustomersView.columns_hash[colname],
|
553
|
+
"Column name #{colname.inspect} was not found in these columns #{CustomersView.columns.map(&:name).inspect}"
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
should 'find identity column' do
|
558
|
+
assert CustomersView.columns_hash['id'].primary
|
559
|
+
assert CustomersView.columns_hash['id'].is_identity?
|
560
|
+
end
|
561
|
+
|
562
|
+
should 'find default values' do
|
563
|
+
assert_equal 0, CustomersView.new.balance
|
564
|
+
end
|
565
|
+
|
566
|
+
should 'respond true to table_exists?' do
|
567
|
+
assert CustomersView.table_exists?
|
568
|
+
end
|
569
|
+
|
570
|
+
should 'have correct table name for all column objects' do
|
571
|
+
assert CustomersView.columns.all?{ |c| c.table_name == 'customers_view' },
|
572
|
+
CustomersView.columns.map(&:table_name).inspect
|
573
|
+
end
|
574
|
+
|
575
|
+
end
|
576
|
+
|
577
|
+
context 'with aliased column names' do
|
578
|
+
|
579
|
+
should 'have matching column objects' do
|
580
|
+
columns = ['id','pretend_null']
|
581
|
+
assert !StringDefaultsView.columns.blank?
|
582
|
+
assert_equal columns.size, StringDefaultsView.columns.size
|
583
|
+
columns.each do |colname|
|
584
|
+
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
|
585
|
+
StringDefaultsView.columns_hash[colname],
|
586
|
+
"Column name #{colname.inspect} was not found in these columns #{StringDefaultsView.columns.map(&:name).inspect}"
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
should 'find identity column' do
|
591
|
+
assert StringDefaultsView.columns_hash['id'].primary
|
592
|
+
assert StringDefaultsView.columns_hash['id'].is_identity?
|
593
|
+
end
|
594
|
+
|
595
|
+
should 'find default values' do
|
596
|
+
assert_equal 'null', StringDefaultsView.new.pretend_null,
|
597
|
+
StringDefaultsView.columns_hash['pretend_null'].inspect
|
598
|
+
end
|
599
|
+
|
600
|
+
should 'respond true to table_exists?' do
|
601
|
+
assert StringDefaultsView.table_exists?
|
602
|
+
end
|
603
|
+
|
604
|
+
should 'have correct table name for all column objects' do
|
605
|
+
assert StringDefaultsView.columns.all?{ |c| c.table_name == 'string_defaults_view' },
|
606
|
+
StringDefaultsView.columns.map(&:table_name).inspect
|
607
|
+
end
|
608
|
+
|
609
|
+
end
|
610
|
+
|
611
|
+
end
|
612
|
+
|
613
|
+
context 'doing identity inserts' do
|
614
|
+
|
615
|
+
setup do
|
616
|
+
@view_insert_sql = "INSERT INTO [customers_view] ([id],[name],[balance]) VALUES (420,'Microsoft',0)"
|
617
|
+
end
|
618
|
+
|
619
|
+
should 'respond true/tablename to #query_requires_identity_insert?' do
|
620
|
+
assert_equal '[customers_view]', @connection.send(:query_requires_identity_insert?,@view_insert_sql)
|
621
|
+
end
|
622
|
+
|
623
|
+
should 'be able to do an identity insert' do
|
624
|
+
assert_nothing_raised { @connection.execute(@view_insert_sql) }
|
625
|
+
assert CustomersView.find(420)
|
626
|
+
end
|
627
|
+
|
628
|
+
end
|
629
|
+
|
630
|
+
context 'that have more than 4000 chars for their defintion' do
|
631
|
+
|
632
|
+
should 'cope with null returned for the defintion' do
|
633
|
+
assert_nothing_raised() { StringDefaultsBigView.columns }
|
634
|
+
end
|
635
|
+
|
636
|
+
should 'using alternate view defintion still be able to find real default' do
|
637
|
+
assert_equal 'null', StringDefaultsBigView.new.pretend_null,
|
638
|
+
StringDefaultsBigView.columns_hash['pretend_null'].inspect
|
639
|
+
end
|
640
|
+
|
641
|
+
end
|
642
|
+
|
643
|
+
end
|
644
|
+
|
645
|
+
|
646
|
+
|
647
|
+
private
|
648
|
+
|
649
|
+
def sql_for_association_limiting?(sql)
|
650
|
+
@connection.send :sql_for_association_limiting?, sql
|
651
|
+
end
|
652
|
+
|
653
|
+
def orders_and_dirs_set(order)
|
654
|
+
@connection.send :orders_and_dirs_set, order
|
655
|
+
end
|
656
|
+
|
657
|
+
def add_order!(order,sql='')
|
658
|
+
ActiveRecord::Base.send :add_order!, sql, order, nil
|
659
|
+
sql
|
660
|
+
end
|
661
|
+
|
662
|
+
def order_to_min_set(order)
|
663
|
+
@connection.send :order_to_min_set, order
|
664
|
+
end
|
665
|
+
|
666
|
+
def with_enable_default_unicode_types(setting)
|
667
|
+
old_setting = ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types
|
668
|
+
old_text = ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type
|
669
|
+
old_string = ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type
|
670
|
+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types = setting
|
671
|
+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = nil
|
672
|
+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type = nil
|
673
|
+
yield
|
674
|
+
ensure
|
675
|
+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types = old_setting
|
676
|
+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = old_text
|
677
|
+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type = old_string
|
678
|
+
end
|
679
|
+
|
680
|
+
end
|
681
|
+
|
682
|
+
|
683
|
+
class AdapterTest < ActiveRecord::TestCase
|
684
|
+
|
685
|
+
COERCED_TESTS = [
|
686
|
+
:test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas,
|
687
|
+
:test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas
|
688
|
+
]
|
689
|
+
|
690
|
+
include SqlserverCoercedTest
|
691
|
+
|
692
|
+
def test_coerced_test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas
|
693
|
+
sql_inject = "1 select * from schema"
|
694
|
+
connection = ActiveRecord::Base.connection
|
695
|
+
assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject) }
|
696
|
+
assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) }
|
697
|
+
end
|
698
|
+
|
699
|
+
def test_coerced_test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas
|
700
|
+
sql_inject = "1, 7 procedure help()"
|
701
|
+
connection = ActiveRecord::Base.connection
|
702
|
+
assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject) }
|
703
|
+
assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=> '1 ; DROP TABLE USERS', :offset=>7) }
|
704
|
+
assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) }
|
705
|
+
end
|
706
|
+
|
707
|
+
end
|