activerecord-sqlserver-adapter 4.1.8 → 4.2.0.pre
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.
- checksums.yaml +4 -4
- data/.gitignore +15 -0
- data/CHANGELOG.md +60 -0
- data/Gemfile +45 -0
- data/Guardfile +29 -0
- data/MIT-LICENSE +5 -5
- data/README.md +193 -0
- data/RUNNING_UNIT_TESTS.md +95 -0
- data/Rakefile +48 -0
- data/activerecord-sqlserver-adapter.gemspec +28 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +5 -15
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +25 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +6 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +9 -3
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +130 -151
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +0 -25
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +39 -78
- data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +71 -47
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +14 -30
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +112 -108
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +4 -2
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +52 -7
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +52 -0
- data/lib/active_record/connection_adapters/sqlserver/type.rb +46 -0
- data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +13 -0
- data/lib/active_record/connection_adapters/sqlserver/type/castable.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/char.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/core_ext/value.rb +39 -0
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +14 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +37 -0
- data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/sqlserver/type/float.rb +17 -0
- data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +13 -0
- data/lib/active_record/connection_adapters/sqlserver/type/money.rb +21 -0
- data/lib/active_record/connection_adapters/sqlserver/type/quoter.rb +32 -0
- data/lib/active_record/connection_adapters/sqlserver/type/real.rb +17 -0
- data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +13 -0
- data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +21 -0
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +24 -0
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/text.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +59 -0
- data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +22 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +23 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +118 -12
- data/lib/active_record/connection_adapters/sqlserver/version.rb +11 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +133 -198
- data/lib/active_record/connection_adapters/sqlserver_column.rb +15 -86
- data/lib/active_record/sqlserver_base.rb +2 -0
- data/lib/arel/visitors/sqlserver.rb +120 -393
- data/lib/{arel/arel_sqlserver.rb → arel_sqlserver.rb} +1 -3
- data/test/cases/adapter_test_sqlserver.rb +420 -0
- data/test/cases/coerced_tests.rb +642 -0
- data/test/cases/column_test_sqlserver.rb +703 -0
- data/test/cases/connection_test_sqlserver.rb +216 -0
- data/test/cases/database_statements_test_sqlserver.rb +57 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +38 -0
- data/test/cases/helper_sqlserver.rb +36 -0
- data/test/cases/migration_test_sqlserver.rb +66 -0
- data/test/cases/order_test_sqlserver.rb +147 -0
- data/test/cases/pessimistic_locking_test_sqlserver.rb +90 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +175 -0
- data/test/cases/schema_test_sqlserver.rb +54 -0
- data/test/cases/scratchpad_test_sqlserver.rb +9 -0
- data/test/cases/showplan_test_sqlserver.rb +65 -0
- data/test/cases/specific_schema_test_sqlserver.rb +118 -0
- data/test/cases/transaction_test_sqlserver.rb +61 -0
- data/test/cases/utils_test_sqlserver.rb +91 -0
- data/test/cases/uuid_test_sqlserver.rb +41 -0
- data/test/config.yml +35 -0
- data/test/fixtures/1px.gif +0 -0
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
- data/test/models/sqlserver/customers_view.rb +3 -0
- data/test/models/sqlserver/datatype.rb +3 -0
- data/test/models/sqlserver/datatype_migration.rb +3 -0
- data/test/models/sqlserver/dollar_table_name.rb +3 -0
- data/test/models/sqlserver/edge_schema.rb +13 -0
- data/test/models/sqlserver/fk_has_fk.rb +3 -0
- data/test/models/sqlserver/fk_has_pk.rb +3 -0
- data/test/models/sqlserver/natural_pk_data.rb +4 -0
- data/test/models/sqlserver/natural_pk_int_data.rb +3 -0
- data/test/models/sqlserver/no_pk_data.rb +3 -0
- data/test/models/sqlserver/quoted_table.rb +7 -0
- data/test/models/sqlserver/quoted_view_1.rb +3 -0
- data/test/models/sqlserver/quoted_view_2.rb +3 -0
- data/test/models/sqlserver/string_default.rb +3 -0
- data/test/models/sqlserver/string_defaults_big_view.rb +3 -0
- data/test/models/sqlserver/string_defaults_view.rb +3 -0
- data/test/models/sqlserver/tinyint_pk.rb +3 -0
- data/test/models/sqlserver/upper.rb +3 -0
- data/test/models/sqlserver/uppered.rb +3 -0
- data/test/models/sqlserver/uuid.rb +3 -0
- data/test/schema/datatypes/2012.sql +64 -0
- data/test/schema/sqlserver_specific_schema.rb +181 -0
- data/test/support/coerceable_test_sqlserver.rb +45 -0
- data/test/support/load_schema_sqlserver.rb +29 -0
- data/test/support/minitest_sqlserver.rb +1 -0
- data/test/support/paths_sqlserver.rb +48 -0
- data/test/support/rake_helpers.rb +41 -0
- data/test/support/sql_counter_sqlserver.rb +32 -0
- metadata +271 -21
- data/CHANGELOG +0 -39
- data/VERSION +0 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +0 -17
- data/lib/active_record/sqlserver_test_case.rb +0 -17
- data/lib/arel/nodes_sqlserver.rb +0 -14
- data/lib/arel/select_manager_sqlserver.rb +0 -62
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
require 'cases/helper_sqlserver'
|
|
2
|
+
require 'models/topic'
|
|
3
|
+
require 'models/task'
|
|
4
|
+
require 'models/post'
|
|
5
|
+
require 'models/subscriber'
|
|
6
|
+
require 'models/minimalistic'
|
|
7
|
+
|
|
8
|
+
class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
9
|
+
|
|
10
|
+
fixtures :tasks
|
|
11
|
+
|
|
12
|
+
let(:basic_insert_sql) { "INSERT INTO [funny_jokes] ([name]) VALUES('Knock knock')" }
|
|
13
|
+
let(:basic_update_sql) { "UPDATE [customers] SET [address_street] = NULL WHERE [id] = 2" }
|
|
14
|
+
let(:basic_select_sql) { "SELECT * FROM [customers] WHERE ([customers].[id] = 1)" }
|
|
15
|
+
|
|
16
|
+
it 'has basic and non-senstive information in the adpaters inspect method' do
|
|
17
|
+
string = connection.inspect
|
|
18
|
+
string.must_match %r{ActiveRecord::ConnectionAdapters::SQLServerAdapter}
|
|
19
|
+
string.must_match %r{version\: \d.\d}
|
|
20
|
+
string.must_match %r{mode: (dblib|odbc)}
|
|
21
|
+
string.must_match %r{azure: (true|false)}
|
|
22
|
+
string.wont_match %r{host}
|
|
23
|
+
string.wont_match %r{password}
|
|
24
|
+
string.wont_match %r{username}
|
|
25
|
+
string.wont_match %r{port}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'has a 128 max #table_alias_length' do
|
|
29
|
+
assert connection.table_alias_length <= 128
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'raises invalid statement error for bad SQL' do
|
|
33
|
+
assert_raise(ActiveRecord::StatementInvalid) { Topic.connection.update("UPDATE XXX") }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'is has our adapter_name' do
|
|
37
|
+
assert_equal 'SQLServer', connection.adapter_name
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'supports migrations' do
|
|
41
|
+
assert connection.supports_migrations?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'support DDL in transactions' do
|
|
45
|
+
assert connection.supports_ddl_transactions?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'allow owner table name prefixs like dbo to still allow table exists to return true' do
|
|
49
|
+
begin
|
|
50
|
+
assert_equal 'topics', Topic.table_name
|
|
51
|
+
assert Topic.table_exists?
|
|
52
|
+
Topic.table_name = 'dbo.topics'
|
|
53
|
+
assert Topic.table_exists?, 'Tasks table name of dbo.topics should return true for exists.'
|
|
54
|
+
ensure
|
|
55
|
+
Topic.table_name = 'topics'
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'return true to insert sql query for inserts only' do
|
|
60
|
+
assert connection.send(:insert_sql?,'INSERT...')
|
|
61
|
+
assert connection.send(:insert_sql?, "EXEC sp_executesql N'INSERT INTO [fk_test_has_fks] ([fk_id]) VALUES (@0); SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident', N'@0 int', @0 = 0")
|
|
62
|
+
assert !connection.send(:insert_sql?,'UPDATE...')
|
|
63
|
+
assert !connection.send(:insert_sql?,'SELECT...')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'return unquoted table name object from basic INSERT UPDATE and SELECT statements' do
|
|
67
|
+
assert_equal 'funny_jokes', connection.send(:get_table_name, basic_insert_sql)
|
|
68
|
+
assert_equal 'customers', connection.send(:get_table_name, basic_update_sql)
|
|
69
|
+
assert_equal 'customers', connection.send(:get_table_name, basic_select_sql)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe 'with different language' do
|
|
73
|
+
|
|
74
|
+
before do
|
|
75
|
+
@default_language = connection.user_options_language
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
after do
|
|
79
|
+
connection.execute("SET LANGUAGE #{@default_language}") rescue nil
|
|
80
|
+
connection.send :initialize_dateformatter
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'memos users dateformat' do
|
|
84
|
+
connection.execute("SET LANGUAGE us_english") rescue nil
|
|
85
|
+
dateformat = connection.instance_variable_get(:@database_dateformat)
|
|
86
|
+
assert_equal 'mdy', dateformat
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'has a dateformatter' do
|
|
90
|
+
assert Date::DATE_FORMATS[:_sqlserver_dateformat]
|
|
91
|
+
assert Time::DATE_FORMATS[:_sqlserver_dateformat]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'does a datetime insertion when language is german' do
|
|
95
|
+
connection.execute("SET LANGUAGE deutsch")
|
|
96
|
+
connection.send :initialize_dateformatter
|
|
97
|
+
assert_nothing_raised do
|
|
98
|
+
starting = Time.utc(2000, 1, 31, 5, 42, 0)
|
|
99
|
+
ending = Date.new(2006, 12, 31)
|
|
100
|
+
Task.create! starting: starting, ending: ending
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
describe 'testing #lowercase_schema_reflection' do
|
|
107
|
+
|
|
108
|
+
before do
|
|
109
|
+
SSTestUpper.delete_all
|
|
110
|
+
SSTestUpper.create COLUMN1: 'Got a minute?', COLUMN2: 419
|
|
111
|
+
SSTestUpper.create COLUMN1: 'Favorite number?', COLUMN2: 69
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
after do
|
|
115
|
+
connection.lowercase_schema_reflection = false
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it 'not lowercase schema reflection by default' do
|
|
119
|
+
assert SSTestUpper.columns_hash['COLUMN1']
|
|
120
|
+
assert_equal 'Got a minute?', SSTestUpper.first.COLUMN1
|
|
121
|
+
assert_equal 'Favorite number?', SSTestUpper.last.COLUMN1
|
|
122
|
+
assert SSTestUpper.columns_hash['COLUMN2']
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'lowercase schema reflection when set' do
|
|
126
|
+
connection.lowercase_schema_reflection = true
|
|
127
|
+
assert SSTestUppered.columns_hash['column1']
|
|
128
|
+
assert_equal 'Got a minute?', SSTestUppered.first.column1
|
|
129
|
+
assert_equal 'Favorite number?', SSTestUppered.last.column1
|
|
130
|
+
assert SSTestUppered.columns_hash['column2']
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
describe 'identity inserts' do
|
|
136
|
+
|
|
137
|
+
before do
|
|
138
|
+
@identity_insert_sql = "INSERT INTO [funny_jokes] ([id],[name]) VALUES(420,'Knock knock')"
|
|
139
|
+
@identity_insert_sql_unquoted = "INSERT INTO funny_jokes (id, name) VALUES(420, 'Knock knock')"
|
|
140
|
+
@identity_insert_sql_unordered = "INSERT INTO [funny_jokes] ([name],[id]) VALUES('Knock knock',420)"
|
|
141
|
+
@identity_insert_sql_sp = "EXEC sp_executesql N'INSERT INTO [funny_jokes] ([id],[name]) VALUES (@0, @1)', N'@0 int, @1 nvarchar(255)', @0 = 420, @1 = N'Knock knock'"
|
|
142
|
+
@identity_insert_sql_unquoted_sp = "EXEC sp_executesql N'INSERT INTO [funny_jokes] (id, name) VALUES (@0, @1)', N'@0 int, @1 nvarchar(255)', @0 = 420, @1 = N'Knock knock'"
|
|
143
|
+
@identity_insert_sql_unordered_sp = "EXEC sp_executesql N'INSERT INTO [funny_jokes] ([name],[id]) VALUES (@0, @1)', N'@0 nvarchar(255), @1 int', @0 = N'Knock knock', @1 = 420"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it 'return quoted table_name to #query_requires_identity_insert? when INSERT sql contains id column' do
|
|
147
|
+
assert_equal '[funny_jokes]', connection.send(:query_requires_identity_insert?,@identity_insert_sql)
|
|
148
|
+
assert_equal '[funny_jokes]', connection.send(:query_requires_identity_insert?,@identity_insert_sql_unquoted)
|
|
149
|
+
assert_equal '[funny_jokes]', connection.send(:query_requires_identity_insert?,@identity_insert_sql_unordered)
|
|
150
|
+
assert_equal '[funny_jokes]', connection.send(:query_requires_identity_insert?,@identity_insert_sql_sp)
|
|
151
|
+
assert_equal '[funny_jokes]', connection.send(:query_requires_identity_insert?,@identity_insert_sql_unquoted_sp)
|
|
152
|
+
assert_equal '[funny_jokes]', connection.send(:query_requires_identity_insert?,@identity_insert_sql_unordered_sp)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'return false to #query_requires_identity_insert? for normal SQL' do
|
|
156
|
+
[basic_insert_sql, basic_update_sql, basic_select_sql].each do |sql|
|
|
157
|
+
assert !connection.send(:query_requires_identity_insert?,sql), "SQL was #{sql}"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'find identity column using #identity_column' do
|
|
162
|
+
task_id_column = Task.columns_hash['id']
|
|
163
|
+
assert_equal task_id_column.name, connection.send(:identity_column, Task.table_name).name
|
|
164
|
+
assert_equal task_id_column.sql_type, connection.send(:identity_column, Task.table_name).sql_type
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it 'return nil when calling #identity_column for a table_name with no identity' do
|
|
168
|
+
assert_nil connection.send(:identity_column, Subscriber.table_name)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
describe 'quoting' do
|
|
174
|
+
|
|
175
|
+
it 'return 1 for #quoted_true' do
|
|
176
|
+
assert_equal '1', connection.quoted_true
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it 'return 0 for #quoted_false' do
|
|
180
|
+
assert_equal '0', connection.quoted_false
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it 'not escape backslash characters like abstract adapter' do
|
|
184
|
+
string_with_backslashs = "\\n"
|
|
185
|
+
assert_equal string_with_backslashs, connection.quote_string(string_with_backslashs)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it 'quote column names with brackets' do
|
|
189
|
+
assert_equal '[foo]', connection.quote_column_name(:foo)
|
|
190
|
+
assert_equal '[foo]', connection.quote_column_name('foo')
|
|
191
|
+
assert_equal '[foo].[bar]', connection.quote_column_name('foo.bar')
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it 'not quote already quoted column names with brackets' do
|
|
195
|
+
assert_equal '[foo]', connection.quote_column_name('[foo]')
|
|
196
|
+
assert_equal '[foo].[bar]', connection.quote_column_name('[foo].[bar]')
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it 'quote table names like columns' do
|
|
200
|
+
assert_equal '[foo].[bar]', connection.quote_column_name('foo.bar')
|
|
201
|
+
assert_equal '[foo].[bar].[baz]', connection.quote_column_name('foo.bar.baz')
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it "surround string with national prefix" do
|
|
205
|
+
assert_equal "N'foo'", connection.quote("foo")
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
it "escape all single quotes by repeating them" do
|
|
209
|
+
assert_equal "N'''quotation''s'''", connection.quote("'quotation's'")
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
describe 'disabling referential integrity' do
|
|
215
|
+
|
|
216
|
+
before do
|
|
217
|
+
connection.disable_referential_integrity { SSTestHasPk.delete_all; SSTestHasFk.delete_all }
|
|
218
|
+
@parent = SSTestHasPk.create!
|
|
219
|
+
@member = SSTestHasFk.create!(fk_id: @parent.id)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it 'NOT ALLOW by default the deletion of a referenced parent' do
|
|
223
|
+
SSTestHasPk.connection.disable_referential_integrity { }
|
|
224
|
+
assert_raise(ActiveRecord::StatementInvalid) { @parent.destroy }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it 'ALLOW deletion of referenced parent using #disable_referential_integrity block' do
|
|
228
|
+
SSTestHasPk.connection.disable_referential_integrity { @parent.destroy }
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it 'again NOT ALLOW deletion of referenced parent after #disable_referential_integrity block' do
|
|
232
|
+
assert_raise(ActiveRecord::StatementInvalid) do
|
|
233
|
+
SSTestHasPk.connection.disable_referential_integrity { }
|
|
234
|
+
@parent.destroy
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
describe 'database statements' do
|
|
241
|
+
|
|
242
|
+
it "run the database consistency checker useroptions command" do
|
|
243
|
+
keys = [:textsize, :language, :isolation_level, :dateformat]
|
|
244
|
+
user_options = connection.user_options
|
|
245
|
+
keys.each do |key|
|
|
246
|
+
msg = "Expected key:#{key} in user_options:#{user_options.inspect}"
|
|
247
|
+
assert user_options.key?(key), msg
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
it "return a underscored key hash with indifferent access of the results" do
|
|
252
|
+
user_options = connection.user_options
|
|
253
|
+
assert_equal 'read committed', user_options['isolation_level']
|
|
254
|
+
assert_equal 'read committed', user_options[:isolation_level]
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
describe 'schema statements' do
|
|
260
|
+
|
|
261
|
+
it 'create integers when no limit supplied' do
|
|
262
|
+
assert_equal 'integer', connection.type_to_sql(:integer)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
it 'create integers when limit is 4' do
|
|
266
|
+
assert_equal 'integer', connection.type_to_sql(:integer, 4)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
it 'create integers when limit is 3' do
|
|
270
|
+
assert_equal 'integer', connection.type_to_sql(:integer, 3)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
it 'create smallints when limit is less than 3' do
|
|
274
|
+
assert_equal 'smallint', connection.type_to_sql(:integer, 2)
|
|
275
|
+
assert_equal 'smallint', connection.type_to_sql(:integer, 1)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
it 'create bigints when limit is greateer than 4' do
|
|
279
|
+
assert_equal 'bigint', connection.type_to_sql(:integer, 5)
|
|
280
|
+
assert_equal 'bigint', connection.type_to_sql(:integer, 6)
|
|
281
|
+
assert_equal 'bigint', connection.type_to_sql(:integer, 7)
|
|
282
|
+
assert_equal 'bigint', connection.type_to_sql(:integer, 8)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
it 'create floats when no limit supplied' do
|
|
286
|
+
assert_equal 'float', connection.type_to_sql(:float)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
describe 'indexes' do
|
|
292
|
+
|
|
293
|
+
let(:desc_index_name) { 'idx_credit_limit_test_desc' }
|
|
294
|
+
|
|
295
|
+
it 'have indexes with descending order' do
|
|
296
|
+
begin
|
|
297
|
+
connection.execute "CREATE INDEX [#{desc_index_name}] ON [accounts] (credit_limit DESC)"
|
|
298
|
+
assert connection.indexes('accounts').find { |i| i.name == desc_index_name }
|
|
299
|
+
ensure
|
|
300
|
+
connection.execute "DROP INDEX [#{desc_index_name}] ON [accounts]"
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
describe 'views' do
|
|
307
|
+
|
|
308
|
+
# Using connection.views
|
|
309
|
+
|
|
310
|
+
it 'return an array' do
|
|
311
|
+
assert_instance_of Array, connection.views
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
it 'find SSTestCustomersView table name' do
|
|
315
|
+
connection.views.must_include 'sst_customers_view'
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
it 'work with dynamic finders' do
|
|
319
|
+
name = 'MetaSkills'
|
|
320
|
+
customer = SSTestCustomersView.create! name: name
|
|
321
|
+
assert_equal customer, SSTestCustomersView.find_by_name(name)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
it 'not contain system views' do
|
|
325
|
+
systables = ['sysconstraints','syssegments']
|
|
326
|
+
systables.each do |systable|
|
|
327
|
+
assert !connection.views.include?(systable), "This systable #{systable} should not be in the views array."
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
it 'allow the connection#view_information method to return meta data on the view' do
|
|
332
|
+
view_info = connection.send(:view_information,'sst_customers_view')
|
|
333
|
+
assert_equal('sst_customers_view', view_info['TABLE_NAME'])
|
|
334
|
+
assert_match(/CREATE VIEW sst_customers_view/, view_info['VIEW_DEFINITION'])
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
it 'allow the connection#view_table_name method to return true table_name for the view' do
|
|
338
|
+
assert_equal 'customers', connection.send(:view_table_name,'sst_customers_view')
|
|
339
|
+
assert_equal 'topics', connection.send(:view_table_name,'topics'), 'No view here, the same table name should come back.'
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# With same column names
|
|
343
|
+
|
|
344
|
+
it 'have matching column objects' do
|
|
345
|
+
columns = ['id','name','balance']
|
|
346
|
+
assert !SSTestCustomersView.columns.blank?
|
|
347
|
+
assert_equal columns.size, SSTestCustomersView.columns.size
|
|
348
|
+
columns.each do |colname|
|
|
349
|
+
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
|
|
350
|
+
SSTestCustomersView.columns_hash[colname],
|
|
351
|
+
"Column name #{colname.inspect} was not found in these columns #{SSTestCustomersView.columns.map(&:name).inspect}"
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
it 'find identity column' do
|
|
356
|
+
SSTestCustomersView.primary_key.must_equal 'id'
|
|
357
|
+
connection.primary_key(SSTestCustomersView.table_name).must_equal 'id'
|
|
358
|
+
SSTestCustomersView.columns_hash['id'].must_be :is_identity?
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
it 'find default values' do
|
|
362
|
+
assert_equal 0, SSTestCustomersView.new.balance
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
it 'respond true to table_exists?' do
|
|
366
|
+
assert SSTestCustomersView.table_exists?
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# With aliased column names
|
|
370
|
+
|
|
371
|
+
it 'have matching column objects' do
|
|
372
|
+
columns = ['id','pretend_null']
|
|
373
|
+
assert !SSTestStringDefaultsView.columns.blank?
|
|
374
|
+
assert_equal columns.size, SSTestStringDefaultsView.columns.size
|
|
375
|
+
columns.each do |colname|
|
|
376
|
+
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
|
|
377
|
+
SSTestStringDefaultsView.columns_hash[colname],
|
|
378
|
+
"Column name #{colname.inspect} was not found in these columns #{SSTestStringDefaultsView.columns.map(&:name).inspect}"
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
it 'find identity column' do
|
|
383
|
+
SSTestStringDefaultsView.primary_key.must_equal 'id'
|
|
384
|
+
connection.primary_key(SSTestStringDefaultsView.table_name).must_equal 'id'
|
|
385
|
+
SSTestStringDefaultsView.columns_hash['id'].must_be :is_identity?
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
it 'find default values' do
|
|
389
|
+
assert_equal 'null', SSTestStringDefaultsView.new.pretend_null,
|
|
390
|
+
SSTestStringDefaultsView.columns_hash['pretend_null'].inspect
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
it 'respond true to table_exists?' do
|
|
394
|
+
assert SSTestStringDefaultsView.table_exists?
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# Doing identity inserts
|
|
398
|
+
|
|
399
|
+
it 'be able to do an identity insert' do
|
|
400
|
+
customer = SSTestCustomersView.new
|
|
401
|
+
customer.id = 420
|
|
402
|
+
customer.save!
|
|
403
|
+
assert SSTestCustomersView.find(420)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# That have more than 4000 chars for their defintion
|
|
407
|
+
|
|
408
|
+
it 'cope with null returned for the defintion' do
|
|
409
|
+
assert_nothing_raised() { SSTestStringDefaultsBigView.columns }
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
it 'using alternate view defintion still be able to find real default' do
|
|
413
|
+
assert_equal 'null', SSTestStringDefaultsBigView.new.pretend_null,
|
|
414
|
+
SSTestStringDefaultsBigView.columns_hash['pretend_null'].inspect
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
end
|
|
420
|
+
|
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
require 'cases/helper_sqlserver'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
class AdapterTest < ActiveRecord::TestCase
|
|
6
|
+
|
|
7
|
+
# As far as I can tell, SQL Server does not support null bytes in strings.
|
|
8
|
+
coerce_tests! :test_update_prepared_statement
|
|
9
|
+
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
require 'models/topic'
|
|
17
|
+
class AttributeMethodsTest < ActiveRecord::TestCase
|
|
18
|
+
|
|
19
|
+
coerce_tests! :test_typecast_attribute_from_select_to_false
|
|
20
|
+
def test_typecast_attribute_from_select_to_false_coerced
|
|
21
|
+
Topic.create(:title => 'Budget')
|
|
22
|
+
topic = Topic.all.merge!(:select => "topics.*, IIF (1 = 2, 1, 0) as is_test").first
|
|
23
|
+
assert !topic.is_test?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
coerce_tests! :test_typecast_attribute_from_select_to_true
|
|
27
|
+
def test_typecast_attribute_from_select_to_true_coerced
|
|
28
|
+
Topic.create(:title => 'Budget')
|
|
29
|
+
topic = Topic.all.merge!(:select => "topics.*, IIF (1 = 1, 1, 0) as is_test").first
|
|
30
|
+
assert topic.is_test?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BasicsTest < ActiveRecord::TestCase
|
|
39
|
+
|
|
40
|
+
coerce_tests! :test_column_names_are_escaped
|
|
41
|
+
def test_column_names_are_escaped_coerced
|
|
42
|
+
conn = ActiveRecord::Base.connection
|
|
43
|
+
classname = conn.class.name[/[^:]*$/]
|
|
44
|
+
badchar = "'"
|
|
45
|
+
quoted = conn.quote_column_name "foo#{badchar}bar"
|
|
46
|
+
assert_equal "[foo'bar]", quoted
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# This test has a few problems. First, it would require that we use
|
|
50
|
+
# the `Type::SQLServer::BigInteger.new(limit: 8)` for the `world_population`
|
|
51
|
+
# attribute. Second, since we allow the DB to win at casting for TinyTDS,
|
|
52
|
+
# it always comes back as a BigDecimal.
|
|
53
|
+
coerce_tests! :test_numeric_fields
|
|
54
|
+
|
|
55
|
+
# Just like PostgreSQLAdapter does.
|
|
56
|
+
coerce_tests! :test_respect_internal_encoding
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class BelongsToAssociationsTest < ActiveRecord::TestCase
|
|
64
|
+
|
|
65
|
+
# Since @client.firm is a single first/top, and we use FETCH the order clause is used.
|
|
66
|
+
coerce_tests! :test_belongs_to_does_not_use_order_by
|
|
67
|
+
|
|
68
|
+
coerce_tests! :test_belongs_to_with_primary_key_joins_on_correct_column
|
|
69
|
+
def test_belongs_to_with_primary_key_joins_on_correct_column_coerced
|
|
70
|
+
sql = Client.joins(:firm_with_primary_key).to_sql
|
|
71
|
+
assert_no_match(/\[firm_with_primary_keys_companies\]\.\[id\]/, sql)
|
|
72
|
+
assert_match(/\[firm_with_primary_keys_companies\]\.\[name\]/, sql)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
module ActiveRecord
|
|
81
|
+
class BindParameterTest < ActiveRecord::TestCase
|
|
82
|
+
|
|
83
|
+
# Never finds `sql` since we use `EXEC sp_executesql` wrappers.
|
|
84
|
+
coerce_tests! :test_binds_are_logged,
|
|
85
|
+
:test_binds_are_logged_after_type_cast
|
|
86
|
+
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class CalculationsTest < ActiveRecord::TestCase
|
|
94
|
+
|
|
95
|
+
# Are decimal, not integer.
|
|
96
|
+
coerce_tests! :test_should_return_decimal_average_of_integer_field
|
|
97
|
+
def test_should_return_decimal_average_of_integer_field_coerced
|
|
98
|
+
value = Account.average(:id)
|
|
99
|
+
assert_equal BigDecimal('3.0').to_s, BigDecimal(value).to_s
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
coerce_tests! :test_limit_is_kept
|
|
103
|
+
def test_limit_is_kept_coerced
|
|
104
|
+
queries = assert_sql { Account.limit(1).count }
|
|
105
|
+
assert_equal 1, queries.length
|
|
106
|
+
queries.first.must_match %r{ORDER BY \[accounts\]\.\[id\] ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY}
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
coerce_tests! :test_limit_with_offset_is_kept
|
|
110
|
+
def test_limit_with_offset_is_kept_coerced
|
|
111
|
+
queries = assert_sql { Account.limit(1).offset(1).count }
|
|
112
|
+
assert_equal 1, queries.length
|
|
113
|
+
queries.first.must_match %r{ORDER BY \[accounts\]\.\[id\] ASC OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
module ActiveRecord
|
|
122
|
+
class Migration
|
|
123
|
+
class ChangeSchemaTest < ActiveRecord::TestCase
|
|
124
|
+
|
|
125
|
+
# We test these.
|
|
126
|
+
coerce_tests! :test_create_table_with_bigint,
|
|
127
|
+
:test_create_table_with_defaults
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
module ActiveRecord
|
|
137
|
+
class Migration
|
|
138
|
+
class ColumnAttributesTest < ActiveRecord::TestCase
|
|
139
|
+
|
|
140
|
+
# We have a default 4000 varying character limit.
|
|
141
|
+
coerce_tests! :test_add_column_without_limit
|
|
142
|
+
def test_add_column_without_limit_coerced
|
|
143
|
+
add_column :test_models, :description, :string, limit: nil
|
|
144
|
+
TestModel.reset_column_information
|
|
145
|
+
TestModel.columns_hash["description"].limit.must_equal 4000
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
module ActiveRecord
|
|
156
|
+
class Migration
|
|
157
|
+
class ColumnsTest
|
|
158
|
+
|
|
159
|
+
# Our defaults are reall 70000 integers vs '70000' strings.
|
|
160
|
+
coerce_tests! :test_rename_column_preserves_default_value_not_null
|
|
161
|
+
def test_rename_column_preserves_default_value_not_null_coerced
|
|
162
|
+
add_column 'test_models', 'salary', :integer, :default => 70000
|
|
163
|
+
default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default
|
|
164
|
+
assert_equal 70000, default_before
|
|
165
|
+
rename_column "test_models", "salary", "annual_salary"
|
|
166
|
+
assert TestModel.column_names.include?("annual_salary")
|
|
167
|
+
default_after = connection.columns("test_models").find { |c| c.name == "annual_salary" }.default
|
|
168
|
+
assert_equal 70000, default_after
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Dropping the column removes the single index.
|
|
172
|
+
coerce_tests! :test_remove_column_with_multi_column_index
|
|
173
|
+
def test_remove_column_with_multi_column_index_coerced
|
|
174
|
+
add_column "test_models", :hat_size, :integer
|
|
175
|
+
add_column "test_models", :hat_style, :string, :limit => 100
|
|
176
|
+
add_index "test_models", ["hat_style", "hat_size"], :unique => true
|
|
177
|
+
assert_equal 1, connection.indexes('test_models').size
|
|
178
|
+
remove_column("test_models", "hat_size")
|
|
179
|
+
assert_equal [], connection.indexes('test_models').map(&:name)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class CoreTest < ActiveRecord::TestCase
|
|
190
|
+
|
|
191
|
+
# I think fixtures are useing the wrong time zone and the `:first`
|
|
192
|
+
# `topics`.`bonus_time` attribute of 2005-01-30t15:28:00.00+01:00 is
|
|
193
|
+
# getting local EST time for me and set to "09:28:00.0000000".
|
|
194
|
+
coerce_tests! :test_pretty_print_persisted
|
|
195
|
+
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
module ActiveRecord
|
|
202
|
+
module ConnectionAdapters
|
|
203
|
+
|
|
204
|
+
# Just like PostgreSQLAdapter does.
|
|
205
|
+
TypeLookupTest.coerce_all_tests! if defined?(TypeLookupTest)
|
|
206
|
+
|
|
207
|
+
# All sorts of errors due to how we test. Even setting ENV['RAILS_ENV'] to
|
|
208
|
+
# a value of 'default_env' will still show tests failing. Just ignoring all
|
|
209
|
+
# of them since we have no monkey in this circus.
|
|
210
|
+
MergeAndResolveDefaultUrlConfigTest.coerce_all_tests! if defined?(MergeAndResolveDefaultUrlConfigTest)
|
|
211
|
+
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class DefaultScopingTest < ActiveRecord::TestCase
|
|
219
|
+
|
|
220
|
+
# We are not doing order duplicate removal anymore.
|
|
221
|
+
coerce_tests! :test_order_in_default_scope_should_not_prevail
|
|
222
|
+
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
require 'models/post'
|
|
229
|
+
require 'models/subscriber'
|
|
230
|
+
class EachTest < ActiveRecord::TestCase
|
|
231
|
+
|
|
232
|
+
coerce_tests! :test_find_in_batches_should_quote_batch_order
|
|
233
|
+
def test_find_in_batches_should_quote_batch_order_coerced
|
|
234
|
+
c = Post.connection
|
|
235
|
+
assert_sql(/ORDER BY \[posts\]\.\[id\]/) do
|
|
236
|
+
Post.find_in_batches(:batch_size => 1) do |batch|
|
|
237
|
+
assert_kind_of Array, batch
|
|
238
|
+
assert_kind_of Post, batch.first
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
require 'models/owner'
|
|
249
|
+
class Owner < ActiveRecord::Base
|
|
250
|
+
scope :including_last_pet, -> {
|
|
251
|
+
select('owners.*, (select TOP (1) p.pet_id from pets p where p.owner_id = owners.owner_id order by p.name desc ) as last_pet_id').
|
|
252
|
+
includes(:last_pet)
|
|
253
|
+
}
|
|
254
|
+
end
|
|
255
|
+
class EagerAssociationTest < ActiveRecord::TestCase
|
|
256
|
+
|
|
257
|
+
# Use LEN() vs length() function.
|
|
258
|
+
coerce_tests! :test_count_with_include
|
|
259
|
+
def test_count_with_include_coerced
|
|
260
|
+
assert_equal 3, authors(:david).posts_with_comments.where("LEN(comments.body) > 15").references(:comments).count
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Use TOP (1) in scope vs limit 1.
|
|
264
|
+
coerce_tests! %r{including association based on sql condition and no database column}
|
|
265
|
+
it "including association based on sql condition and no database column coerced" do
|
|
266
|
+
assert_equal pets(:parrot), Owner.including_last_pet.first.last_pet
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
require 'models/topic'
|
|
275
|
+
class FinderTest < ActiveRecord::TestCase
|
|
276
|
+
|
|
277
|
+
coerce_tests! %r{doesn't have implicit ordering},
|
|
278
|
+
:test_find_doesnt_have_implicit_ordering # We have implicit ordering, via FETCH.
|
|
279
|
+
|
|
280
|
+
coerce_tests! :test_exists_does_not_select_columns_without_alias
|
|
281
|
+
def test_exists_does_not_select_columns_without_alias_coerced
|
|
282
|
+
assert_sql(/SELECT\s+1 AS one FROM \[topics\].*OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY/i) do
|
|
283
|
+
Topic.exists?
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
coerce_tests! :test_string_sanitation
|
|
288
|
+
def test_string_sanitation_coerced
|
|
289
|
+
assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
|
|
290
|
+
assert_equal "N'something; select table'", ActiveRecord::Base.sanitize("something; select table")
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
coerce_tests! :test_take_and_first_and_last_with_integer_should_use_sql_limit
|
|
294
|
+
def test_take_and_first_and_last_with_integer_should_use_sql_limit_coerced
|
|
295
|
+
assert_sql(/OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY/) { Topic.take(3).entries }
|
|
296
|
+
assert_sql(/OFFSET 0 ROWS FETCH NEXT 2 ROWS ONLY/) { Topic.first(2).entries }
|
|
297
|
+
assert_sql(/OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY/) { Topic.last(5).entries }
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class HasOneAssociationsTest < ActiveRecord::TestCase
|
|
306
|
+
|
|
307
|
+
# We use OFFSET/FETCH vs TOP. So we always have an order.
|
|
308
|
+
coerce_tests! :test_has_one_does_not_use_order_by
|
|
309
|
+
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
require 'models/company'
|
|
316
|
+
class InheritanceTest < ActiveRecord::TestCase
|
|
317
|
+
|
|
318
|
+
coerce_tests! :test_a_bad_type_column
|
|
319
|
+
def test_a_bad_type_column_coerced
|
|
320
|
+
Company.connection.with_identity_insert_enabled('companies') do
|
|
321
|
+
Company.connection.insert "INSERT INTO companies (id, #{QUOTED_TYPE}, name) VALUES(100, 'bad_class!', 'Not happening')"
|
|
322
|
+
end
|
|
323
|
+
assert_raise(ActiveRecord::SubclassNotFound) { Company.find(100) }
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
coerce_tests! :test_eager_load_belongs_to_primary_key_quoting
|
|
327
|
+
def test_eager_load_belongs_to_primary_key_quoting_coerced
|
|
328
|
+
con = Account.connection
|
|
329
|
+
assert_sql(/\[companies\]\.\[id\] IN \(1\)/) do
|
|
330
|
+
Account.all.merge!(:includes => :firm).find(1)
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class BigNumber < ActiveRecord::Base
|
|
340
|
+
attribute :value_of_e, Type::SQLServer::Integer.new
|
|
341
|
+
attribute :my_house_population, Type::SQLServer::Integer.new
|
|
342
|
+
end
|
|
343
|
+
class MigrationTest < ActiveRecord::TestCase
|
|
344
|
+
|
|
345
|
+
coerce_tests! :test_add_table_with_decimals
|
|
346
|
+
def test_add_table_with_decimals_coerced
|
|
347
|
+
Person.connection.drop_table :big_numbers rescue nil
|
|
348
|
+
assert !BigNumber.table_exists?
|
|
349
|
+
GiveMeBigNumbers.up
|
|
350
|
+
assert BigNumber.create(
|
|
351
|
+
:bank_balance => 1586.43,
|
|
352
|
+
:big_bank_balance => BigDecimal("1000234000567.95"),
|
|
353
|
+
:world_population => 6000000000,
|
|
354
|
+
:my_house_population => 3,
|
|
355
|
+
:value_of_e => BigDecimal("2.7182818284590452353602875")
|
|
356
|
+
)
|
|
357
|
+
b = BigNumber.first
|
|
358
|
+
assert_not_nil b
|
|
359
|
+
assert_not_nil b.bank_balance
|
|
360
|
+
assert_not_nil b.big_bank_balance
|
|
361
|
+
assert_not_nil b.world_population
|
|
362
|
+
assert_not_nil b.my_house_population
|
|
363
|
+
assert_not_nil b.value_of_e
|
|
364
|
+
# SQLServer: We rock and cast during assignment.
|
|
365
|
+
assert_kind_of BigDecimal, b.world_population
|
|
366
|
+
assert_equal BigDecimal('6000000000'), b.world_population
|
|
367
|
+
# TODO: Our trust the DB policy breaks this expectation. Review SQLServer::Type::Castable module.
|
|
368
|
+
skip
|
|
369
|
+
assert_kind_of Fixnum, b.my_house_population
|
|
370
|
+
assert_equal 3, b.my_house_population
|
|
371
|
+
assert_kind_of BigDecimal, b.bank_balance
|
|
372
|
+
assert_equal BigDecimal("1586.43"), b.bank_balance
|
|
373
|
+
assert_kind_of BigDecimal, b.big_bank_balance
|
|
374
|
+
assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance
|
|
375
|
+
assert_kind_of BigDecimal, b.value_of_e
|
|
376
|
+
assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e
|
|
377
|
+
GiveMeBigNumbers.down
|
|
378
|
+
assert_raise(ActiveRecord::StatementInvalid) { BigNumber.first }
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
require 'models/developer'
|
|
387
|
+
require 'models/computer'
|
|
388
|
+
class NestedRelationScopingTest < ActiveRecord::TestCase
|
|
389
|
+
|
|
390
|
+
coerce_tests! :test_merge_options
|
|
391
|
+
def test_merge_options_coerced
|
|
392
|
+
Developer.where('salary = 80000').scoping do
|
|
393
|
+
Developer.limit(10).scoping do
|
|
394
|
+
devs = Developer.all
|
|
395
|
+
sql = devs.to_sql
|
|
396
|
+
assert_match '(salary = 80000)', sql
|
|
397
|
+
assert_match 'FETCH NEXT 10 ROWS ONLY', sql
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
require 'models/topic'
|
|
408
|
+
class PersistenceTest < ActiveRecord::TestCase
|
|
409
|
+
|
|
410
|
+
# We can not UPDATE identity columns.
|
|
411
|
+
coerce_tests! :test_update_columns_changing_id
|
|
412
|
+
|
|
413
|
+
# Previous test required updating a identity column.
|
|
414
|
+
coerce_tests! :test_update_all_doesnt_ignore_order
|
|
415
|
+
def test_update_all_doesnt_ignore_order_coerced
|
|
416
|
+
david, mary = authors(:david), authors(:mary)
|
|
417
|
+
david.id.must_equal 1
|
|
418
|
+
mary.id.must_equal 2
|
|
419
|
+
david.name.wont_equal mary.name
|
|
420
|
+
assert_sql(/UPDATE.*\(SELECT \[authors\].\[id\] FROM \[authors\].*ORDER BY \[authors\].\[id\]/i) do
|
|
421
|
+
Author.where('[id] > 1').order(:id).update_all(name: 'Test')
|
|
422
|
+
end
|
|
423
|
+
david.reload.name.must_equal 'David'
|
|
424
|
+
mary.reload.name.must_equal 'Test'
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# We can not UPDATE identity columns.
|
|
428
|
+
coerce_tests! :test_update_attributes
|
|
429
|
+
def test_update_attributes_coerced
|
|
430
|
+
topic = Topic.find(1)
|
|
431
|
+
assert !topic.approved?
|
|
432
|
+
assert_equal "The First Topic", topic.title
|
|
433
|
+
topic.update_attributes("approved" => true, "title" => "The First Topic Updated")
|
|
434
|
+
topic.reload
|
|
435
|
+
assert topic.approved?
|
|
436
|
+
assert_equal "The First Topic Updated", topic.title
|
|
437
|
+
topic.update_attributes(approved: false, title: "The First Topic")
|
|
438
|
+
topic.reload
|
|
439
|
+
assert !topic.approved?
|
|
440
|
+
assert_equal "The First Topic", topic.title
|
|
441
|
+
# SQLServer: Here is where it breaks down. No exceptions.
|
|
442
|
+
# assert_raise(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid) do
|
|
443
|
+
# topic.update_attributes(id: 3, title: "Hm is it possible?")
|
|
444
|
+
# end
|
|
445
|
+
# assert_not_equal "Hm is it possible?", Topic.find(3).title
|
|
446
|
+
# topic.update_attributes(id: 1234)
|
|
447
|
+
# assert_nothing_raised { topic.reload }
|
|
448
|
+
# assert_equal topic.title, Topic.find(1234).title
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
require 'models/topic'
|
|
457
|
+
module ActiveRecord
|
|
458
|
+
class PredicateBuilderTest < ActiveRecord::TestCase
|
|
459
|
+
|
|
460
|
+
coerce_tests! :test_registering_new_handlers
|
|
461
|
+
def test_registering_new_handlers_coerced
|
|
462
|
+
PredicateBuilder.register_handler(Regexp, proc do |column, value|
|
|
463
|
+
Arel::Nodes::InfixOperation.new('~', column, Arel.sql(value.source))
|
|
464
|
+
end)
|
|
465
|
+
assert_match %r{\[topics\]\.\[title\] ~ rails}i, Topic.where(title: /rails/).to_sql
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
require 'models/task'
|
|
475
|
+
class QueryCacheTest < ActiveRecord::TestCase
|
|
476
|
+
|
|
477
|
+
coerce_tests! :test_cache_does_not_wrap_string_results_in_arrays
|
|
478
|
+
def test_cache_does_not_wrap_string_results_in_arrays_coerced
|
|
479
|
+
Task.cache do
|
|
480
|
+
assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
require 'models/post'
|
|
490
|
+
class RelationTest < ActiveRecord::TestCase
|
|
491
|
+
|
|
492
|
+
# We have implicit ordering, via FETCH.
|
|
493
|
+
coerce_tests! %r{doesn't have implicit ordering}
|
|
494
|
+
|
|
495
|
+
# We are not doing order duplicate removal anymore.
|
|
496
|
+
coerce_tests! :test_order_using_scoping
|
|
497
|
+
|
|
498
|
+
# Account for our `EXEC sp_executesql...` statements.
|
|
499
|
+
coerce_tests! :test_to_sql_on_eager_join
|
|
500
|
+
def test_to_sql_on_eager_join_coerced
|
|
501
|
+
expected = assert_sql { Post.eager_load(:last_comment).order('comments.id DESC').to_a }.first
|
|
502
|
+
actual = Post.eager_load(:last_comment).order('comments.id DESC').to_sql
|
|
503
|
+
actual = "EXEC sp_executesql N'#{ActiveRecord::ConnectionAdapters::SQLServer::Utils.quote_string(actual)}'"
|
|
504
|
+
assert_equal expected, actual
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
# We are not doing order duplicate removal anymore.
|
|
508
|
+
coerce_tests! :test_default_scope_order_with_scope_order
|
|
509
|
+
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
require 'models/post'
|
|
516
|
+
class SanitizeTest < ActiveRecord::TestCase
|
|
517
|
+
|
|
518
|
+
coerce_tests! :test_sanitize_sql_like_example_use_case
|
|
519
|
+
def test_sanitize_sql_like_example_use_case_coerced
|
|
520
|
+
searchable_post = Class.new(Post) do
|
|
521
|
+
def self.search(term)
|
|
522
|
+
where("title LIKE ?", sanitize_sql_like(term, '!'))
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
assert_sql(/\(title LIKE N''20!% !_reduction!_!!''\)/) do
|
|
526
|
+
searchable_post.search("20% _reduction_!").to_a
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
class SchemaDumperTest < ActiveRecord::TestCase
|
|
536
|
+
|
|
537
|
+
# We have precision to 38.
|
|
538
|
+
coerce_tests! :test_schema_dump_keeps_large_precision_integer_columns_as_decimal
|
|
539
|
+
def test_schema_dump_keeps_large_precision_integer_columns_as_decimal_coerced
|
|
540
|
+
output = standard_dump
|
|
541
|
+
assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 38}, output
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# This accidently returns the wrong number because of our tables too.
|
|
545
|
+
coerce_tests! :test_types_line_up
|
|
546
|
+
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
|
|
553
|
+
|
|
554
|
+
# We trust Rails on this since we do not want to install mysql.
|
|
555
|
+
coerce_tests! %r{inspect on Model class does not raise}
|
|
556
|
+
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
require 'models/topic'
|
|
563
|
+
class TransactionTest < ActiveRecord::TestCase
|
|
564
|
+
|
|
565
|
+
coerce_tests! :test_releasing_named_savepoints
|
|
566
|
+
def test_releasing_named_savepoints_coerced
|
|
567
|
+
Topic.transaction do
|
|
568
|
+
Topic.connection.create_savepoint("another")
|
|
569
|
+
Topic.connection.release_savepoint("another")
|
|
570
|
+
# We do not have a notion of releasing, so this does nothing vs raise an error.
|
|
571
|
+
Topic.connection.release_savepoint("another")
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
require 'models/tag'
|
|
581
|
+
class TransactionIsolationTest < ActiveRecord::TestCase
|
|
582
|
+
|
|
583
|
+
# SQL Server will lock the table for counts even when both
|
|
584
|
+
# connections are `READ COMMITTED`. So we bypass with `READPAST`.
|
|
585
|
+
coerce_tests! %r{read committed}
|
|
586
|
+
test "read committed coerced" do
|
|
587
|
+
Tag.transaction(isolation: :read_committed) do
|
|
588
|
+
assert_equal 0, Tag.count
|
|
589
|
+
Tag2.transaction do
|
|
590
|
+
Tag2.create
|
|
591
|
+
assert_equal 0, Tag.lock('WITH(READPAST)').count
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
assert_equal 1, Tag.count
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# I really need some help understanding this one.
|
|
598
|
+
coerce_tests! %r{repeatable read}
|
|
599
|
+
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
require 'models/post'
|
|
604
|
+
module ActiveRecord
|
|
605
|
+
class WhereChainTest < ActiveRecord::TestCase
|
|
606
|
+
|
|
607
|
+
coerce_tests! :test_not_eq_with_array_parameter
|
|
608
|
+
def test_not_eq_with_array_parameter_coerced
|
|
609
|
+
expected = Arel::Nodes::Not.new("title = N'hello'")
|
|
610
|
+
relation = Post.where.not(['title = ?', 'hello'])
|
|
611
|
+
assert_equal([expected], relation.where_values)
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
class ViewWithPrimaryKeyTest < ActiveRecord::TestCase
|
|
621
|
+
|
|
622
|
+
# We do better than ActiveRecord and find the views PK.
|
|
623
|
+
coerce_tests! :test_does_not_assume_id_column_as_primary_key
|
|
624
|
+
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
require 'models/author'
|
|
631
|
+
class YamlSerializationTest < ActiveRecord::TestCase
|
|
632
|
+
|
|
633
|
+
coerce_tests! :test_types_of_virtual_columns_are_not_changed_on_round_trip
|
|
634
|
+
def test_types_of_virtual_columns_are_not_changed_on_round_trip_coerced
|
|
635
|
+
author = Author.select('authors.*, 5 as posts_count').first
|
|
636
|
+
dumped = YAML.load(YAML.dump(author))
|
|
637
|
+
assert_equal 5, author.posts_count
|
|
638
|
+
assert_equal 5, dumped.posts_count
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
end
|
|
642
|
+
|