activerecord-jdbcsqlserver-adapter 50.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.travis.yml +27 -0
  4. data/CHANGELOG.md +124 -0
  5. data/CODE_OF_CONDUCT.md +31 -0
  6. data/Dockerfile +20 -0
  7. data/Gemfile +77 -0
  8. data/Guardfile +29 -0
  9. data/MIT-LICENSE +20 -0
  10. data/RAILS5-TODO.md +5 -0
  11. data/README.md +93 -0
  12. data/RUNNING_UNIT_TESTS.md +96 -0
  13. data/Rakefile +46 -0
  14. data/VERSION +1 -0
  15. data/activerecord-jdbcsqlserver-adapter.gemspec +21 -0
  16. data/appveyor.yml +39 -0
  17. data/docker-compose.ci.yml +11 -0
  18. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +27 -0
  19. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +25 -0
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/date_time.rb +58 -0
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +47 -0
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +4 -0
  23. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
  24. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +362 -0
  25. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +67 -0
  26. data/lib/active_record/connection_adapters/sqlserver/errors.rb +7 -0
  27. data/lib/active_record/connection_adapters/sqlserver/jdbc_overrides.rb +192 -0
  28. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +99 -0
  29. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +34 -0
  30. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +16 -0
  31. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +517 -0
  32. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +66 -0
  33. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +66 -0
  34. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +22 -0
  35. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +20 -0
  36. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +112 -0
  37. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +64 -0
  38. data/lib/active_record/connection_adapters/sqlserver/type.rb +49 -0
  39. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +19 -0
  40. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +21 -0
  41. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +15 -0
  42. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +32 -0
  43. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +30 -0
  44. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +61 -0
  45. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +71 -0
  46. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +17 -0
  47. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +23 -0
  48. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +21 -0
  49. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +19 -0
  50. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +15 -0
  51. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +11 -0
  52. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +25 -0
  53. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +19 -0
  54. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +15 -0
  55. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +25 -0
  56. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +29 -0
  57. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +12 -0
  58. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +19 -0
  59. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +68 -0
  60. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +93 -0
  61. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +19 -0
  62. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +25 -0
  63. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +21 -0
  64. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +12 -0
  65. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +19 -0
  66. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +26 -0
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +24 -0
  68. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +36 -0
  69. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +26 -0
  70. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +24 -0
  71. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +26 -0
  72. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +24 -0
  73. data/lib/active_record/connection_adapters/sqlserver/utils.rb +146 -0
  74. data/lib/active_record/connection_adapters/sqlserver/version.rb +11 -0
  75. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +445 -0
  76. data/lib/active_record/connection_adapters/sqlserver_column.rb +28 -0
  77. data/lib/active_record/jdbc_sqlserver_connection_methods.rb +31 -0
  78. data/lib/active_record/sqlserver_base.rb +16 -0
  79. data/lib/active_record/tasks/sqlserver_database_tasks.rb +131 -0
  80. data/lib/activerecord-jdbcsqlserver-adapter.rb +24 -0
  81. data/lib/activerecord-sqlserver-adapter.rb +1 -0
  82. data/lib/arel/visitors/sqlserver.rb +205 -0
  83. data/lib/arel_sqlserver.rb +3 -0
  84. data/test/appveyor/dbsetup.ps1 +27 -0
  85. data/test/appveyor/dbsetup.sql +11 -0
  86. data/test/bin/wait-for.sh +79 -0
  87. data/test/cases/adapter_test_sqlserver.rb +430 -0
  88. data/test/cases/coerced_tests.rb +845 -0
  89. data/test/cases/column_test_sqlserver.rb +812 -0
  90. data/test/cases/connection_test_sqlserver.rb +71 -0
  91. data/test/cases/execute_procedure_test_sqlserver.rb +45 -0
  92. data/test/cases/fetch_test_sqlserver.rb +57 -0
  93. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +76 -0
  94. data/test/cases/helper_sqlserver.rb +44 -0
  95. data/test/cases/index_test_sqlserver.rb +47 -0
  96. data/test/cases/json_test_sqlserver.rb +32 -0
  97. data/test/cases/migration_test_sqlserver.rb +61 -0
  98. data/test/cases/order_test_sqlserver.rb +147 -0
  99. data/test/cases/pessimistic_locking_test_sqlserver.rb +94 -0
  100. data/test/cases/rake_test_sqlserver.rb +169 -0
  101. data/test/cases/schema_dumper_test_sqlserver.rb +234 -0
  102. data/test/cases/schema_test_sqlserver.rb +54 -0
  103. data/test/cases/scratchpad_test_sqlserver.rb +8 -0
  104. data/test/cases/showplan_test_sqlserver.rb +65 -0
  105. data/test/cases/specific_schema_test_sqlserver.rb +180 -0
  106. data/test/cases/transaction_test_sqlserver.rb +91 -0
  107. data/test/cases/utils_test_sqlserver.rb +129 -0
  108. data/test/cases/uuid_test_sqlserver.rb +49 -0
  109. data/test/config.yml +38 -0
  110. data/test/debug.rb +14 -0
  111. data/test/fixtures/1px.gif +0 -0
  112. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
  113. data/test/models/sqlserver/booking.rb +3 -0
  114. data/test/models/sqlserver/customers_view.rb +3 -0
  115. data/test/models/sqlserver/datatype.rb +3 -0
  116. data/test/models/sqlserver/datatype_migration.rb +8 -0
  117. data/test/models/sqlserver/dollar_table_name.rb +3 -0
  118. data/test/models/sqlserver/dot_table_name.rb +3 -0
  119. data/test/models/sqlserver/edge_schema.rb +13 -0
  120. data/test/models/sqlserver/fk_has_fk.rb +3 -0
  121. data/test/models/sqlserver/fk_has_pk.rb +3 -0
  122. data/test/models/sqlserver/natural_pk_data.rb +4 -0
  123. data/test/models/sqlserver/natural_pk_int_data.rb +3 -0
  124. data/test/models/sqlserver/no_pk_data.rb +3 -0
  125. data/test/models/sqlserver/object_default.rb +3 -0
  126. data/test/models/sqlserver/quoted_table.rb +7 -0
  127. data/test/models/sqlserver/quoted_view_1.rb +3 -0
  128. data/test/models/sqlserver/quoted_view_2.rb +3 -0
  129. data/test/models/sqlserver/sst_memory.rb +3 -0
  130. data/test/models/sqlserver/string_default.rb +3 -0
  131. data/test/models/sqlserver/string_defaults_big_view.rb +3 -0
  132. data/test/models/sqlserver/string_defaults_view.rb +3 -0
  133. data/test/models/sqlserver/tinyint_pk.rb +3 -0
  134. data/test/models/sqlserver/upper.rb +3 -0
  135. data/test/models/sqlserver/uppered.rb +3 -0
  136. data/test/models/sqlserver/uuid.rb +3 -0
  137. data/test/schema/datatypes/2012.sql +55 -0
  138. data/test/schema/enable-in-memory-oltp.sql +81 -0
  139. data/test/schema/sqlserver_specific_schema.rb +238 -0
  140. data/test/support/coerceable_test_sqlserver.rb +49 -0
  141. data/test/support/connection_reflection.rb +34 -0
  142. data/test/support/load_schema_sqlserver.rb +29 -0
  143. data/test/support/minitest_sqlserver.rb +1 -0
  144. data/test/support/paths_sqlserver.rb +50 -0
  145. data/test/support/rake_helpers.rb +41 -0
  146. data/test/support/sql_counter_sqlserver.rb +28 -0
  147. data/test/support/test_in_memory_oltp.rb +15 -0
  148. metadata +310 -0
@@ -0,0 +1,67 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module DatabaseTasks
5
+
6
+ def create_database(database, options = {})
7
+ name = SQLServer::Utils.extract_identifiers(database)
8
+ db_options = create_database_options(options)
9
+ edition_options = create_database_edition_options(options)
10
+ do_execute "CREATE DATABASE #{name} #{db_options} #{edition_options}"
11
+ end
12
+
13
+ def drop_database(database)
14
+ name = SQLServer::Utils.extract_identifiers(database)
15
+ do_execute "DROP DATABASE #{name}"
16
+ end
17
+
18
+ def current_database
19
+ select_value 'SELECT DB_NAME()'
20
+ end
21
+
22
+ def charset
23
+ select_value "SELECT DATABASEPROPERTYEX(DB_NAME(), 'SqlCharSetName')"
24
+ end
25
+
26
+ def collation
27
+ @collation ||= select_value "SELECT DATABASEPROPERTYEX(DB_NAME(), 'Collation')"
28
+ end
29
+
30
+ private
31
+
32
+ def create_database_options(options={})
33
+ keys = [:collate]
34
+ copts = @connection_options
35
+ options = {
36
+ collate: copts[:collation]
37
+ }.merge(options.symbolize_keys).select { |_, v|
38
+ v.present?
39
+ }.slice(*keys).map { |k,v|
40
+ "#{k.to_s.upcase} #{v}"
41
+ }.join(' ')
42
+ options
43
+ end
44
+
45
+ def create_database_edition_options(options={})
46
+ keys = [:maxsize, :edition, :service_objective]
47
+ copts = @connection_options
48
+ edition_options = {
49
+ maxsize: copts[:azure_maxsize],
50
+ edition: copts[:azure_edition],
51
+ service_objective: copts[:azure_service_objective]
52
+ }.merge(options.symbolize_keys).select { |_, v|
53
+ v.present?
54
+ }.slice(*keys).map { |k,v|
55
+ "#{k.to_s.upcase} = #{v}"
56
+ }.join(', ')
57
+ edition_options = "( #{edition_options} )" if edition_options.present?
58
+ edition_options
59
+ end
60
+
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+
67
+
@@ -0,0 +1,7 @@
1
+ module ActiveRecord
2
+
3
+ class DeadlockVictim < WrappedDatabaseException
4
+ end
5
+
6
+
7
+ end
@@ -0,0 +1,192 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module JDBCOverrides
5
+
6
+ # @Override
7
+ # Needed to reapply this since the jdbc abstract versions don't do the check and
8
+ # end up overriding the sqlserver gem's version
9
+ def exec_insert(sql, name, binds, pk = nil, _sequence_name = nil)
10
+ if id_insert_table_name = exec_insert_requires_identity?(sql, pk, binds)
11
+ with_identity_insert_enabled(id_insert_table_name) { super }
12
+ else
13
+ super
14
+ end
15
+ end
16
+
17
+ # @Override
18
+ # Needed to reapply this since the jdbc abstract versions don't do the check and
19
+ # end up overriding the sqlserver gem's version
20
+ def execute(sql, name = nil)
21
+ if id_insert_table_name = query_requires_identity_insert?(sql)
22
+ with_identity_insert_enabled(id_insert_table_name) { super }
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ # TODO Move to java for potential perf boost
29
+ def execute_procedure(proc_name, *variables, &block)
30
+ vars = if variables.any? && variables.first.is_a?(Hash)
31
+ variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
32
+ else
33
+ variables.map { |v| quote(v) }
34
+ end.join(', ')
35
+ sql = "EXEC #{proc_name} #{vars}".strip
36
+ log(sql, 'Execute Procedure') do
37
+ result = @connection.execute(sql)
38
+
39
+ return [] unless result
40
+
41
+ if result.is_a?(Array)
42
+ result.map! do |res|
43
+ process_execute_procedure_result(res, &block)
44
+ end
45
+ else
46
+ result = process_execute_procedure_result(result, &block)
47
+ end
48
+
49
+ result
50
+ end
51
+ end
52
+
53
+ # @Override
54
+ # MSSQL does not return query plans for prepared statements, so we have to unprepare them
55
+ # SQLServer gem handles this by overridding exec_explain but that doesn't correctly unprepare them for our needs
56
+ def explain(arel, binds = [])
57
+ arel = ActiveRecord::Base.send(:replace_bind_variables, arel, binds.map(&:value_for_database))
58
+ sql = to_sql(arel)
59
+ result = with_showplan_on { execute(sql, 'EXPLAIN') }
60
+ if result.is_a?(Array)
61
+ # We got back multiple result sets but the printer expects them to all be in one
62
+ main_result = result[0]
63
+ result.each_with_index do |result_obj, i|
64
+ next if i == 0
65
+ main_result.rows.concat(result_obj.rows)
66
+ end
67
+ result = main_result
68
+ end
69
+ printer = showplan_printer.new(result)
70
+ printer.pp
71
+ end
72
+
73
+ # @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_connection_class
74
+ def jdbc_connection_class(_spec)
75
+ ::ActiveRecord::ConnectionAdapters::MSSQLJdbcConnection
76
+ end
77
+
78
+ # Override
79
+ # Since we aren't passing dates/times around as strings we need to
80
+ # process them here, just making sure they are a string
81
+ def quoted_date(value)
82
+ super.to_s
83
+ end
84
+
85
+ # @Override
86
+ def reset!
87
+ clear_cache!
88
+ reset_transaction
89
+ @connection.rollback # Have to deal with rollbacks differently than the SQLServer gem
90
+ @connection.configure_connection
91
+ end
92
+
93
+ # @Overwrite
94
+ # Had some special logic and skipped using gem's internal query methods
95
+ def select_rows(sql, name = nil, binds = [])
96
+
97
+ # In some cases the limit is converted to a `TOP(1)` but the bind parameter is still in the array
98
+ if !binds.empty? && sql.include?('TOP(1)')
99
+ binds = binds.delete_if {|b| b.name == 'LIMIT' }
100
+ end
101
+
102
+ exec_query(sql, name, binds).rows
103
+ end
104
+
105
+ # Have to reset this because the default arjdbc functionality is to return false unless a level is passed in
106
+ def supports_transaction_isolation?
107
+ true
108
+ end
109
+
110
+ protected
111
+
112
+ # Called to set any connection specific settings that aren't defined ahead of time
113
+ def configure_connection
114
+ # For sql server 2008+ we want it to send an actual time otherwise comparisons with time columns don't work
115
+ @connection.connection.setSendTimeAsDatetime(false)
116
+ end
117
+
118
+ # @Overwrite
119
+ # Makes a connection before configuring it
120
+ # @connection actually gets defined and then the connect method in the sqlserver gem overrides it
121
+ # This can probably be fixed with a patch to the main gem
122
+ def connect
123
+ @spid = @connection.execute('SELECT @@SPID').first.values.first
124
+ @version_year = version_year # Not sure if this is necessary but kept it this way because the gem has it this way
125
+ configure_connection
126
+ end
127
+
128
+ # @Overwrite
129
+ # This ends up as a no-op without the override
130
+ def do_execute(sql, name = 'SQL')
131
+ execute(sql, name)
132
+ end
133
+
134
+ # @Overwrite
135
+ # Overriding this in case it gets used in places that we don't override by default
136
+ def raw_connection_do(sql)
137
+ @connection.execute(sql)
138
+ ensure
139
+ @update_sql = false
140
+ end
141
+
142
+ # @Overwrite
143
+ def sp_executesql(sql, name, binds, _options = {})
144
+ exec_query(sql, name, binds)
145
+ end
146
+
147
+ # @Overwrite
148
+ # Prevents turning an insert statement into a query with results
149
+ # Slightly adjusted since we know there should always be a table name in the sql
150
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
151
+ pk = primary_key(get_table_name(sql)) if pk.nil?
152
+ [sql, binds, pk, sequence_name]
153
+ end
154
+
155
+ # @Override
156
+ def translate_exception(exception, message)
157
+ return ActiveRecord::ValueTooLong.new(message) if exception.message.include?('java.sql.DataTruncation')
158
+ super
159
+ end
160
+
161
+ # @Overwrite
162
+ # Made it so we don't use the internal calls from the gem
163
+ def version_year
164
+ return @version_year if defined?(@version_year)
165
+ @version_year = begin
166
+ vstring = select_value('SELECT @@version').to_s
167
+ return 2016 if vstring =~ /vNext/
168
+ /SQL Server (\d+)/.match(vstring).to_a.last.to_s.to_i
169
+ rescue Exception => e
170
+ 2016
171
+ end
172
+ end
173
+
174
+ private
175
+
176
+ def _quote(value)
177
+ return value.quoted if value.is_a?(SQLServer::CoreExt::Time) || value.is_a?(SQLServer::CoreExt::DateTime)
178
+ super
179
+ end
180
+
181
+ def process_execute_procedure_result(result)
182
+ result.map do |row|
183
+ obj = row.with_indifferent_access
184
+ yield(obj) if block_given?
185
+ obj
186
+ end
187
+ end
188
+
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,99 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module Quoting
5
+
6
+ QUOTED_TRUE = '1'.freeze
7
+ QUOTED_FALSE = '0'.freeze
8
+ QUOTED_STRING_PREFIX = 'N'.freeze
9
+
10
+ def fetch_type_metadata(sql_type, sqlserver_options = {})
11
+ cast_type = lookup_cast_type(sql_type)
12
+ SQLServer::SqlTypeMetadata.new(
13
+ sql_type: sql_type,
14
+ type: cast_type.type,
15
+ limit: cast_type.limit,
16
+ precision: cast_type.precision,
17
+ scale: cast_type.scale,
18
+ sqlserver_options: sqlserver_options
19
+ )
20
+ end
21
+
22
+ def quote_string(s)
23
+ SQLServer::Utils.quote_string(s)
24
+ end
25
+
26
+ def quote_string_single(s)
27
+ SQLServer::Utils.quote_string_single(s)
28
+ end
29
+
30
+ def quote_string_single_national(s)
31
+ SQLServer::Utils.quote_string_single_national(s)
32
+ end
33
+
34
+ def quote_column_name(name)
35
+ SQLServer::Utils.extract_identifiers(name).quoted
36
+ end
37
+
38
+ def quote_default_expression(value, column)
39
+ cast_type = lookup_cast_type(column.sql_type)
40
+ if cast_type.type == :uuid && value =~ /\(\)/
41
+ value
42
+ else
43
+ super
44
+ end
45
+ end
46
+
47
+ def quoted_true
48
+ QUOTED_TRUE
49
+ end
50
+
51
+ def unquoted_true
52
+ 1
53
+ end
54
+
55
+ def quoted_false
56
+ QUOTED_FALSE
57
+ end
58
+
59
+ def unquoted_false
60
+ 0
61
+ end
62
+
63
+ def quoted_date(value)
64
+ if value.acts_like?(:date)
65
+ Type::Date.new.serialize(value)
66
+ else value.acts_like?(:time)
67
+ Type::DateTime.new.serialize(value)
68
+ end
69
+ end
70
+
71
+
72
+ private
73
+
74
+ def _quote(value)
75
+ case value
76
+ when Type::Binary::Data
77
+ "0x#{value.hex}"
78
+ when ActiveRecord::Type::SQLServer::Data
79
+ value.quoted
80
+ when String, ActiveSupport::Multibyte::Chars
81
+ "#{QUOTED_STRING_PREFIX}#{super}"
82
+ else
83
+ super
84
+ end
85
+ end
86
+
87
+ def _type_cast(value)
88
+ case value
89
+ when ActiveRecord::Type::SQLServer::Data
90
+ value.to_s
91
+ else
92
+ super
93
+ end
94
+ end
95
+
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,34 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ class SchemaCreation < AbstractAdapter::SchemaCreation
5
+
6
+ private
7
+
8
+ def visit_TableDefinition(o)
9
+ if o.as
10
+ table_name = quote_table_name(o.temporary ? "##{o.name}" : o.name)
11
+ projections, source = @conn.to_sql(o.as).match(%r{SELECT\s+(.*)?\s+FROM\s+(.*)?}).captures
12
+ select_into = "SELECT #{projections} INTO #{table_name} FROM #{source}"
13
+ else
14
+ o.instance_variable_set :@as, nil
15
+ super
16
+ end
17
+ end
18
+
19
+ def action_sql(action, dependency)
20
+ case dependency
21
+ when :restrict
22
+ raise ArgumentError, <<-MSG.strip_heredoc
23
+ '#{dependency}' is not supported for :on_update or :on_delete.
24
+ Supported values are: :nullify, :cascade
25
+ MSG
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module SchemaDumper
5
+
6
+ private
7
+
8
+ def schema_collation(column)
9
+ return unless column.collation
10
+ column.collation if column.collation != collation
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,517 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module SchemaStatements
5
+
6
+ def native_database_types
7
+ @native_database_types ||= initialize_native_database_types.freeze
8
+ end
9
+
10
+ def tables(name = nil)
11
+ ActiveSupport::Deprecation.warn 'Passing arguments to #tables is deprecated without replacement.' if name
12
+ tables_sql('BASE TABLE')
13
+ end
14
+
15
+ def table_exists?(table_name)
16
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
17
+ #table_exists? currently checks both tables and views.
18
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
19
+ Use #data_source_exists? instead.
20
+ MSG
21
+ data_source_exists?(table_name)
22
+ end
23
+
24
+ def data_source_exists?(table_name)
25
+ return false if table_name.blank?
26
+ unquoted_table_name = SQLServer::Utils.extract_identifiers(table_name).object
27
+ super(unquoted_table_name)
28
+ end
29
+
30
+ def views
31
+ tables_sql('VIEW')
32
+ end
33
+
34
+ def view_exists?(table_name)
35
+ identifier = SQLServer::Utils.extract_identifiers(table_name)
36
+ super(identifier.object)
37
+ end
38
+
39
+ def create_table(table_name, comment: nil, **options)
40
+ res = super
41
+ clear_cache!
42
+ res
43
+ end
44
+
45
+ def drop_table(table_name, options = {})
46
+ if options[:if_exists] && @version_year != 2016
47
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}"
48
+ else
49
+ super
50
+ end
51
+ end
52
+
53
+ def indexes(table_name, name = nil)
54
+ data = (select("EXEC sp_helpindex #{quote(table_name)}", name) || []) rescue [] # JDBC returns nil instead of an array or erring out for no results
55
+ data.reduce([]) do |indexes, index|
56
+ index = index.with_indifferent_access
57
+ if index[:index_description] =~ /primary key/
58
+ indexes
59
+ else
60
+ name = index[:index_name]
61
+ unique = index[:index_description] =~ /unique/
62
+ where = select_value("SELECT [filter_definition] FROM sys.indexes WHERE name = #{quote(name)}")
63
+ columns = index[:index_keys].split(',').map do |column|
64
+ column.strip!
65
+ column.gsub! '(-)', '' if column.ends_with?('(-)')
66
+ column
67
+ end
68
+ indexes << IndexDefinition.new(table_name, name, unique, columns, nil, nil, where)
69
+ end
70
+ end
71
+ end
72
+
73
+ def columns(table_name)
74
+ return [] if table_name.blank?
75
+ column_definitions(table_name).map do |ci|
76
+ sqlserver_options = ci.slice :ordinal_position, :is_primary, :is_identity
77
+ sql_type_metadata = fetch_type_metadata ci[:type], sqlserver_options
78
+ new_column(
79
+ ci[:name],
80
+ ci[:default_value],
81
+ sql_type_metadata,
82
+ ci[:null],
83
+ ci[:table_name],
84
+ ci[:default_function],
85
+ ci[:collation],
86
+ nil,
87
+ sqlserver_options
88
+ )
89
+ end
90
+ end
91
+
92
+ def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil, comment = nil, sqlserver_options = {})
93
+ SQLServerColumn.new(
94
+ name,
95
+ default,
96
+ sql_type_metadata,
97
+ null, table_name,
98
+ default_function,
99
+ collation,
100
+ comment,
101
+ sqlserver_options
102
+ )
103
+ end
104
+
105
+ def primary_keys(table_name)
106
+ primaries = schema_cache.columns(table_name).select(&:is_primary?).map(&:name)
107
+ primaries.present? ? primaries : identity_columns(table_name).map(&:name)
108
+ end
109
+
110
+ def rename_table(table_name, new_name)
111
+ do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
112
+ rename_table_indexes(table_name, new_name)
113
+ end
114
+
115
+ def remove_column(table_name, column_name, type = nil, options = {})
116
+ raise ArgumentError.new('You must specify at least one column name. Example: remove_column(:people, :first_name)') if column_name.is_a? Array
117
+ remove_check_constraints(table_name, column_name)
118
+ remove_default_constraint(table_name, column_name)
119
+ remove_indexes(table_name, column_name)
120
+ do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
121
+ end
122
+
123
+ def change_column(table_name, column_name, type, options = {})
124
+ sql_commands = []
125
+ indexes = []
126
+ column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s }
127
+ if options_include_default?(options) || (column_object && column_object.type != type.to_sym)
128
+ remove_default_constraint(table_name, column_name)
129
+ indexes = indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }
130
+ remove_indexes(table_name, column_name)
131
+ end
132
+ sql_commands << "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(options[:default], column_object)} WHERE #{quote_column_name(column_name)} IS NULL" if !options[:null].nil? && options[:null] == false && !options[:default].nil?
133
+ sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
134
+ sql_commands[-1] << ' NOT NULL' if !options[:null].nil? && options[:null] == false
135
+ if options_include_default?(options)
136
+ sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote_default_expression(options[:default], column_object)} FOR #{quote_column_name(column_name)}"
137
+ end
138
+ # Add any removed indexes back
139
+ indexes.each do |index|
140
+ sql_commands << "CREATE INDEX #{quote_table_name(index.name)} ON #{quote_table_name(table_name)} (#{index.columns.map { |c| quote_column_name(c) }.join(', ')})"
141
+ end
142
+ sql_commands.each { |c| do_execute(c) }
143
+ end
144
+
145
+ def change_column_default(table_name, column_name, default_or_changes)
146
+ clear_cache!
147
+ column = column_for(table_name, column_name)
148
+ return unless column
149
+ remove_default_constraint(table_name, column_name)
150
+ default = extract_new_default_value(default_or_changes)
151
+ do_execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote_default_expression(default, column)} FOR #{quote_column_name(column_name)}"
152
+ clear_cache!
153
+ end
154
+
155
+ def rename_column(table_name, column_name, new_column_name)
156
+ clear_cache!
157
+ identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{column_name}")
158
+ execute_procedure :sp_rename, identifier.quoted, new_column_name, 'COLUMN'
159
+ rename_column_indexes(table_name, column_name, new_column_name)
160
+ clear_cache!
161
+ end
162
+
163
+ def rename_index(table_name, old_name, new_name)
164
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" if new_name.length > allowed_index_name_length
165
+ identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{old_name}")
166
+ execute_procedure :sp_rename, identifier.quoted, new_name, 'INDEX'
167
+ end
168
+
169
+ def remove_index!(table_name, index_name)
170
+ do_execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
171
+ end
172
+
173
+ def foreign_keys(table_name)
174
+ identifier = SQLServer::Utils.extract_identifiers(table_name)
175
+ fk_info = execute_procedure :sp_fkeys, nil, identifier.schema, nil, identifier.object, identifier.schema
176
+ fk_info.map do |row|
177
+ from_table = identifier.object
178
+ to_table = row['PKTABLE_NAME']
179
+ options = {
180
+ name: row['FK_NAME'],
181
+ column: row['FKCOLUMN_NAME'],
182
+ primary_key: row['PKCOLUMN_NAME'],
183
+ on_update: extract_foreign_key_action('update', row['FK_NAME']),
184
+ on_delete: extract_foreign_key_action('delete', row['FK_NAME'])
185
+ }
186
+ ForeignKeyDefinition.new from_table, to_table, options
187
+ end
188
+ end
189
+
190
+ def extract_foreign_key_action(action, fk_name)
191
+ case select_value("SELECT #{action}_referential_action_desc FROM sys.foreign_keys WHERE name = '#{fk_name}'")
192
+ when 'CASCADE' then :cascade
193
+ when 'SET_NULL' then :nullify
194
+ end
195
+ end
196
+
197
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
198
+ type_limitable = %w(string integer float char nchar varchar nvarchar).include?(type.to_s)
199
+ limit = nil unless type_limitable
200
+ case type.to_s
201
+ when 'integer'
202
+ case limit
203
+ when 1..2 then 'smallint'
204
+ when 3..4, nil then 'integer'
205
+ when 5..8 then 'bigint'
206
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
207
+ end
208
+ when 'datetime2'
209
+ column_type_sql = super
210
+ if precision
211
+ if (0..7) === precision
212
+ column_type_sql << "(#{precision})"
213
+ else
214
+ raise(ActiveRecordError, "The dattime2 type has precision of #{precision}. The allowed range of precision is from 0 to 7")
215
+ end
216
+ end
217
+ column_type_sql
218
+ else
219
+ super
220
+ end
221
+ end
222
+
223
+ def columns_for_distinct(columns, orders)
224
+ order_columns = orders.reject(&:blank?).map{ |s|
225
+ s = s.to_sql unless s.is_a?(String)
226
+ s.gsub(/\s+(?:ASC|DESC)\b/i, '')
227
+ .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
228
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
229
+ [super, *order_columns].join(', ')
230
+ end
231
+
232
+ def update_table_definition(table_name, base)
233
+ SQLServer::Table.new(table_name, base)
234
+ end
235
+
236
+ def change_column_null(table_name, column_name, allow_null, default = nil)
237
+ table_id = SQLServer::Utils.extract_identifiers(table_name)
238
+ column_id = SQLServer::Utils.extract_identifiers(column_name)
239
+ column = detect_column_for! table_name, column_name
240
+ if !allow_null.nil? && allow_null == false && !default.nil?
241
+ do_execute("UPDATE #{table_id} SET #{column_id}=#{quote(default)} WHERE #{column_id} IS NULL")
242
+ end
243
+ sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
244
+ sql << ' NOT NULL' if !allow_null.nil? && allow_null == false
245
+ do_execute sql
246
+ end
247
+
248
+
249
+ protected
250
+
251
+ # === SQLServer Specific ======================================== #
252
+
253
+ def initialize_native_database_types
254
+ {
255
+ primary_key: 'int NOT NULL IDENTITY(1,1) PRIMARY KEY',
256
+ primary_key_nonclustered: 'int NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED',
257
+ integer: { name: 'int', limit: 4 },
258
+ bigint: { name: 'bigint' },
259
+ boolean: { name: 'bit' },
260
+ decimal: { name: 'decimal' },
261
+ money: { name: 'money' },
262
+ smallmoney: { name: 'smallmoney' },
263
+ float: { name: 'float' },
264
+ real: { name: 'real' },
265
+ date: { name: 'date' },
266
+ datetime: { name: 'datetime' },
267
+ datetime2: { name: 'datetime2' },
268
+ datetimeoffset: { name: 'datetimeoffset' },
269
+ smalldatetime: { name: 'smalldatetime' },
270
+ timestamp: { name: 'datetime' },
271
+ time: { name: 'time' },
272
+ char: { name: 'char' },
273
+ varchar: { name: 'varchar', limit: 8000 },
274
+ varchar_max: { name: 'varchar(max)' },
275
+ text_basic: { name: 'text' },
276
+ nchar: { name: 'nchar' },
277
+ string: { name: 'nvarchar', limit: 4000 },
278
+ text: { name: 'nvarchar(max)' },
279
+ ntext: { name: 'ntext' },
280
+ binary_basic: { name: 'binary' },
281
+ varbinary: { name: 'varbinary', limit: 8000 },
282
+ binary: { name: 'varbinary(max)' },
283
+ uuid: { name: 'uniqueidentifier' },
284
+ ss_timestamp: { name: 'timestamp' },
285
+ json: { name: 'nvarchar(max)' }
286
+ }
287
+ end
288
+
289
+ def tables_sql(type)
290
+ tn = lowercase_schema_reflection_sql 'TABLE_NAME'
291
+ sql = "SELECT #{tn} FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = '#{type}' ORDER BY TABLE_NAME"
292
+ select_values sql, 'SCHEMA'
293
+ end
294
+
295
+ COLUMN_DEFINITION_BIND_STRING_0 = defined?(JRUBY_VERSION) ? '?' : '@0'
296
+ COLUMN_DEFINITION_BIND_STRING_1 = defined?(JRUBY_VERSION) ? '?' : '@1'
297
+
298
+ def column_definitions(table_name)
299
+ identifier = if database_prefix_remote_server?
300
+ SQLServer::Utils.extract_identifiers("#{database_prefix}#{table_name}")
301
+ else
302
+ SQLServer::Utils.extract_identifiers(table_name)
303
+ end
304
+ database = identifier.fully_qualified_database_quoted
305
+ view_exists = view_exists?(table_name)
306
+ view_tblnm = view_table_name(table_name) if view_exists
307
+ sql = %{
308
+ SELECT DISTINCT
309
+ #{lowercase_schema_reflection_sql('columns.TABLE_NAME')} AS table_name,
310
+ #{lowercase_schema_reflection_sql('columns.COLUMN_NAME')} AS name,
311
+ columns.DATA_TYPE AS type,
312
+ columns.COLUMN_DEFAULT AS default_value,
313
+ columns.NUMERIC_SCALE AS numeric_scale,
314
+ columns.NUMERIC_PRECISION AS numeric_precision,
315
+ columns.DATETIME_PRECISION AS datetime_precision,
316
+ columns.COLLATION_NAME AS [collation],
317
+ columns.ordinal_position,
318
+ CASE
319
+ WHEN columns.DATA_TYPE IN ('nchar','nvarchar','char','varchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
320
+ ELSE COL_LENGTH('#{database}.'+columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
321
+ END AS [length],
322
+ CASE
323
+ WHEN columns.IS_NULLABLE = 'YES' THEN 1
324
+ ELSE NULL
325
+ END AS [is_nullable],
326
+ CASE
327
+ WHEN KCU.COLUMN_NAME IS NOT NULL AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY' THEN 1
328
+ ELSE NULL
329
+ END AS [is_primary],
330
+ c.is_identity AS [is_identity]
331
+ FROM #{database}.INFORMATION_SCHEMA.COLUMNS columns
332
+ LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
333
+ ON TC.TABLE_NAME = columns.TABLE_NAME
334
+ AND TC.TABLE_SCHEMA = columns.TABLE_SCHEMA
335
+ AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
336
+ LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
337
+ ON KCU.COLUMN_NAME = columns.COLUMN_NAME
338
+ AND KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
339
+ AND KCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG
340
+ AND KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
341
+ INNER JOIN #{database}.sys.schemas AS s
342
+ ON s.name = columns.TABLE_SCHEMA
343
+ AND s.schema_id = s.schema_id
344
+ INNER JOIN #{database}.sys.objects AS o
345
+ ON s.schema_id = o.schema_id
346
+ AND o.is_ms_shipped = 0
347
+ AND o.type IN ('U', 'V')
348
+ AND o.name = columns.TABLE_NAME
349
+ INNER JOIN #{database}.sys.columns AS c
350
+ ON o.object_id = c.object_id
351
+ AND c.name = columns.COLUMN_NAME
352
+ WHERE columns.TABLE_NAME = #{prepared_statements ? COLUMN_DEFINITION_BIND_STRING_0 : quote(identifier.object)}
353
+ AND columns.TABLE_SCHEMA = #{identifier.schema.blank? ? 'schema_name()' : (prepared_statements ? COLUMN_DEFINITION_BIND_STRING_1 : quote(identifier.schema))}
354
+ ORDER BY columns.ordinal_position
355
+ }.gsub(/[ \t\r\n]+/, ' ').strip
356
+
357
+ binds = []
358
+ if prepared_statements
359
+ nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
360
+ binds << Relation::QueryAttribute.new('TABLE_NAME', identifier.object, nv128)
361
+ binds << Relation::QueryAttribute.new('TABLE_SCHEMA', identifier.schema, nv128) unless identifier.schema.blank?
362
+ end
363
+
364
+ results = sp_executesql(sql, 'SCHEMA', binds)
365
+ results.map do |ci|
366
+ ci = ci.symbolize_keys
367
+ ci[:_type] = ci[:type]
368
+ ci[:table_name] = view_tblnm || table_name
369
+ ci[:type] = case ci[:type]
370
+ when /^bit|image|text|ntext|datetime$/
371
+ ci[:type]
372
+ when /^datetime2|datetimeoffset$/i
373
+ "#{ci[:type]}(#{ci[:datetime_precision]})"
374
+ when /^time$/i
375
+ "#{ci[:type]}(#{ci[:datetime_precision]})"
376
+ when /^numeric|decimal$/i
377
+ "#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
378
+ when /^float|real$/i
379
+ "#{ci[:type]}"
380
+ when /^char|nchar|varchar|nvarchar|binary|varbinary|bigint|int|smallint$/
381
+ ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
382
+ else
383
+ ci[:type]
384
+ end
385
+ ci[:default_value],
386
+ ci[:default_function] = begin
387
+ default = ci[:default_value]
388
+ if default.nil? && view_exists
389
+ default = select_value "
390
+ SELECT c.COLUMN_DEFAULT
391
+ FROM #{database}.INFORMATION_SCHEMA.COLUMNS c
392
+ WHERE c.TABLE_NAME = '#{view_tblnm}'
393
+ AND c.COLUMN_NAME = '#{views_real_column_name(table_name, ci[:name])}'".squish, 'SCHEMA'
394
+ end
395
+ case default
396
+ when nil
397
+ [nil, nil]
398
+ when /\A\((\w+\(\))\)\Z/
399
+ default_function = Regexp.last_match[1]
400
+ [nil, default_function]
401
+ when /\A\(N'(.*)'\)\Z/m
402
+ string_literal = SQLServer::Utils.unquote_string(Regexp.last_match[1])
403
+ [string_literal, nil]
404
+ when /CREATE DEFAULT/mi
405
+ [nil, nil]
406
+ else
407
+ type = case ci[:type]
408
+ when /smallint|int|bigint/ then ci[:_type]
409
+ else ci[:type]
410
+ end
411
+ value = default.match(/\A\((.*)\)\Z/m)[1]
412
+ value = select_value "SELECT CAST(#{value} AS #{type}) AS value", 'SCHEMA'
413
+ [value, nil]
414
+ end
415
+ end
416
+ ci[:null] = ci[:is_nullable].to_i == 1
417
+ ci.delete(:is_nullable)
418
+ ci[:is_primary] = ci[:is_primary].to_i == 1
419
+ ci[:is_identity] = ci[:is_identity].to_i == 1 unless [TrueClass, FalseClass].include?(ci[:is_identity].class)
420
+ ci
421
+ end
422
+ end
423
+
424
+ def remove_check_constraints(table_name, column_name)
425
+ constraints = select_values "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{quote_string(table_name)}' and COLUMN_NAME = '#{quote_string(column_name)}'", 'SCHEMA'
426
+ constraints.each do |constraint|
427
+ do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
428
+ end
429
+ end
430
+
431
+ def remove_default_constraint(table_name, column_name)
432
+ # If their are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
433
+ execute_procedure(:sp_helpconstraint, table_name, 'nomsg').flatten.select do |row|
434
+ row['constraint_type'] == "DEFAULT on column #{column_name}"
435
+ end.each do |row|
436
+ do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
437
+ end
438
+ end
439
+
440
+ def remove_indexes(table_name, column_name)
441
+ indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }.each do |index|
442
+ remove_index(table_name, name: index.name)
443
+ end
444
+ end
445
+
446
+ # === SQLServer Specific (Misc Helpers) ========================= #
447
+
448
+ def get_table_name(sql)
449
+ tn = if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
450
+ Regexp.last_match[3] || Regexp.last_match[4]
451
+ elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
452
+ Regexp.last_match[1]
453
+ else
454
+ nil
455
+ end
456
+ SQLServer::Utils.extract_identifiers(tn).object
457
+ end
458
+
459
+ def default_constraint_name(table_name, column_name)
460
+ "DF_#{table_name}_#{column_name}"
461
+ end
462
+
463
+ def detect_column_for!(table_name, column_name)
464
+ unless column = schema_cache.columns(table_name).find { |c| c.name == column_name.to_s }
465
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
466
+ end
467
+ column
468
+ end
469
+
470
+ def lowercase_schema_reflection_sql(node)
471
+ lowercase_schema_reflection ? "LOWER(#{node})" : node
472
+ end
473
+
474
+ # === SQLServer Specific (View Reflection) ====================== #
475
+
476
+ def view_table_name(table_name)
477
+ view_info = view_information(table_name)
478
+ view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
479
+ end
480
+
481
+ def view_information(table_name)
482
+ @view_information ||= {}
483
+ @view_information[table_name] ||= begin
484
+ identifier = SQLServer::Utils.extract_identifiers(table_name)
485
+ view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{identifier.object}'", 'SCHEMA'
486
+ if view_info
487
+ view_info = view_info.with_indifferent_access
488
+ if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
489
+ view_info[:VIEW_DEFINITION] = begin
490
+ select_values("EXEC sp_helptext #{identifier.object_quoted}", 'SCHEMA').join
491
+ rescue
492
+ warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
493
+ nil
494
+ end
495
+ end
496
+ end
497
+ view_info
498
+ end
499
+ end
500
+
501
+ def views_real_column_name(table_name, column_name)
502
+ view_definition = view_information(table_name)[:VIEW_DEFINITION]
503
+ return column_name unless view_definition
504
+ match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
505
+ match_data ? match_data[1] : column_name
506
+ end
507
+
508
+ private
509
+
510
+ def create_table_definition(*args)
511
+ SQLServer::TableDefinition.new(*args)
512
+ end
513
+
514
+ end
515
+ end
516
+ end
517
+ end