activerecord-sqlserver-adapter 2.3.24 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/CHANGELOG +5 -108
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +33 -61
  4. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +57 -0
  5. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +57 -0
  6. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
  7. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +336 -0
  8. data/lib/active_record/connection_adapters/sqlserver/errors.rb +33 -0
  9. data/lib/active_record/connection_adapters/sqlserver/query_cache.rb +17 -0
  10. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +61 -0
  11. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +373 -0
  12. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +131 -1121
  13. data/lib/arel/engines/sql/compilers/sqlserver_compiler.rb +267 -0
  14. metadata +26 -76
  15. data/RUNNING_UNIT_TESTS +0 -31
  16. data/Rakefile +0 -60
  17. data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/active_record.rb +0 -151
  18. data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/odbc.rb +0 -40
  19. data/test/cases/aaaa_create_tables_test_sqlserver.rb +0 -19
  20. data/test/cases/adapter_test_sqlserver.rb +0 -755
  21. data/test/cases/attribute_methods_test_sqlserver.rb +0 -33
  22. data/test/cases/basics_test_sqlserver.rb +0 -86
  23. data/test/cases/calculations_test_sqlserver.rb +0 -20
  24. data/test/cases/column_test_sqlserver.rb +0 -354
  25. data/test/cases/connection_test_sqlserver.rb +0 -148
  26. data/test/cases/eager_association_test_sqlserver.rb +0 -42
  27. data/test/cases/execute_procedure_test_sqlserver.rb +0 -35
  28. data/test/cases/inheritance_test_sqlserver.rb +0 -28
  29. data/test/cases/method_scoping_test_sqlserver.rb +0 -28
  30. data/test/cases/migration_test_sqlserver.rb +0 -108
  31. data/test/cases/named_scope_test_sqlserver.rb +0 -21
  32. data/test/cases/offset_and_limit_test_sqlserver.rb +0 -108
  33. data/test/cases/pessimistic_locking_test_sqlserver.rb +0 -125
  34. data/test/cases/query_cache_test_sqlserver.rb +0 -24
  35. data/test/cases/schema_dumper_test_sqlserver.rb +0 -72
  36. data/test/cases/specific_schema_test_sqlserver.rb +0 -154
  37. data/test/cases/sqlserver_helper.rb +0 -140
  38. data/test/cases/table_name_test_sqlserver.rb +0 -38
  39. data/test/cases/transaction_test_sqlserver.rb +0 -93
  40. data/test/cases/unicode_test_sqlserver.rb +0 -54
  41. data/test/cases/validations_test_sqlserver.rb +0 -18
  42. data/test/connections/native_sqlserver/connection.rb +0 -26
  43. data/test/connections/native_sqlserver_odbc/connection.rb +0 -28
  44. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +0 -11
  45. data/test/schema/sqlserver_specific_schema.rb +0 -113
@@ -0,0 +1,49 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ module DatabaseLimits
5
+
6
+ def table_alias_length
7
+ 128
8
+ end
9
+
10
+ def column_name_length
11
+ 128
12
+ end
13
+
14
+ def table_name_length
15
+ 128
16
+ end
17
+
18
+ def index_name_length
19
+ 128
20
+ end
21
+
22
+ def columns_per_table
23
+ 1024
24
+ end
25
+
26
+ def indexes_per_table
27
+ 999
28
+ end
29
+
30
+ def columns_per_multicolumn_index
31
+ 16
32
+ end
33
+
34
+ def in_clause_length
35
+ 65536
36
+ end
37
+
38
+ def sql_query_length
39
+ 65536 * 4096
40
+ end
41
+
42
+ def joins_per_query
43
+ 256
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,336 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ module DatabaseStatements
5
+
6
+ def select_one(sql, name = nil)
7
+ result = raw_select sql, name, :fetch => :one
8
+ (result && result.first.present?) ? result.first : nil
9
+ end
10
+
11
+ def select_rows(sql, name = nil)
12
+ raw_select sql, name, :fetch => :rows
13
+ end
14
+
15
+ def execute(sql, name = nil, skip_logging = false)
16
+ if table_name = query_requires_identity_insert?(sql)
17
+ with_identity_insert_enabled(table_name) { do_execute(sql,name) }
18
+ else
19
+ do_execute(sql,name)
20
+ end
21
+ end
22
+
23
+ def outside_transaction?
24
+ info_schema_query { select_value("SELECT @@TRANCOUNT") == 0 }
25
+ end
26
+
27
+ def begin_db_transaction
28
+ do_execute "BEGIN TRANSACTION"
29
+ end
30
+
31
+ def commit_db_transaction
32
+ do_execute "COMMIT TRANSACTION"
33
+ end
34
+
35
+ def rollback_db_transaction
36
+ do_execute "ROLLBACK TRANSACTION" rescue nil
37
+ end
38
+
39
+ def create_savepoint
40
+ do_execute "SAVE TRANSACTION #{current_savepoint_name}"
41
+ end
42
+
43
+ def release_savepoint
44
+ end
45
+
46
+ def rollback_to_savepoint
47
+ do_execute "ROLLBACK TRANSACTION #{current_savepoint_name}"
48
+ end
49
+
50
+ def add_limit_offset!(sql, options)
51
+ raise NotImplementedError, 'This has been moved to the SQLServerCompiler in Arel.'
52
+ end
53
+
54
+ def empty_insert_statement_value
55
+ "DEFAULT VALUES"
56
+ end
57
+
58
+ def case_sensitive_equality_operator
59
+ cs_equality_operator
60
+ end
61
+
62
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
63
+ match_data = where_sql.match(/^(.*?[\]\) ])WHERE[\[\( ]/)
64
+ limit = match_data[1]
65
+ where_sql.sub!(limit,'')
66
+ "WHERE #{quoted_primary_key} IN (SELECT #{limit} #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
67
+ end
68
+
69
+ # === SQLServer Specific ======================================== #
70
+
71
+ def execute_procedure(proc_name, *variables)
72
+ vars = variables.map{ |v| quote(v) }.join(', ')
73
+ sql = "EXEC #{proc_name} #{vars}".strip
74
+ results = []
75
+ log(sql,'Execute Procedure') do
76
+ raw_connection_run(sql) do |handle|
77
+ get_rows = lambda {
78
+ rows = handle_to_names_and_values handle, :fetch => :all
79
+ rows.each_with_index { |r,i| rows[i] = r.with_indifferent_access }
80
+ results << rows
81
+ }
82
+ get_rows.call
83
+ while handle_more_results?(handle)
84
+ get_rows.call
85
+ end
86
+ end
87
+ end
88
+ results.many? ? results : results.first
89
+ end
90
+
91
+ def use_database(database=nil)
92
+ database ||= @connection_options[:database]
93
+ do_execute "USE #{quote_table_name(database)}" unless database.blank?
94
+ end
95
+
96
+ def user_options
97
+ info_schema_query do
98
+ select_rows("dbcc useroptions").inject(HashWithIndifferentAccess.new) do |values,row|
99
+ set_option = row[0].gsub(/\s+/,'_')
100
+ user_value = row[1]
101
+ values[set_option] = user_value
102
+ values
103
+ end
104
+ end
105
+ end
106
+
107
+ def run_with_isolation_level(isolation_level)
108
+ raise ArgumentError, "Invalid isolation level, #{isolation_level}. Supported levels include #{valid_isolation_levels.to_sentence}." if !valid_isolation_levels.include?(isolation_level.upcase)
109
+ initial_isolation_level = user_options[:isolation_level] || "READ COMMITTED"
110
+ do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
111
+ begin
112
+ yield
113
+ ensure
114
+ do_execute "SET TRANSACTION ISOLATION LEVEL #{initial_isolation_level}"
115
+ end if block_given?
116
+ end
117
+
118
+ def newid_function
119
+ select_value "SELECT NEWID()"
120
+ end
121
+
122
+ def newsequentialid_function
123
+ select_value "SELECT NEWSEQUENTIALID()"
124
+ end
125
+
126
+ # === SQLServer Specific (Rake/Test Helpers) ==================== #
127
+
128
+ def recreate_database
129
+ remove_database_connections_and_rollback do
130
+ do_execute "EXEC sp_MSforeachtable 'DROP TABLE ?'"
131
+ end
132
+ end
133
+
134
+ def recreate_database!(database=nil)
135
+ current_db = current_database
136
+ database ||= current_db
137
+ this_db = database.to_s == current_db
138
+ do_execute 'USE master' if this_db
139
+ drop_database(database)
140
+ create_database(database)
141
+ ensure
142
+ use_database(current_db) if this_db
143
+ end
144
+
145
+ def drop_database(database)
146
+ retry_count = 0
147
+ max_retries = 1
148
+ begin
149
+ do_execute "DROP DATABASE #{quote_table_name(database)}"
150
+ rescue ActiveRecord::StatementInvalid => err
151
+ if err.message =~ /because it is currently in use/i
152
+ raise if retry_count >= max_retries
153
+ retry_count += 1
154
+ remove_database_connections_and_rollback(database)
155
+ retry
156
+ else
157
+ raise
158
+ end
159
+ end
160
+ end
161
+
162
+ def create_database(database)
163
+ do_execute "CREATE DATABASE #{quote_table_name(database)}"
164
+ end
165
+
166
+ def current_database
167
+ select_value 'SELECT DB_NAME()'
168
+ end
169
+
170
+ def charset
171
+ select_value "SELECT SERVERPROPERTY('SqlCharSetName')"
172
+ end
173
+
174
+
175
+ protected
176
+
177
+ def select(sql, name = nil)
178
+ raw_select sql, name, :fetch => :all
179
+ end
180
+
181
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
182
+ super || select_value("SELECT SCOPE_IDENTITY() AS Ident")
183
+ end
184
+
185
+ def update_sql(sql, name = nil)
186
+ execute(sql, name)
187
+ select_value('SELECT @@ROWCOUNT AS AffectedRows')
188
+ end
189
+
190
+ # === SQLServer Specific ======================================== #
191
+
192
+ def valid_isolation_levels
193
+ ["READ COMMITTED", "READ UNCOMMITTED", "REPEATABLE READ", "SERIALIZABLE", "SNAPSHOT"]
194
+ end
195
+
196
+ # === SQLServer Specific (Executing) ============================ #
197
+
198
+ def do_execute(sql, name = nil)
199
+ name ||= 'EXECUTE'
200
+ log(sql, name) do
201
+ with_auto_reconnect { raw_connection_do(sql) }
202
+ end
203
+ end
204
+
205
+ def raw_connection_do(sql)
206
+ case connection_mode
207
+ when :odbc
208
+ raw_connection.do(sql)
209
+ else :adonet
210
+ raw_connection.create_command.tap{ |cmd| cmd.command_text = sql }.execute_non_query
211
+ end
212
+ end
213
+
214
+ # === SQLServer Specific (Selecting) ============================ #
215
+
216
+ def raw_select(sql, name=nil, options={})
217
+ log(sql,name) do
218
+ begin
219
+ handle = raw_connection_run(sql)
220
+ handle_to_names_and_values(handle, options)
221
+ ensure
222
+ finish_statement_handle(handle)
223
+ end
224
+ end
225
+ end
226
+
227
+ def raw_connection_run(sql)
228
+ with_auto_reconnect do
229
+ case connection_mode
230
+ when :odbc
231
+ block_given? ? raw_connection.run_block(sql) { |handle| yield(handle) } : raw_connection.run(sql)
232
+ else :adonet
233
+ raw_connection.create_command.tap{ |cmd| cmd.command_text = sql }.execute_reader
234
+ end
235
+ end
236
+ end
237
+
238
+ def handle_more_results?(handle)
239
+ case connection_mode
240
+ when :odbc
241
+ handle.more_results
242
+ when :adonet
243
+ handle.next_result
244
+ end
245
+ end
246
+
247
+ def handle_to_names_and_values(handle, options={})
248
+ case connection_mode
249
+ when :odbc
250
+ handle_to_names_and_values_odbc(handle, options)
251
+ when :adonet
252
+ handle_to_names_and_values_adonet(handle, options)
253
+ end
254
+ end
255
+
256
+ def handle_to_names_and_values_odbc(handle, options={})
257
+ case options[:fetch]
258
+ when :all, :one
259
+ rows = if options[:fetch] == :all
260
+ handle.fetch_all || []
261
+ else
262
+ row = handle.fetch
263
+ row ? [row] : [[]]
264
+ end
265
+ names = handle.columns(true).map{ |c| c.name }
266
+ names_and_values = []
267
+ rows.each do |row|
268
+ h = {}
269
+ i = 0
270
+ while i < row.size
271
+ v = row[i]
272
+ h[names[i]] = v.respond_to?(:to_sqlserver_string) ? v.to_sqlserver_string : v
273
+ i += 1
274
+ end
275
+ names_and_values << h
276
+ end
277
+ names_and_values
278
+ when :rows
279
+ rows = handle.fetch_all || []
280
+ rows.each do |row|
281
+ i = 0
282
+ while i < row.size
283
+ v = row[i]
284
+ row[i] = v.to_sqlserver_string if v.respond_to?(:to_sqlserver_string)
285
+ i += 1
286
+ end
287
+ end
288
+ rows
289
+ end
290
+ end
291
+
292
+ def handle_to_names_and_values_adonet(handle, options={})
293
+ if handle.has_rows
294
+ fields = []
295
+ rows = []
296
+ fields_named = false
297
+ while handle.read
298
+ row = []
299
+ handle.visible_field_count.times do |row_index|
300
+ value = handle.get_value(row_index)
301
+ value = if value.is_a? System::String
302
+ value.to_s
303
+ elsif value.is_a? System::DBNull
304
+ nil
305
+ elsif value.is_a? System::DateTime
306
+ value.to_string("yyyy-MM-dd HH:MM:ss.fff").to_s
307
+ else
308
+ value
309
+ end
310
+ row << value
311
+ fields << handle.get_name(row_index).to_s unless fields_named
312
+ end
313
+ rows << row
314
+ fields_named = true
315
+ end
316
+ else
317
+ fields, rows = [], []
318
+ end
319
+ [fields,rows]
320
+ end
321
+
322
+ def finish_statement_handle(handle)
323
+ case connection_mode
324
+ when :odbc
325
+ handle.drop if handle && handle.respond_to?(:drop) && !handle.finished?
326
+ when :adonet
327
+ handle.close if handle && handle.respond_to?(:close) && !handle.is_closed
328
+ handle.dispose if handle && handle.respond_to?(:dispose)
329
+ end
330
+ handle
331
+ end
332
+
333
+ end
334
+ end
335
+ end
336
+ end
@@ -0,0 +1,33 @@
1
+ module ActiveRecord
2
+
3
+ class LostConnection < WrappedDatabaseException
4
+ end
5
+
6
+ module ConnectionAdapters
7
+ module Sqlserver
8
+ module Errors
9
+
10
+ LOST_CONNECTION_EXCEPTIONS = {
11
+ :odbc => ['ODBC::Error','ODBC_UTF8::Error','ODBC_NONE::Error'],
12
+ :adonet => ['TypeError','System::Data::SqlClient::SqlException']
13
+ }.freeze
14
+
15
+ LOST_CONNECTION_MESSAGES = {
16
+ :odbc => [/link failure/, /server failed/, /connection was already closed/, /invalid handle/i],
17
+ :adonet => [/current state is closed/, /network-related/]
18
+ }.freeze
19
+
20
+
21
+ def lost_connection_exceptions
22
+ exceptions = LOST_CONNECTION_EXCEPTIONS[connection_mode]
23
+ @lost_connection_exceptions ||= exceptions ? exceptions.map{ |e| e.constantize rescue nil }.compact : []
24
+ end
25
+
26
+ def lost_connection_messages
27
+ LOST_CONNECTION_MESSAGES[connection_mode]
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ module QueryCache
5
+
6
+ def select_one(*args)
7
+ if @query_cache_enabled
8
+ cache_sql(args.first) { super }
9
+ else
10
+ super
11
+ end
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,61 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ module Quoting
5
+
6
+ def quote(value, column = nil)
7
+ case value
8
+ when String, ActiveSupport::Multibyte::Chars
9
+ if column && column.type == :binary
10
+ column.class.string_to_binary(value)
11
+ elsif quote_value_as_utf8?(value) || column && column.respond_to?(:is_utf8?) && column.is_utf8?
12
+ quoted_utf8_value(value)
13
+ else
14
+ super
15
+ end
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+ def quote_string(string)
22
+ string.to_s.gsub(/\'/, "''")
23
+ end
24
+
25
+ def quote_column_name(column_name)
26
+ column_name.to_s.split('.').map{ |name| name =~ /^\[.*\]$/ ? name : "[#{name}]" }.join('.')
27
+ end
28
+
29
+ def quote_table_name(table_name)
30
+ return table_name if table_name =~ /^\[.*\]$/
31
+ quote_column_name(table_name)
32
+ end
33
+
34
+ def quoted_true
35
+ '1'
36
+ end
37
+
38
+ def quoted_false
39
+ '0'
40
+ end
41
+
42
+ def quoted_date(value)
43
+ if value.acts_like?(:time) && value.respond_to?(:usec)
44
+ "#{super}.#{sprintf("%03d",value.usec/1000)}"
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ def quoted_utf8_value(value)
51
+ "N'#{quote_string(value)}'"
52
+ end
53
+
54
+ def quote_value_as_utf8?(value)
55
+ value.is_utf8? || enable_default_unicode_types
56
+ end
57
+
58
+ end
59
+ end
60
+ end
61
+ end