@@ -39,6 +39,8 @@ def cast_result(raw_result)
3939 end
4040
4141 def affected_rows ( raw_result )
42+ return if raw_result . blank?
43+
4244 column_name = lowercase_schema_reflection ? 'affectedrows' : 'AffectedRows'
4345 raw_result . first [ column_name ]
4446 end
@@ -53,20 +55,18 @@ def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow
5355 end
5456
5557 def internal_exec_sql_query ( sql , conn )
56- handle = internal_raw_execute ( sql , conn )
58+ handle = raw_connection_run ( sql , conn )
5759 handle_to_names_and_values ( handle , ar_result : true )
5860 ensure
5961 finish_statement_handle ( handle )
6062 end
6163
6264 def exec_delete ( sql , name = nil , binds = [ ] )
63- sql = sql . dup << "; SELECT @@ROWCOUNT AS AffectedRows"
64- super ( sql , name , binds )
65+ super || super ( "SELECT @@ROWCOUNT As AffectedRows" , "" , [ ] )
6566 end
6667
6768 def exec_update ( sql , name = nil , binds = [ ] )
68- sql = sql . dup << "; SELECT @@ROWCOUNT AS AffectedRows"
69- super ( sql , name , binds )
69+ super || super ( "SELECT @@ROWCOUNT As AffectedRows" , "" , [ ] )
7070 end
7171
7272 def begin_db_transaction
@@ -170,17 +170,8 @@ def execute_procedure(proc_name, *variables)
170170
171171 log ( sql , "Execute Procedure" ) do |notification_payload |
172172 with_raw_connection do |conn |
173- result = internal_raw_execute ( sql , conn )
174- verified!
175- options = { as : :hash , cache_rows : true , timezone : ActiveRecord . default_timezone || :utc }
176-
177- result . each ( options ) do |row |
178- r = row . with_indifferent_access
179- yield ( r ) if block_given?
180- end
181-
182- result = result . each . map { |row | row . is_a? ( Hash ) ? row . with_indifferent_access : row }
183- notification_payload [ :row_count ] = result . count
173+ result = send ( "execute_#{ @config [ :mode ] } _procedure" , sql , conn )
174+ notification_payload [ :row_count ] = result &.count
184175 result
185176 end
186177 end
@@ -280,10 +271,12 @@ def sql_for_insert(sql, pk, binds, returning)
280271 }
281272 end
282273
283- <<~SQL . squish
274+ <<-SQL . strip_heredoc
275+ SET NOCOUNT ON
284276 DECLARE @ssaIdInsertTable table (#{ pk_and_types . map { |pk_and_type | "#{ pk_and_type [ :quoted ] } #{ pk_and_type [ :id_sql_type ] } " } . join ( ", " ) } );
285- #{ sql . dup . insert sql . index ( / (DEFAULT )?VALUES/i ) , " OUTPUT #{ pk_and_types . map { |pk_and_type | "INSERTED.#{ pk_and_type [ :quoted ] } " } . join ( ", " ) } INTO @ssaIdInsertTable" }
286- SELECT #{ pk_and_types . map { |pk_and_type | "CAST(#{ pk_and_type [ :quoted ] } AS #{ pk_and_type [ :id_sql_type ] } ) #{ pk_and_type [ :quoted ] } " } . join ( ", " ) } FROM @ssaIdInsertTable
277+ #{ sql . dup . insert sql . index ( / (DEFAULT )?VALUES/ ) , " OUTPUT #{ pk_and_types . map { |pk_and_type | "INSERTED.#{ pk_and_type [ :quoted ] } " } . join ( ", " ) } INTO @ssaIdInsertTable" }
278+ SELECT #{ pk_and_types . map { |pk_and_type | "CAST(#{ pk_and_type [ :quoted ] } AS #{ pk_and_type [ :id_sql_type ] } ) #{ pk_and_type [ :quoted ] } " } . join ( ", " ) } FROM @ssaIdInsertTable;
279+ SET NOCOUNT OFF
287280 SQL
288281 else
289282 returning_columns = returning || Array ( pk )
@@ -296,7 +289,14 @@ def sql_for_insert(sql, pk, binds, returning)
296289 end
297290 end
298291 else
299- "#{ sql } ; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
292+ table = get_table_name ( sql )
293+ id_column = identity_columns ( table . to_s . strip ) . first
294+
295+ if id_column . present?
296+ sql . sub ( /\s *VALUES\s *\( / , " OUTPUT INSERTED.#{ id_column . name } VALUES (" )
297+ else
298+ sql . sub ( /\s *VALUES\s *\( / , " OUTPUT CAST(SCOPE_IDENTITY() AS bigint) AS Ident VALUES (" )
299+ end
300300 end
301301
302302 [ sql , binds ]
@@ -305,7 +305,11 @@ def sql_for_insert(sql, pk, binds, returning)
305305 # === SQLServer Specific ======================================== #
306306
307307 def set_identity_insert ( table_name , conn , enable )
308- internal_raw_execute ( "SET IDENTITY_INSERT #{ table_name } #{ enable ? 'ON' : 'OFF' } " , conn , perform_do : true )
308+ if @config [ :mode ] . to_sym == :dblib
309+ internal_raw_execute ( "SET IDENTITY_INSERT #{ table_name } #{ enable ? 'ON' : 'OFF' } " , conn , perform_do : true )
310+ else
311+ internal_raw_execute_odbc ( "SET IDENTITY_INSERT #{ table_name } #{ enable ? 'ON' : 'OFF' } " , conn , perform_do : true )
312+ end
309313 rescue Exception
310314 raise ActiveRecordError , "IDENTITY_INSERT could not be turned #{ enable ? 'ON' : 'OFF' } for table #{ table_name } "
311315 end
@@ -338,7 +342,12 @@ def sp_executesql_sql_type(attr)
338342 value = active_model_attribute? ( attr ) ? attr . value_for_database : attr
339343
340344 if value . is_a? ( Numeric )
341- value > 2_147_483_647 ? "bigint" . freeze : "int" . freeze
345+ if value . is_a? ( Integer )
346+ value > 2_147_483_647 ? "bigint" . freeze : "int" . freeze
347+ else
348+ # For Float, BigDecimal, Rational etc.
349+ value . is_a? ( BigDecimal ) ? "decimal(18,6)" . freeze : "float" . freeze
350+ end
342351 else
343352 "nvarchar(max)" . freeze
344353 end
@@ -420,13 +429,26 @@ def identity_columns(table_name)
420429 # === SQLServer Specific (Selecting) ============================ #
421430
422431 def _raw_select ( sql , conn )
423- handle = internal_raw_execute ( sql , conn )
432+ handle = raw_connection_run ( sql , conn )
424433 handle_to_names_and_values ( handle , fetch : :rows )
425434 ensure
426435 finish_statement_handle ( handle )
427436 end
428437
438+ def raw_connection_run ( sql , conn , perform_do : false )
439+ case @config [ :mode ] . to_sym
440+ when :dblib
441+ internal_raw_execute ( sql , conn , perform_do : perform_do )
442+ when :odbc
443+ internal_raw_execute_odbc ( sql , conn , perform_do : perform_do )
444+ end
445+ end
446+
429447 def handle_to_names_and_values ( handle , options = { } )
448+ send ( "handle_to_names_and_values_#{ @config [ :mode ] } " , handle , options )
449+ end
450+
451+ def handle_to_names_and_values_dblib ( handle , options = { } )
430452 query_options = { } . tap do |qo |
431453 qo [ :timezone ] = ActiveRecord . default_timezone || :utc
432454 qo [ :as ] = ( options [ :ar_result ] || options [ :fetch ] == :rows ) ? :array : :hash
@@ -441,8 +463,33 @@ def handle_to_names_and_values(handle, options = {})
441463 options [ :ar_result ] ? ActiveRecord ::Result . new ( columns , results ) : results
442464 end
443465
466+ def handle_to_names_and_values_odbc ( handle , options = { } )
467+ @raw_connection . use_utc = ActiveRecord . default_timezone || :utc
468+
469+ if options [ :ar_result ]
470+ columns = lowercase_schema_reflection ? handle . columns ( true ) . map { |c | c . name . downcase } : handle . columns ( true ) . map { |c | c . name }
471+ rows = handle . fetch_all || [ ]
472+ ActiveRecord ::Result . new ( columns , rows )
473+ else
474+ case options [ :fetch ]
475+ when :all
476+ handle . each_hash || [ ]
477+ when :rows
478+ handle . fetch_all || [ ]
479+ end
480+ end
481+ end
482+
444483 def finish_statement_handle ( handle )
445- handle . cancel if handle
484+ return unless handle
485+
486+ case @config [ :mode ] . to_sym
487+ when :dblib
488+ handle . cancel
489+ when :odbc
490+ handle . drop if handle . respond_to? ( :drop ) && !handle . finished?
491+ end
492+
446493 handle
447494 end
448495
@@ -455,6 +502,54 @@ def internal_raw_execute(sql, raw_connection, perform_do: false)
455502
456503 perform_do ? result . do : result
457504 end
505+
506+ # Executing SQL for ODBC mode
507+ def internal_raw_execute_odbc ( sql , raw_connection , perform_do : false )
508+ return raw_connection . do ( sql ) if perform_do
509+
510+ block_given? ? raw_connection . run_block ( sql ) { |handle | yield ( handle ) } : raw_connection . run ( sql )
511+ end
512+
513+ private
514+
515+ def execute_dblib_procedure ( sql , conn )
516+ result = internal_raw_execute ( sql , conn )
517+ verified!
518+ options = { as : :hash , cache_rows : true , timezone : ActiveRecord . default_timezone || :utc }
519+
520+ raw_rows = result . each ( options ) . map do |row |
521+ row = row . with_indifferent_access
522+ yield ( row ) if block_given?
523+ row
524+ end
525+
526+ raw_rows . map { |row | row . is_a? ( Hash ) ? row . with_indifferent_access : row }
527+ end
528+
529+ def execute_odbc_procedure ( sql , conn )
530+ results = [ ]
531+
532+ internal_raw_execute_odbc ( sql , conn ) do |handle |
533+ get_rows = lambda do
534+ rows = handle_to_names_and_values handle , fetch : :all
535+ results << rows . map! ( &:with_indifferent_access )
536+ end
537+
538+ get_rows . call
539+ get_rows . call while handle_more_results? ( handle )
540+ end
541+
542+ results . many? ? results : results . first
543+ end
544+
545+
546+ def handle_more_results? ( handle )
547+ case @config [ :mode ] . to_sym
548+ when :dblib
549+ when :odbc
550+ handle . more_results
551+ end
552+ end
458553 end
459554 end
460555 end
0 commit comments