@@ -37,18 +37,16 @@ def indexes(table_name)
3737 data = select ( "EXEC sp_helpindex #{ quote ( table_name ) } " , "SCHEMA" ) rescue [ ]
3838
3939 data . reduce ( [ ] ) do |indexes , index |
40- index = index . with_indifferent_access
41-
42- if index [ :index_description ] . match? ( /primary key/ )
40+ if index [ 'index_description' ] . match? ( /primary key/ )
4341 indexes
4442 else
45- name = index [ : index_name]
46- unique = index [ : index_description] . match? ( /unique/ )
43+ name = index [ ' index_name' ]
44+ unique = index [ ' index_description' ] . match? ( /unique/ )
4745 where = select_value ( "SELECT [filter_definition] FROM sys.indexes WHERE name = #{ quote ( name ) } " , "SCHEMA" )
4846 orders = { }
4947 columns = [ ]
5048
51- index [ : index_keys] . split ( "," ) . each do |column |
49+ index [ ' index_keys' ] . split ( "," ) . each do |column |
5250 column . strip!
5351
5452 if column . end_with? ( "(-)" )
@@ -480,16 +478,15 @@ def initialize_native_database_types
480478 end
481479
482480 def column_definitions ( table_name )
483- identifier = database_prefix_identifier ( table_name )
484- database = identifier . fully_qualified_database_quoted
485- view_exists = view_exists? ( table_name )
486- view_tblnm = view_table_name ( table_name ) if view_exists
481+ identifier = database_prefix_identifier ( table_name )
482+ database = identifier . fully_qualified_database_quoted
483+ view_exists = view_exists? ( table_name )
487484
488485 if view_exists
489486 sql = <<~SQL
490487 SELECT LOWER(c.COLUMN_NAME) AS [name], c.COLUMN_DEFAULT AS [default]
491488 FROM #{ database } .INFORMATION_SCHEMA.COLUMNS c
492- WHERE c.TABLE_NAME = #{ quote ( view_tblnm ) }
489+ WHERE c.TABLE_NAME = #{ quote ( view_table_name ( table_name ) ) }
493490 SQL
494491 results = internal_exec_query ( sql , "SCHEMA" )
495492 default_functions = results . each . with_object ( { } ) { |row , out | out [ row [ "name" ] ] = row [ "default" ] } . compact
@@ -498,71 +495,93 @@ def column_definitions(table_name)
498495 sql = column_definitions_sql ( database , identifier )
499496
500497 binds = [ ]
501- nv128 = SQLServer ::Type ::UnicodeVarchar . new limit : 128
498+ nv128 = SQLServer ::Type ::UnicodeVarchar . new ( limit : 128 )
502499 binds << Relation ::QueryAttribute . new ( "TABLE_NAME" , identifier . object , nv128 )
503500 binds << Relation ::QueryAttribute . new ( "TABLE_SCHEMA" , identifier . schema , nv128 ) unless identifier . schema . blank?
501+
504502 results = internal_exec_query ( sql , "SCHEMA" , binds )
503+ raise ActiveRecord ::StatementInvalid , "Table '#{ table_name } ' doesn't exist" if results . empty?
505504
506505 columns = results . map do |ci |
507- ci = ci . symbolize_keys
508- ci [ :_type ] = ci [ :type ]
509- ci [ :table_name ] = view_tblnm || table_name
510- ci [ :type ] = case ci [ :type ]
511- when /^bit|image|text|ntext|datetime$/
512- ci [ :type ]
513- when /^datetime2|datetimeoffset$/i
514- "#{ ci [ :type ] } (#{ ci [ :datetime_precision ] } )"
515- when /^time$/i
516- "#{ ci [ :type ] } (#{ ci [ :datetime_precision ] } )"
517- when /^numeric|decimal$/i
518- "#{ ci [ :type ] } (#{ ci [ :numeric_precision ] } ,#{ ci [ :numeric_scale ] } )"
519- when /^float|real$/i
520- "#{ ci [ :type ] } "
521- when /^char|nchar|varchar|nvarchar|binary|varbinary|bigint|int|smallint$/
522- ci [ :length ] . to_i == -1 ? "#{ ci [ :type ] } (max)" : "#{ ci [ :type ] } (#{ ci [ :length ] } )"
523- else
524- ci [ :type ]
525- end
526- ci [ :default_value ] ,
527- ci [ :default_function ] = begin
528- default = ci [ :default_value ]
529- if default . nil? && view_exists
530- view_column = views_real_column_name ( table_name , ci [ :name ] ) . downcase
531- default = default_functions [ view_column ] if view_column . present?
532- end
533- case default
534- when nil
535- [ nil , nil ]
536- when /\A \( (\w +\( \) )\) \Z /
537- default_function = Regexp . last_match [ 1 ]
538- [ nil , default_function ]
539- when /\A \( N'(.*)'\) \Z /m
540- string_literal = SQLServer ::Utils . unquote_string ( Regexp . last_match [ 1 ] )
541- [ string_literal , nil ]
542- when /CREATE DEFAULT/mi
543- [ nil , nil ]
544- else
545- type = case ci [ :type ]
546- when /smallint|int|bigint/ then ci [ :_type ]
547- else ci [ :type ]
548- end
549- value = default . match ( /\A \( (.*)\) \Z /m ) [ 1 ]
550- value = select_value ( "SELECT CAST(#{ value } AS #{ type } ) AS value" , "SCHEMA" )
551- [ value , nil ]
552- end
506+ col = {
507+ name : ci [ "name" ] ,
508+ numeric_scale : ci [ "numeric_scale" ] ,
509+ numeric_precision : ci [ "numeric_precision" ] ,
510+ datetime_precision : ci [ "datetime_precision" ] ,
511+ collation : ci [ "collation" ] ,
512+ ordinal_position : ci [ "ordinal_position" ] ,
513+ length : ci [ "length" ]
514+ }
515+
516+ col [ :table_name ] = view_table_name ( table_name ) || table_name
517+ col [ :type ] = column_type ( ci : ci )
518+ col [ :default_value ] , col [ :default_function ] = default_value_and_function ( default : ci [ 'default_value' ] ,
519+ name : ci [ 'name' ] ,
520+ type : col [ :type ] ,
521+ original_type : ci [ 'type' ] ,
522+ view_exists : view_exists ,
523+ table_name : table_name ,
524+ default_functions : default_functions )
525+
526+ col [ :null ] = ci [ 'is_nullable' ] . to_i == 1
527+ col [ :is_primary ] = ci [ 'is_primary' ] . to_i == 1
528+
529+ if [ true , false ] . include? ( ci [ 'is_identity' ] )
530+ col [ :is_identity ] = ci [ 'is_identity' ]
531+ else
532+ col [ :is_identity ] = ci [ 'is_identity' ] . to_i == 1
553533 end
554- ci [ :null ] = ci [ :is_nullable ] . to_i == 1
555- ci . delete ( :is_nullable )
556- ci [ :is_primary ] = ci [ :is_primary ] . to_i == 1
557- ci [ :is_identity ] = ci [ :is_identity ] . to_i == 1 unless [ TrueClass , FalseClass ] . include? ( ci [ :is_identity ] . class )
558- ci
534+
535+ col
559536 end
560537
561- # Since Rails 7, it's expected that all adapter raise error when table doesn't exists.
562- # I'm not aware of the possibility of tables without columns on SQL Server (postgres have those).
563- # Raise error if the method return an empty array
564- columns . tap do |result |
565- raise ActiveRecord ::StatementInvalid , "Table '#{ table_name } ' doesn't exist" if result . empty?
538+ columns
539+ end
540+
541+ def default_value_and_function ( default :, name :, type :, original_type :, view_exists :, table_name :, default_functions :)
542+ if default . nil? && view_exists
543+ view_column = views_real_column_name ( table_name , name ) . downcase
544+ default = default_functions [ view_column ] if view_column . present?
545+ end
546+
547+ case default
548+ when nil
549+ [ nil , nil ]
550+ when /\A \( (\w +\( \) )\) \Z /
551+ default_function = Regexp . last_match [ 1 ]
552+ [ nil , default_function ]
553+ when /\A \( N'(.*)'\) \Z /m
554+ string_literal = SQLServer ::Utils . unquote_string ( Regexp . last_match [ 1 ] )
555+ [ string_literal , nil ]
556+ when /CREATE DEFAULT/mi
557+ [ nil , nil ]
558+ else
559+ type = case type
560+ when /smallint|int|bigint/ then original_type
561+ else type
562+ end
563+ value = default . match ( /\A \( (.*)\) \Z /m ) [ 1 ]
564+ value = select_value ( "SELECT CAST(#{ value } AS #{ type } ) AS value" , "SCHEMA" )
565+ [ value , nil ]
566+ end
567+ end
568+
569+ def column_type ( ci :)
570+ case ci [ 'type' ]
571+ when /^bit|image|text|ntext|datetime$/
572+ ci [ 'type' ]
573+ when /^datetime2|datetimeoffset$/i
574+ "#{ ci [ 'type' ] } (#{ ci [ 'datetime_precision' ] } )"
575+ when /^time$/i
576+ "#{ ci [ 'type' ] } (#{ ci [ 'datetime_precision' ] } )"
577+ when /^numeric|decimal$/i
578+ "#{ ci [ 'type' ] } (#{ ci [ 'numeric_precision' ] } ,#{ ci [ 'numeric_scale' ] } )"
579+ when /^float|real$/i
580+ "#{ ci [ 'type' ] } "
581+ when /^char|nchar|varchar|nvarchar|binary|varbinary|bigint|int|smallint$/
582+ ci [ 'length' ] . to_i == -1 ? "#{ ci [ 'type' ] } (max)" : "#{ ci [ 'type' ] } (#{ ci [ 'length' ] } )"
583+ else
584+ ci [ 'type' ]
566585 end
567586 end
568587
@@ -701,25 +720,26 @@ def lowercase_schema_reflection_sql(node)
701720
702721 def view_table_name ( table_name )
703722 view_info = view_information ( table_name )
704- view_info ? get_table_name ( view_info [ "VIEW_DEFINITION" ] ) : table_name
723+ view_info . present? ? get_table_name ( view_info [ "VIEW_DEFINITION" ] ) : table_name
705724 end
706725
707726 def view_information ( table_name )
708727 @view_information ||= { }
728+
709729 @view_information [ table_name ] ||= begin
710730 identifier = SQLServer ::Utils . extract_identifiers ( table_name )
711731 information_query_table = identifier . database . present? ? "[#{ identifier . database } ].[INFORMATION_SCHEMA].[VIEWS]" : "[INFORMATION_SCHEMA].[VIEWS]"
712- view_info = select_one "SELECT * FROM #{ information_query_table } WITH (NOLOCK) WHERE TABLE_NAME = #{ quote ( identifier . object ) } " , "SCHEMA"
713-
714- if view_info
715- view_info = view_info . with_indifferent_access
716- if view_info [ : VIEW_DEFINITION] . blank? || view_info [ : VIEW_DEFINITION] . length == 4000
717- view_info [ : VIEW_DEFINITION] = begin
718- select_values ( "EXEC sp_helptext #{ identifier . object_quoted } " , "SCHEMA" ) . join
719- rescue
720- warn "No view definition found, possible permissions problem.\n Please run GRANT VIEW DEFINITION TO your_user;"
721- nil
722- end
732+
733+ view_info = select_one ( "SELECT * FROM #{ information_query_table } WITH (NOLOCK) WHERE TABLE_NAME = #{ quote ( identifier . object ) } " , "SCHEMA" ) . to_h
734+
735+ if view_info . present?
736+ if view_info [ ' VIEW_DEFINITION' ] . blank? || view_info [ ' VIEW_DEFINITION' ] . length == 4000
737+ view_info [ ' VIEW_DEFINITION' ] = begin
738+ select_values ( "EXEC sp_helptext #{ identifier . object_quoted } " , "SCHEMA" ) . join
739+ rescue
740+ warn "No view definition found, possible permissions problem.\n Please run GRANT VIEW DEFINITION TO your_user;"
741+ nil
742+ end
723743 end
724744 end
725745
@@ -728,8 +748,8 @@ def view_information(table_name)
728748 end
729749
730750 def views_real_column_name ( table_name , column_name )
731- view_definition = view_information ( table_name ) [ : VIEW_DEFINITION]
732- return column_name unless view_definition
751+ view_definition = view_information ( table_name ) [ ' VIEW_DEFINITION' ]
752+ return column_name if view_definition . blank?
733753
734754 # Remove "CREATE VIEW ... AS SELECT ..." and then match the column name.
735755 match_data = view_definition . sub ( /CREATE\s +VIEW.*AS\s +SELECT\s / , '' ) . match ( /([\w -]*)\s +AS\s +#{ column_name } \W /im )
0 commit comments