@@ -13,39 +13,58 @@ def write_query?(sql) # :nodoc:
13
13
!READ_QUERY . match? ( sql . b )
14
14
end
15
15
16
- def execute ( sql , name = nil )
16
+ def raw_execute ( sql , name , async : false , allow_retry : false , materialize_transactions : true )
17
+ result = nil
17
18
sql = transform_query ( sql )
18
- if preventing_writes? && write_query? ( sql )
19
- raise ActiveRecord ::ReadOnlyError , "Write query attempted while in readonly mode: #{ sql } "
20
- end
21
19
22
- materialize_transactions
23
- mark_transaction_written_if_write ( sql )
20
+ log ( sql , name , async : async ) do
21
+ with_raw_connection ( allow_retry : allow_retry , materialize_transactions : materialize_transactions ) do |conn |
22
+ handle = if id_insert_table_name = query_requires_identity_insert? ( sql )
23
+ with_identity_insert_enabled ( id_insert_table_name ) { _execute ( sql , conn ) }
24
+ else
25
+ _execute ( sql , conn )
26
+ end
24
27
25
- if id_insert_table_name = query_requires_identity_insert? ( sql )
26
- with_identity_insert_enabled ( id_insert_table_name ) { do_execute ( sql , name ) }
27
- else
28
- do_execute ( sql , name )
28
+ result = handle . do
29
+ end
29
30
end
31
+
32
+ result
30
33
end
31
34
32
35
def internal_exec_query ( sql , name = "SQL" , binds = [ ] , prepare : false , async : false )
36
+ result = nil
33
37
sql = transform_query ( sql )
34
- if preventing_writes? && write_query? ( sql )
35
- raise ActiveRecord ::ReadOnlyError , "Write query attempted while in readonly mode: #{ sql } "
36
- end
37
38
38
- materialize_transactions
39
+ check_if_write_query ( sql )
39
40
mark_transaction_written_if_write ( sql )
40
41
41
- sp_executesql ( sql , name , binds , prepare : prepare , async : async )
42
+ unless without_prepared_statement? ( binds )
43
+ types , params = sp_executesql_types_and_parameters ( binds )
44
+ sql = sp_executesql_sql ( sql , types , params , name )
45
+ end
46
+
47
+ log ( sql , name , binds , async : async ) do
48
+ with_raw_connection do |conn |
49
+ begin
50
+ options = { ar_result : true }
51
+
52
+ handle = _execute ( sql , conn )
53
+ result = handle_to_names_and_values ( handle , options )
54
+ ensure
55
+ finish_statement_handle ( handle )
56
+ end
57
+ end
58
+ end
59
+
60
+ result
42
61
end
43
62
44
63
def exec_insert ( sql , name = nil , binds = [ ] , pk = nil , _sequence_name = nil , returning : nil )
45
64
if id_insert_table_name = exec_insert_requires_identity? ( sql , pk , binds )
46
- with_identity_insert_enabled ( id_insert_table_name ) { super ( sql , name , binds , pk , returning ) }
65
+ with_identity_insert_enabled ( id_insert_table_name ) { super }
47
66
else
48
- super ( sql , name , binds , pk , returning )
67
+ super
49
68
end
50
69
end
51
70
@@ -60,7 +79,7 @@ def exec_update(sql, name, binds)
60
79
end
61
80
62
81
def begin_db_transaction
63
- do_execute "BEGIN TRANSACTION" , "TRANSACTION"
82
+ execute "BEGIN TRANSACTION" , "TRANSACTION"
64
83
end
65
84
66
85
def transaction_isolation_levels
@@ -73,25 +92,25 @@ def begin_isolated_db_transaction(isolation)
73
92
end
74
93
75
94
def set_transaction_isolation_level ( isolation_level )
76
- do_execute "SET TRANSACTION ISOLATION LEVEL #{ isolation_level } " , "TRANSACTION"
95
+ execute "SET TRANSACTION ISOLATION LEVEL #{ isolation_level } " , "TRANSACTION"
77
96
end
78
97
79
98
def commit_db_transaction
80
- do_execute "COMMIT TRANSACTION" , "TRANSACTION"
99
+ execute "COMMIT TRANSACTION" , "TRANSACTION"
81
100
end
82
101
83
102
def exec_rollback_db_transaction
84
- do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION" , "TRANSACTION"
103
+ execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION" , "TRANSACTION"
85
104
end
86
105
87
106
include Savepoints
88
107
89
108
def create_savepoint ( name = current_savepoint_name )
90
- do_execute "SAVE TRANSACTION #{ name } " , "TRANSACTION"
109
+ execute "SAVE TRANSACTION #{ name } " , "TRANSACTION"
91
110
end
92
111
93
112
def exec_rollback_to_savepoint ( name = current_savepoint_name )
94
- do_execute "ROLLBACK TRANSACTION #{ name } " , "TRANSACTION"
113
+ execute "ROLLBACK TRANSACTION #{ name } " , "TRANSACTION"
95
114
end
96
115
97
116
def release_savepoint ( name = current_savepoint_name )
@@ -164,27 +183,27 @@ def build_insert_sql(insert) # :nodoc:
164
183
# === SQLServer Specific ======================================== #
165
184
166
185
def execute_procedure ( proc_name , *variables )
167
- materialize_transactions
168
-
169
186
vars = if variables . any? && variables . first . is_a? ( Hash )
170
187
variables . first . map { |k , v | "@#{ k } = #{ quote ( v ) } " }
171
188
else
172
189
variables . map { |v | quote ( v ) }
173
190
end . join ( ", " )
174
191
sql = "EXEC #{ proc_name } #{ vars } " . strip
175
- name = "Execute Procedure"
176
- log ( sql , name ) do
177
- case @connection_options [ :mode ]
178
- when :dblib
179
- result = ensure_established_connection! { dblib_execute ( sql ) }
192
+
193
+ log ( sql , "Execute Procedure" ) do
194
+ with_raw_connection do |conn |
195
+ result = _execute ( sql , conn )
180
196
options = { as : :hash , cache_rows : true , timezone : ActiveRecord . default_timezone || :utc }
197
+
181
198
result . each ( options ) do |row |
182
199
r = row . with_indifferent_access
183
200
yield ( r ) if block_given?
184
201
end
202
+
185
203
result . each . map { |row | row . is_a? ( Hash ) ? row . with_indifferent_access : row }
186
204
end
187
205
end
206
+
188
207
end
189
208
190
209
def with_identity_insert_enabled ( table_name )
@@ -198,8 +217,8 @@ def with_identity_insert_enabled(table_name)
198
217
def use_database ( database = nil )
199
218
return if sqlserver_azure?
200
219
201
- name = SQLServer ::Utils . extract_identifiers ( database || @connection_options [ :database ] ) . quoted
202
- do_execute "USE #{ name } " unless name . blank?
220
+ name = SQLServer ::Utils . extract_identifiers ( database || @connection_parameters [ :database ] ) . quoted
221
+ execute "USE #{ name } " unless name . blank?
203
222
end
204
223
205
224
def user_options
@@ -270,19 +289,22 @@ def sql_for_insert(sql, pk, binds, _returning)
270
289
end
271
290
272
291
sql = if pk && use_output_inserted? && !database_prefix_remote_server?
273
- quoted_pk = SQLServer ::Utils . extract_identifiers ( pk ) . quoted
274
292
table_name ||= get_table_name ( sql )
275
293
exclude_output_inserted = exclude_output_inserted_table_name? ( table_name , sql )
276
294
277
295
if exclude_output_inserted
296
+ quoted_pk = SQLServer ::Utils . extract_identifiers ( pk ) . quoted
297
+
278
298
id_sql_type = exclude_output_inserted . is_a? ( TrueClass ) ? "bigint" : exclude_output_inserted
279
299
<<~SQL . squish
280
300
DECLARE @ssaIdInsertTable table (#{ quoted_pk } #{ id_sql_type } );
281
301
#{ sql . dup . insert sql . index ( / (DEFAULT )?VALUES/i ) , " OUTPUT INSERTED.#{ quoted_pk } INTO @ssaIdInsertTable" }
282
302
SELECT CAST(#{ quoted_pk } AS #{ id_sql_type } ) FROM @ssaIdInsertTable
283
303
SQL
284
304
else
285
- sql . dup . insert sql . index ( / (DEFAULT )?VALUES/i ) , " OUTPUT INSERTED.#{ quoted_pk } "
305
+ inserted_keys = Array ( pk ) . map { |primary_key | " INSERTED.#{ SQLServer ::Utils . extract_identifiers ( primary_key ) . quoted } " }
306
+
307
+ sql . dup . insert sql . index ( / (DEFAULT )?VALUES/i ) , " OUTPUT" + inserted_keys . join ( "," )
286
308
end
287
309
else
288
310
"#{ sql } ; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
@@ -293,29 +315,22 @@ def sql_for_insert(sql, pk, binds, _returning)
293
315
# === SQLServer Specific ======================================== #
294
316
295
317
def set_identity_insert ( table_name , enable = true )
296
- do_execute "SET IDENTITY_INSERT #{ table_name } #{ enable ? 'ON' : 'OFF' } "
318
+ execute "SET IDENTITY_INSERT #{ table_name } #{ enable ? 'ON' : 'OFF' } "
297
319
rescue Exception
298
320
raise ActiveRecordError , "IDENTITY_INSERT could not be turned #{ enable ? 'ON' : 'OFF' } for table #{ table_name } "
299
321
end
300
322
301
323
# === SQLServer Specific (Executing) ============================ #
302
324
303
- def do_execute ( sql , name = "SQL" )
304
- connect if @connection . nil?
305
-
306
- materialize_transactions
307
- mark_transaction_written_if_write ( sql )
308
-
309
- log ( sql , name ) { raw_connection_do ( sql ) }
310
- end
311
-
312
325
# TODO: Adapter should be refactored to use `with_raw_connection` to translate exceptions.
313
326
def sp_executesql ( sql , name , binds , options = { } )
314
327
options [ :ar_result ] = true if options [ :fetch ] != :rows
328
+
315
329
unless without_prepared_statement? ( binds )
316
330
types , params = sp_executesql_types_and_parameters ( binds )
317
331
sql = sp_executesql_sql ( sql , types , params , name )
318
332
end
333
+
319
334
raw_select sql , name , binds , options
320
335
rescue => original_exception
321
336
translated_exception = translate_exception_class ( original_exception , sql , binds )
@@ -373,11 +388,8 @@ def sp_executesql_sql(sql, types, params, name)
373
388
end
374
389
375
390
def raw_connection_do ( sql )
376
- case @connection_options [ :mode ]
377
- when :dblib
378
- result = ensure_established_connection! { dblib_execute ( sql ) }
379
- result . do
380
- end
391
+ result = ensure_established_connection! { dblib_execute ( sql ) }
392
+ result . do
381
393
ensure
382
394
@update_sql = false
383
395
end
@@ -436,45 +448,38 @@ def _raw_select(sql, options = {})
436
448
end
437
449
438
450
def raw_connection_run ( sql )
439
- case @connection_options [ :mode ]
440
- when :dblib
441
- ensure_established_connection! { dblib_execute ( sql ) }
442
- end
443
- end
444
-
445
- def handle_more_results? ( handle )
446
- case @connection_options [ :mode ]
447
- when :dblib
448
- end
451
+ ensure_established_connection! { dblib_execute ( sql ) }
449
452
end
450
453
451
454
def handle_to_names_and_values ( handle , options = { } )
452
- case @connection_options [ :mode ]
453
- when :dblib
454
- handle_to_names_and_values_dblib ( handle , options )
455
- end
456
- end
457
-
458
- def handle_to_names_and_values_dblib ( handle , options = { } )
459
455
query_options = { } . tap do |qo |
460
456
qo [ :timezone ] = ActiveRecord . default_timezone || :utc
461
457
qo [ :as ] = ( options [ :ar_result ] || options [ :fetch ] == :rows ) ? :array : :hash
462
458
end
463
459
results = handle . each ( query_options )
464
460
columns = lowercase_schema_reflection ? handle . fields . map { |c | c . downcase } : handle . fields
461
+
465
462
options [ :ar_result ] ? ActiveRecord ::Result . new ( columns , results ) : results
466
463
end
467
464
468
465
def finish_statement_handle ( handle )
469
- case @connection_options [ :mode ]
470
- when :dblib
471
- handle . cancel if handle
472
- end
466
+ handle . cancel if handle
473
467
handle
474
468
end
475
469
470
+ # TODO: Rename
471
+ def _execute ( sql , conn )
472
+ conn . execute ( sql ) . tap do |result |
473
+ # TinyTDS returns false instead of raising an exception if connection fails.
474
+ # Getting around this by raising an exception ourselves while this PR
475
+ # https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
476
+ raise TinyTds ::Error , "failed to execute statement" if result . is_a? ( FalseClass )
477
+ end
478
+ end
479
+
480
+ # TODO: Remove
476
481
def dblib_execute ( sql )
477
- @connection . execute ( sql ) . tap do |result |
482
+ @raw_connection . execute ( sql ) . tap do |result |
478
483
# TinyTDS returns false instead of raising an exception if connection fails.
479
484
# Getting around this by raising an exception ourselves while this PR
480
485
# https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
@@ -483,7 +488,7 @@ def dblib_execute(sql)
483
488
end
484
489
485
490
def ensure_established_connection!
486
- raise TinyTds ::Error , 'SQL Server client is not connected' unless @connection
491
+ raise TinyTds ::Error , 'SQL Server client is not connected' unless @raw_connection
487
492
488
493
yield
489
494
end
0 commit comments