@@ -13,39 +13,58 @@ def write_query?(sql) # :nodoc:
1313 !READ_QUERY . match? ( sql . b )
1414 end
1515
16- def execute ( sql , name = nil )
16+ def raw_execute ( sql , name , async : false , allow_retry : false , materialize_transactions : true )
17+ result = nil
1718 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
2119
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
2427
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
2930 end
31+
32+ result
3033 end
3134
3235 def internal_exec_query ( sql , name = "SQL" , binds = [ ] , prepare : false , async : false )
36+ result = nil
3337 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
3738
38- materialize_transactions
39+ check_if_write_query ( sql )
3940 mark_transaction_written_if_write ( sql )
4041
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
4261 end
4362
4463 def exec_insert ( sql , name = nil , binds = [ ] , pk = nil , _sequence_name = nil , returning : nil )
4564 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 }
4766 else
48- super ( sql , name , binds , pk , returning )
67+ super
4968 end
5069 end
5170
@@ -60,7 +79,7 @@ def exec_update(sql, name, binds)
6079 end
6180
6281 def begin_db_transaction
63- do_execute "BEGIN TRANSACTION" , "TRANSACTION"
82+ execute "BEGIN TRANSACTION" , "TRANSACTION"
6483 end
6584
6685 def transaction_isolation_levels
@@ -73,25 +92,25 @@ def begin_isolated_db_transaction(isolation)
7392 end
7493
7594 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"
7796 end
7897
7998 def commit_db_transaction
80- do_execute "COMMIT TRANSACTION" , "TRANSACTION"
99+ execute "COMMIT TRANSACTION" , "TRANSACTION"
81100 end
82101
83102 def exec_rollback_db_transaction
84- do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION" , "TRANSACTION"
103+ execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION" , "TRANSACTION"
85104 end
86105
87106 include Savepoints
88107
89108 def create_savepoint ( name = current_savepoint_name )
90- do_execute "SAVE TRANSACTION #{ name } " , "TRANSACTION"
109+ execute "SAVE TRANSACTION #{ name } " , "TRANSACTION"
91110 end
92111
93112 def exec_rollback_to_savepoint ( name = current_savepoint_name )
94- do_execute "ROLLBACK TRANSACTION #{ name } " , "TRANSACTION"
113+ execute "ROLLBACK TRANSACTION #{ name } " , "TRANSACTION"
95114 end
96115
97116 def release_savepoint ( name = current_savepoint_name )
@@ -164,27 +183,27 @@ def build_insert_sql(insert) # :nodoc:
164183 # === SQLServer Specific ======================================== #
165184
166185 def execute_procedure ( proc_name , *variables )
167- materialize_transactions
168-
169186 vars = if variables . any? && variables . first . is_a? ( Hash )
170187 variables . first . map { |k , v | "@#{ k } = #{ quote ( v ) } " }
171188 else
172189 variables . map { |v | quote ( v ) }
173190 end . join ( ", " )
174191 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 )
180196 options = { as : :hash , cache_rows : true , timezone : ActiveRecord . default_timezone || :utc }
197+
181198 result . each ( options ) do |row |
182199 r = row . with_indifferent_access
183200 yield ( r ) if block_given?
184201 end
202+
185203 result . each . map { |row | row . is_a? ( Hash ) ? row . with_indifferent_access : row }
186204 end
187205 end
206+
188207 end
189208
190209 def with_identity_insert_enabled ( table_name )
@@ -198,8 +217,8 @@ def with_identity_insert_enabled(table_name)
198217 def use_database ( database = nil )
199218 return if sqlserver_azure?
200219
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?
203222 end
204223
205224 def user_options
@@ -270,19 +289,22 @@ def sql_for_insert(sql, pk, binds, _returning)
270289 end
271290
272291 sql = if pk && use_output_inserted? && !database_prefix_remote_server?
273- quoted_pk = SQLServer ::Utils . extract_identifiers ( pk ) . quoted
274292 table_name ||= get_table_name ( sql )
275293 exclude_output_inserted = exclude_output_inserted_table_name? ( table_name , sql )
276294
277295 if exclude_output_inserted
296+ quoted_pk = SQLServer ::Utils . extract_identifiers ( pk ) . quoted
297+
278298 id_sql_type = exclude_output_inserted . is_a? ( TrueClass ) ? "bigint" : exclude_output_inserted
279299 <<~SQL . squish
280300 DECLARE @ssaIdInsertTable table (#{ quoted_pk } #{ id_sql_type } );
281301 #{ sql . dup . insert sql . index ( / (DEFAULT )?VALUES/i ) , " OUTPUT INSERTED.#{ quoted_pk } INTO @ssaIdInsertTable" }
282302 SELECT CAST(#{ quoted_pk } AS #{ id_sql_type } ) FROM @ssaIdInsertTable
283303 SQL
284304 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 ( "," )
286308 end
287309 else
288310 "#{ sql } ; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
@@ -293,29 +315,22 @@ def sql_for_insert(sql, pk, binds, _returning)
293315 # === SQLServer Specific ======================================== #
294316
295317 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' } "
297319 rescue Exception
298320 raise ActiveRecordError , "IDENTITY_INSERT could not be turned #{ enable ? 'ON' : 'OFF' } for table #{ table_name } "
299321 end
300322
301323 # === SQLServer Specific (Executing) ============================ #
302324
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-
312325 # TODO: Adapter should be refactored to use `with_raw_connection` to translate exceptions.
313326 def sp_executesql ( sql , name , binds , options = { } )
314327 options [ :ar_result ] = true if options [ :fetch ] != :rows
328+
315329 unless without_prepared_statement? ( binds )
316330 types , params = sp_executesql_types_and_parameters ( binds )
317331 sql = sp_executesql_sql ( sql , types , params , name )
318332 end
333+
319334 raw_select sql , name , binds , options
320335 rescue => original_exception
321336 translated_exception = translate_exception_class ( original_exception , sql , binds )
@@ -373,11 +388,8 @@ def sp_executesql_sql(sql, types, params, name)
373388 end
374389
375390 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
381393 ensure
382394 @update_sql = false
383395 end
@@ -436,45 +448,38 @@ def _raw_select(sql, options = {})
436448 end
437449
438450 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 ) }
449452 end
450453
451454 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 = { } )
459455 query_options = { } . tap do |qo |
460456 qo [ :timezone ] = ActiveRecord . default_timezone || :utc
461457 qo [ :as ] = ( options [ :ar_result ] || options [ :fetch ] == :rows ) ? :array : :hash
462458 end
463459 results = handle . each ( query_options )
464460 columns = lowercase_schema_reflection ? handle . fields . map { |c | c . downcase } : handle . fields
461+
465462 options [ :ar_result ] ? ActiveRecord ::Result . new ( columns , results ) : results
466463 end
467464
468465 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
473467 handle
474468 end
475469
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
476481 def dblib_execute ( sql )
477- @connection . execute ( sql ) . tap do |result |
482+ @raw_connection . execute ( sql ) . tap do |result |
478483 # TinyTDS returns false instead of raising an exception if connection fails.
479484 # Getting around this by raising an exception ourselves while this PR
480485 # https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
@@ -483,7 +488,7 @@ def dblib_execute(sql)
483488 end
484489
485490 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
487492
488493 yield
489494 end
0 commit comments