activerecord-sqlserver-adapter 2.3.24 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. data/CHANGELOG +5 -108
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +33 -61
  4. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +57 -0
  5. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +57 -0
  6. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
  7. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +336 -0
  8. data/lib/active_record/connection_adapters/sqlserver/errors.rb +33 -0
  9. data/lib/active_record/connection_adapters/sqlserver/query_cache.rb +17 -0
  10. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +61 -0
  11. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +373 -0
  12. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +131 -1121
  13. data/lib/arel/engines/sql/compilers/sqlserver_compiler.rb +267 -0
  14. metadata +26 -76
  15. data/RUNNING_UNIT_TESTS +0 -31
  16. data/Rakefile +0 -60
  17. data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/active_record.rb +0 -151
  18. data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/odbc.rb +0 -40
  19. data/test/cases/aaaa_create_tables_test_sqlserver.rb +0 -19
  20. data/test/cases/adapter_test_sqlserver.rb +0 -755
  21. data/test/cases/attribute_methods_test_sqlserver.rb +0 -33
  22. data/test/cases/basics_test_sqlserver.rb +0 -86
  23. data/test/cases/calculations_test_sqlserver.rb +0 -20
  24. data/test/cases/column_test_sqlserver.rb +0 -354
  25. data/test/cases/connection_test_sqlserver.rb +0 -148
  26. data/test/cases/eager_association_test_sqlserver.rb +0 -42
  27. data/test/cases/execute_procedure_test_sqlserver.rb +0 -35
  28. data/test/cases/inheritance_test_sqlserver.rb +0 -28
  29. data/test/cases/method_scoping_test_sqlserver.rb +0 -28
  30. data/test/cases/migration_test_sqlserver.rb +0 -108
  31. data/test/cases/named_scope_test_sqlserver.rb +0 -21
  32. data/test/cases/offset_and_limit_test_sqlserver.rb +0 -108
  33. data/test/cases/pessimistic_locking_test_sqlserver.rb +0 -125
  34. data/test/cases/query_cache_test_sqlserver.rb +0 -24
  35. data/test/cases/schema_dumper_test_sqlserver.rb +0 -72
  36. data/test/cases/specific_schema_test_sqlserver.rb +0 -154
  37. data/test/cases/sqlserver_helper.rb +0 -140
  38. data/test/cases/table_name_test_sqlserver.rb +0 -38
  39. data/test/cases/transaction_test_sqlserver.rb +0 -93
  40. data/test/cases/unicode_test_sqlserver.rb +0 -54
  41. data/test/cases/validations_test_sqlserver.rb +0 -18
  42. data/test/connections/native_sqlserver/connection.rb +0 -26
  43. data/test/connections/native_sqlserver_odbc/connection.rb +0 -28
  44. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +0 -11
  45. data/test/schema/sqlserver_specific_schema.rb +0 -113
@@ -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