activerecord-jdbcsqlserver-adapter 50.0.0 → 52.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.travis.yml +4 -5
  4. data/CHANGELOG.md +22 -101
  5. data/{Dockerfile → Dockerfile.ci} +0 -0
  6. data/Gemfile +1 -3
  7. data/README.md +5 -9
  8. data/VERSION +1 -1
  9. data/activerecord-jdbcsqlserver-adapter.gemspec +2 -2
  10. data/appveyor.yml +1 -1
  11. data/docker-compose.ci.yml +7 -5
  12. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +3 -1
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +3 -1
  14. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +51 -0
  15. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +18 -20
  16. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +5 -3
  17. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +43 -0
  18. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +26 -0
  19. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +13 -2
  20. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +94 -28
  21. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +1 -0
  22. data/lib/active_record/connection_adapters/sqlserver/jdbc_overrides.rb +5 -25
  23. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +24 -1
  24. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +23 -2
  25. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +110 -74
  26. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +15 -7
  27. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +3 -4
  28. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +0 -4
  29. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +5 -0
  30. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +3 -6
  31. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +1 -1
  32. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +7 -0
  33. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -0
  34. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +47 -24
  35. data/lib/active_record/tasks/sqlserver_database_tasks.rb +5 -3
  36. data/lib/activerecord-jdbcsqlserver-adapter.rb +4 -1
  37. data/lib/arel/visitors/sqlserver.rb +17 -4
  38. data/lib/arel_sqlserver.rb +0 -1
  39. data/lib/jdbc_mssql_driver_loader.rb +22 -0
  40. data/test/bin/install-freetds.sh +18 -0
  41. data/test/bin/setup.sh +19 -0
  42. data/test/cases/adapter_test_sqlserver.rb +43 -39
  43. data/test/cases/change_column_null_test_sqlserver.rb +42 -0
  44. data/test/cases/coerced_tests.rb +419 -39
  45. data/test/cases/column_test_sqlserver.rb +496 -462
  46. data/test/cases/connection_test_sqlserver.rb +2 -2
  47. data/test/cases/fetch_test_sqlserver.rb +5 -5
  48. data/test/cases/helper_sqlserver.rb +12 -1
  49. data/test/cases/json_test_sqlserver.rb +6 -6
  50. data/test/cases/migration_test_sqlserver.rb +13 -3
  51. data/test/cases/order_test_sqlserver.rb +19 -19
  52. data/test/cases/pessimistic_locking_test_sqlserver.rb +37 -20
  53. data/test/cases/rake_test_sqlserver.rb +20 -20
  54. data/test/cases/schema_dumper_test_sqlserver.rb +44 -43
  55. data/test/cases/schema_test_sqlserver.rb +2 -2
  56. data/test/cases/showplan_test_sqlserver.rb +25 -10
  57. data/test/cases/specific_schema_test_sqlserver.rb +11 -17
  58. data/test/cases/transaction_test_sqlserver.rb +9 -9
  59. data/test/cases/trigger_test_sqlserver.rb +31 -0
  60. data/test/cases/utils_test_sqlserver.rb +36 -36
  61. data/test/cases/uuid_test_sqlserver.rb +8 -8
  62. data/test/config.yml +2 -2
  63. data/test/migrations/create_clients_and_change_column_null.rb +23 -0
  64. data/test/models/sqlserver/trigger.rb +7 -0
  65. data/test/models/sqlserver/trigger_history.rb +3 -0
  66. data/test/schema/datatypes/2012.sql +1 -0
  67. data/test/schema/sqlserver_specific_schema.rb +47 -5
  68. data/test/support/core_ext/query_cache.rb +29 -0
  69. data/test/support/sql_counter_sqlserver.rb +1 -1
  70. metadata +32 -15
  71. data/RAILS5-TODO.md +0 -5
  72. data/test/models/sqlserver/dot_table_name.rb +0 -3
@@ -0,0 +1,26 @@
1
+ require 'active_record/relation'
2
+ require 'active_record/version'
3
+
4
+ module ActiveRecord
5
+ module ConnectionAdapters
6
+ module SQLServer
7
+ module CoreExt
8
+ module QueryMethods
9
+
10
+ private
11
+
12
+ # Copy of original from Rails master. This patch can be removed when adapter supports Rails 6.
13
+ def table_name_matches?(from)
14
+ table_name = Regexp.escape(table.name)
15
+ quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
16
+ /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ ActiveSupport.on_load(:active_record) do
25
+ ActiveRecord::Relation.include(ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::QueryMethods)
26
+ end
@@ -2,7 +2,6 @@ module ActiveRecord
2
2
  module ConnectionAdapters
3
3
  module SQLServer
4
4
  module DatabaseLimits
5
-
6
5
  def table_alias_length
7
6
  128
8
7
  end
@@ -32,7 +31,7 @@ module ActiveRecord
32
31
  end
33
32
 
34
33
  def in_clause_length
35
- 65_536
34
+ 10_000
36
35
  end
37
36
 
38
37
  def sql_query_length
@@ -43,6 +42,18 @@ module ActiveRecord
43
42
  256
44
43
  end
45
44
 
45
+ private
46
+
47
+ # The max number of binds is 2100, but because sp_executesql takes
48
+ # the first 2 params as the query string and the list of types,
49
+ # we have only 2098 spaces left
50
+ def bind_params_length
51
+ 2_098
52
+ end
53
+
54
+ def insert_rows_length
55
+ 1_000
56
+ end
46
57
  end
47
58
  end
48
59
  end
@@ -3,10 +3,6 @@ module ActiveRecord
3
3
  module SQLServer
4
4
  module DatabaseStatements
5
5
 
6
- def select_rows(sql, name = nil, binds = [])
7
- sp_executesql sql, name, binds, fetch: :rows
8
- end
9
-
10
6
  def execute(sql, name = nil)
11
7
  if id_insert_table_name = query_requires_identity_insert?(sql)
12
8
  with_identity_insert_enabled(id_insert_table_name) { do_execute(sql, name) }
@@ -19,26 +15,22 @@ module ActiveRecord
19
15
  sp_executesql(sql, name, binds, prepare: prepare)
20
16
  end
21
17
 
22
- def exec_insert(sql, name, binds, pk = nil, _sequence_name = nil)
18
+ def exec_insert(sql, name = nil, binds = [], pk = nil, _sequence_name = nil)
23
19
  if id_insert_table_name = exec_insert_requires_identity?(sql, pk, binds)
24
- with_identity_insert_enabled(id_insert_table_name) { exec_query(sql, name, binds) }
20
+ with_identity_insert_enabled(id_insert_table_name) { super(sql, name, binds, pk) }
25
21
  else
26
- exec_query(sql, name, binds)
22
+ super(sql, name, binds, pk)
27
23
  end
28
24
  end
29
25
 
30
26
  def exec_delete(sql, name, binds)
31
- sql << '; SELECT @@ROWCOUNT AS AffectedRows'
32
- super.rows.first.first
27
+ sql = sql.dup << '; SELECT @@ROWCOUNT AS AffectedRows'
28
+ super(sql, name, binds).rows.first.first
33
29
  end
34
30
 
35
31
  def exec_update(sql, name, binds)
36
- sql << '; SELECT @@ROWCOUNT AS AffectedRows'
37
- super.rows.first.first
38
- end
39
-
40
- def supports_statement_cache?
41
- true
32
+ sql = sql.dup << '; SELECT @@ROWCOUNT AS AffectedRows'
33
+ super(sql, name, binds).rows.first.first
42
34
  end
43
35
 
44
36
  def begin_db_transaction
@@ -80,18 +72,56 @@ module ActiveRecord
80
72
  end
81
73
 
82
74
  def case_sensitive_comparison(table, attribute, column, value)
83
- if value && value.acts_like?(:string)
84
- table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
75
+ if column.collation && !column.case_sensitive?
76
+ table[attribute].eq(Arel::Nodes::Bin.new(value))
85
77
  else
86
78
  super
87
79
  end
88
80
  end
89
81
 
82
+ # We should propose this change to Rails team
83
+ def insert_fixtures_set(fixture_set, tables_to_delete = [])
84
+ fixture_inserts = []
85
+
86
+ fixture_set.each do |table_name, fixtures|
87
+ fixtures.each_slice(insert_rows_length) do |batch|
88
+ fixture_inserts << build_fixture_sql(batch, table_name)
89
+ end
90
+ end
91
+
92
+ table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name table}".dup }
93
+ total_sql = Array.wrap(combine_multi_statements(table_deletes + fixture_inserts))
94
+
95
+ disable_referential_integrity do
96
+ transaction(requires_new: true) do
97
+ total_sql.each do |sql|
98
+ execute sql, "Fixtures Load"
99
+ yield if block_given?
100
+ end
101
+ end
102
+ end
103
+ end
104
+
90
105
  def can_perform_case_insensitive_comparison_for?(column)
91
- column.type == :string
106
+ column.type == :string && (!column.collation || column.case_sensitive?)
92
107
  end
93
108
  private :can_perform_case_insensitive_comparison_for?
94
109
 
110
+ def combine_multi_statements(total_sql)
111
+ total_sql
112
+ end
113
+ private :combine_multi_statements
114
+
115
+ def default_insert_value(column)
116
+ if column.is_identity?
117
+ table_name = quote(quote_table_name(column.table_name))
118
+ Arel.sql("IDENT_CURRENT(#{table_name}) + IDENT_INCR(#{table_name})")
119
+ else
120
+ super
121
+ end
122
+ end
123
+ private :default_insert_value
124
+
95
125
  # === SQLServer Specific ======================================== #
96
126
 
97
127
  def execute_procedure(proc_name, *variables)
@@ -196,9 +226,20 @@ module ActiveRecord
196
226
  table_name = query_requires_identity_insert?(sql)
197
227
  pk = primary_key(table_name)
198
228
  end
199
- sql = if pk && self.class.use_output_inserted && !database_prefix_remote_server?
229
+ sql = if pk && use_output_inserted? && !database_prefix_remote_server?
200
230
  quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
201
- sql.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk}"
231
+ table_name ||= get_table_name(sql)
232
+ exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)
233
+ if exclude_output_inserted
234
+ id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? 'bigint' : exclude_output_inserted
235
+ <<-SQL.strip_heredoc
236
+ DECLARE @ssaIdInsertTable table (#{quoted_pk} #{id_sql_type});
237
+ #{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk} INTO @ssaIdInsertTable"}
238
+ SELECT CAST(#{quoted_pk} AS #{id_sql_type}) FROM @ssaIdInsertTable
239
+ SQL
240
+ else
241
+ sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk}"
242
+ end
202
243
  else
203
244
  "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
204
245
  end
@@ -231,6 +272,8 @@ module ActiveRecord
231
272
  def sp_executesql_types_and_parameters(binds)
232
273
  types, params = [], []
233
274
  binds.each_with_index do |attr, index|
275
+ attr = attr.value if attr.is_a?(Arel::Nodes::BindParam)
276
+
234
277
  types << "@#{index} #{sp_executesql_sql_type(attr)}"
235
278
  params << sp_executesql_sql_param(attr)
236
279
  end
@@ -241,19 +284,19 @@ module ActiveRecord
241
284
  return attr.type.sqlserver_type if attr.type.respond_to?(:sqlserver_type)
242
285
  case value = attr.value_for_database
243
286
  when Numeric
244
- 'int'.freeze
287
+ value > 2_147_483_647 ? 'bigint'.freeze : 'int'.freeze
245
288
  else
246
289
  'nvarchar(max)'.freeze
247
290
  end
248
291
  end
249
292
 
250
293
  def sp_executesql_sql_param(attr)
251
- case attr.value_for_database
294
+ case value = attr.value_for_database
252
295
  when Type::Binary::Data,
253
296
  ActiveRecord::Type::SQLServer::Data
254
- quote(attr.value_for_database)
297
+ quote(value)
255
298
  else
256
- quote(type_cast(attr.value_for_database))
299
+ quote(type_cast(value))
257
300
  end
258
301
  end
259
302
 
@@ -261,7 +304,7 @@ module ActiveRecord
261
304
  if name == 'EXPLAIN'
262
305
  params.each.with_index do |param, index|
263
306
  substitute_at_finder = /(@#{index})(?=(?:[^']|'[^']*')*$)/ # Finds unquoted @n values.
264
- sql.sub! substitute_at_finder, param.to_s
307
+ sql = sql.sub substitute_at_finder, param.to_s
265
308
  end
266
309
  else
267
310
  types = quote(types.join(', '))
@@ -275,7 +318,14 @@ module ActiveRecord
275
318
  def raw_connection_do(sql)
276
319
  case @connection_options[:mode]
277
320
  when :dblib
278
- @connection.execute(sql).do
321
+ result = @connection.execute(sql)
322
+
323
+ # TinyTDS returns false instead of raising an exception if connection fails.
324
+ # Getting around this by raising an exception ourselves while this PR
325
+ # https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
326
+ raise TinyTds::Error, "failed to execute statement" if result.is_a?(FalseClass)
327
+
328
+ result.do
279
329
  end
280
330
  ensure
281
331
  @update_sql = false
@@ -283,15 +333,31 @@ module ActiveRecord
283
333
 
284
334
  # === SQLServer Specific (Identity Inserts) ===================== #
285
335
 
336
+ def use_output_inserted?
337
+ self.class.use_output_inserted
338
+ end
339
+
340
+ def exclude_output_inserted_table_names?
341
+ !self.class.exclude_output_inserted_table_names.empty?
342
+ end
343
+
344
+ def exclude_output_inserted_table_name?(table_name, sql)
345
+ return false unless exclude_output_inserted_table_names?
346
+ table_name ||= get_table_name(sql)
347
+ return false unless table_name
348
+ self.class.exclude_output_inserted_table_names[table_name]
349
+ end
350
+
286
351
  def exec_insert_requires_identity?(sql, pk, binds)
287
- query_requires_identity_insert?(sql) if pk && binds.map(&:name).include?(pk)
352
+ query_requires_identity_insert?(sql)
288
353
  end
289
354
 
290
355
  def query_requires_identity_insert?(sql)
291
356
  if insert_sql?(sql)
292
357
  table_name = get_table_name(sql)
293
358
  id_column = identity_columns(table_name).first
294
- id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
359
+ # id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
360
+ id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? table_name : false
295
361
  else
296
362
  false
297
363
  end
@@ -12,6 +12,7 @@ module ActiveRecord
12
12
 
13
13
  def drop_database(database)
14
14
  name = SQLServer::Utils.extract_identifiers(database)
15
+ do_execute "ALTER DATABASE #{name} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
15
16
  do_execute "DROP DATABASE #{name}"
16
17
  end
17
18
 
@@ -90,18 +90,6 @@ module ActiveRecord
90
90
  @connection.configure_connection
91
91
  end
92
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
93
  # Have to reset this because the default arjdbc functionality is to return false unless a level is passed in
106
94
  def supports_transaction_isolation?
107
95
  true
@@ -152,25 +140,17 @@ module ActiveRecord
152
140
  [sql, binds, pk, sequence_name]
153
141
  end
154
142
 
143
+ # @Override original version uses driver specific query command
144
+ def sqlserver_version
145
+ @sqlserver_version ||= select_value('SELECT @@version').to_s
146
+ end
147
+
155
148
  # @Override
156
149
  def translate_exception(exception, message)
157
150
  return ActiveRecord::ValueTooLong.new(message) if exception.message.include?('java.sql.DataTruncation')
158
151
  super
159
152
  end
160
153
 
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
154
  private
175
155
 
176
156
  def _quote(value)
@@ -8,7 +8,8 @@ module ActiveRecord
8
8
  def visit_TableDefinition(o)
9
9
  if o.as
10
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
11
+ query = o.as.respond_to?(:to_sql) ? o.as.to_sql : o.as
12
+ projections, source = query.match(%r{SELECT\s+(.*)?\s+FROM\s+(.*)?}).captures
12
13
  select_into = "SELECT #{projections} INTO #{table_name} FROM #{source}"
13
14
  else
14
15
  o.instance_variable_set :@as, nil
@@ -16,6 +17,20 @@ module ActiveRecord
16
17
  end
17
18
  end
18
19
 
20
+ def add_column_options!(sql, options)
21
+ sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
22
+ if options[:null] == false
23
+ sql << " NOT NULL"
24
+ end
25
+ if options[:is_identity] == true
26
+ sql << " IDENTITY(1,1)"
27
+ end
28
+ if options[:primary_key] == true
29
+ sql << " PRIMARY KEY"
30
+ end
31
+ sql
32
+ end
33
+
19
34
  def action_sql(action, dependency)
20
35
  case dependency
21
36
  when :restrict
@@ -28,6 +43,14 @@ module ActiveRecord
28
43
  end
29
44
  end
30
45
 
46
+ def options_include_default?(options)
47
+ super || options_primary_key_with_nil_default?(options)
48
+ end
49
+
50
+ def options_primary_key_with_nil_default?(options)
51
+ options[:primary_key] && options.include?(:default) && options[:default].nil?
52
+ end
53
+
31
54
  end
32
55
  end
33
56
  end
@@ -1,13 +1,34 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
3
  module SQLServer
4
- module SchemaDumper
4
+ class SchemaDumper < ConnectionAdapters::SchemaDumper
5
+
6
+ SQLSEVER_NO_LIMIT_TYPES = [
7
+ 'text',
8
+ 'ntext',
9
+ 'varchar(max)',
10
+ 'nvarchar(max)',
11
+ 'varbinary(max)'
12
+ ].freeze
5
13
 
6
14
  private
7
15
 
16
+ def explicit_primary_key_default?(column)
17
+ column.is_primary? && !column.is_identity?
18
+ end
19
+
20
+ def schema_limit(column)
21
+ return if SQLSEVER_NO_LIMIT_TYPES.include?(column.sql_type)
22
+ super
23
+ end
24
+
8
25
  def schema_collation(column)
9
26
  return unless column.collation
10
- column.collation if column.collation != collation
27
+ column.collation if column.collation != @connection.collation
28
+ end
29
+
30
+ def default_primary_key?(column)
31
+ super && column.is_primary? && column.is_identity?
11
32
  end
12
33
 
13
34
  end
@@ -7,35 +7,6 @@ module ActiveRecord
7
7
  @native_database_types ||= initialize_native_database_types.freeze
8
8
  end
9
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
10
  def create_table(table_name, comment: nil, **options)
40
11
  res = super
41
12
  clear_cache!
@@ -43,29 +14,50 @@ module ActiveRecord
43
14
  end
44
15
 
45
16
  def drop_table(table_name, options = {})
46
- if options[:if_exists] && @version_year != 2016
17
+ # Mimic CASCADE option as best we can.
18
+ if options[:force] == :cascade
19
+ execute_procedure(:sp_fkeys, pktable_name: table_name).each do |fkdata|
20
+ fktable = fkdata['FKTABLE_NAME']
21
+ fkcolmn = fkdata['FKCOLUMN_NAME']
22
+ pktable = fkdata['PKTABLE_NAME']
23
+ pkcolmn = fkdata['PKCOLUMN_NAME']
24
+ remove_foreign_key fktable, name: fkdata['FK_NAME']
25
+ do_execute "DELETE FROM #{quote_table_name(fktable)} WHERE #{quote_column_name(fkcolmn)} IN ( SELECT #{quote_column_name(pkcolmn)} FROM #{quote_table_name(pktable)} )"
26
+ end
27
+ end
28
+ if options[:if_exists] && @version_year < 2016
47
29
  execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}"
48
30
  else
49
31
  super
50
32
  end
51
33
  end
52
34
 
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
35
+ def indexes(table_name)
36
+ data = (select("EXEC sp_helpindex #{quote(table_name)}", "SCHEMA") || []) rescue [] # JDBC returns nil instead of an array or erring out for no results
55
37
  data.reduce([]) do |indexes, index|
56
38
  index = index.with_indifferent_access
39
+
57
40
  if index[:index_description] =~ /primary key/
58
41
  indexes
59
42
  else
60
43
  name = index[:index_name]
61
44
  unique = index[:index_description] =~ /unique/
62
45
  where = select_value("SELECT [filter_definition] FROM sys.indexes WHERE name = #{quote(name)}")
63
- columns = index[:index_keys].split(',').map do |column|
46
+ orders = {}
47
+ columns = []
48
+
49
+ index[:index_keys].split(',').each do |column|
64
50
  column.strip!
65
- column.gsub! '(-)', '' if column.ends_with?('(-)')
66
- column
51
+
52
+ if column.ends_with?('(-)')
53
+ column.gsub! '(-)', ''
54
+ orders[column] = :desc
55
+ end
56
+
57
+ columns << column
67
58
  end
68
- indexes << IndexDefinition.new(table_name, name, unique, columns, nil, nil, where)
59
+
60
+ indexes << IndexDefinition.new(table_name, name, unique, columns, where: where, orders: orders)
69
61
  end
70
62
  end
71
63
  end
@@ -103,10 +95,36 @@ module ActiveRecord
103
95
  end
104
96
 
105
97
  def primary_keys(table_name)
106
- primaries = schema_cache.columns(table_name).select(&:is_primary?).map(&:name)
98
+ primaries = primary_keys_select(table_name)
107
99
  primaries.present? ? primaries : identity_columns(table_name).map(&:name)
108
100
  end
109
101
 
102
+ def primary_keys_select(table_name)
103
+ identifier = database_prefix_identifier(table_name)
104
+ database = identifier.fully_qualified_database_quoted
105
+ sql = %{
106
+ SELECT KCU.COLUMN_NAME AS [name]
107
+ FROM #{database}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
108
+ LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
109
+ ON KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
110
+ AND KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
111
+ AND KCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG
112
+ AND KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
113
+ AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
114
+ WHERE KCU.TABLE_NAME = #{prepared_statements ? COLUMN_DEFINITION_BIND_STRING_0 : quote(identifier.object)}
115
+ AND KCU.TABLE_SCHEMA = #{identifier.schema.blank? ? 'schema_name()' : (prepared_statements ? COLUMN_DEFINITION_BIND_STRING_1 : quote(identifier.schema))}
116
+ AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
117
+ ORDER BY KCU.ORDINAL_POSITION ASC
118
+ }.gsub(/[[:space:]]/, ' ')
119
+ binds = []
120
+ if prepared_statements
121
+ nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
122
+ binds << Relation::QueryAttribute.new('TABLE_NAME', identifier.object, nv128)
123
+ binds << Relation::QueryAttribute.new('TABLE_SCHEMA', identifier.schema, nv128) unless identifier.schema.blank?
124
+ end
125
+ sp_executesql(sql, 'SCHEMA', binds).map { |r| r['name'] }
126
+ end
127
+
110
128
  def rename_table(table_name, new_name)
111
129
  do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
112
130
  rename_table_indexes(table_name, new_name)
@@ -124,22 +142,30 @@ module ActiveRecord
124
142
  sql_commands = []
125
143
  indexes = []
126
144
  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)
145
+ without_constraints = options.key?(:default) || options.key?(:limit)
146
+ default = if !options.key?(:default) && column_object
147
+ column_object.default
148
+ else
149
+ options[:default]
150
+ end
151
+ if without_constraints || (column_object && column_object.type != type.to_sym)
128
152
  remove_default_constraint(table_name, column_name)
129
153
  indexes = indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }
130
154
  remove_indexes(table_name, column_name)
131
155
  end
132
156
  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)}"
157
+ sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, limit: options[:limit], precision: options[:precision], scale: options[:scale])}"
158
+ sql_commands.last << ' NOT NULL' if !options[:null].nil? && options[:null] == false
159
+ if without_constraints
160
+ default = quote_default_expression(default, column_object || column_for(table_name, column_name))
161
+ sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{default} FOR #{quote_column_name(column_name)}"
137
162
  end
138
163
  # Add any removed indexes back
139
164
  indexes.each do |index|
140
165
  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
166
  end
142
167
  sql_commands.each { |c| do_execute(c) }
168
+ clear_cache!
143
169
  end
144
170
 
145
171
  def change_column_default(table_name, column_name, default_or_changes)
@@ -194,13 +220,14 @@ module ActiveRecord
194
220
  end
195
221
  end
196
222
 
197
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
223
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, **)
198
224
  type_limitable = %w(string integer float char nchar varchar nvarchar).include?(type.to_s)
199
225
  limit = nil unless type_limitable
200
226
  case type.to_s
201
227
  when 'integer'
202
228
  case limit
203
- when 1..2 then 'smallint'
229
+ when 1 then 'tinyint'
230
+ when 2 then 'smallint'
204
231
  when 3..4, nil then 'integer'
205
232
  when 5..8 then 'bigint'
206
233
  else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
@@ -226,7 +253,8 @@ module ActiveRecord
226
253
  s.gsub(/\s+(?:ASC|DESC)\b/i, '')
227
254
  .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
228
255
  }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
229
- [super, *order_columns].join(', ')
256
+
257
+ (order_columns << super).join(", ")
230
258
  end
231
259
 
232
260
  def update_table_definition(table_name, base)
@@ -236,23 +264,48 @@ module ActiveRecord
236
264
  def change_column_null(table_name, column_name, allow_null, default = nil)
237
265
  table_id = SQLServer::Utils.extract_identifiers(table_name)
238
266
  column_id = SQLServer::Utils.extract_identifiers(column_name)
239
- column = detect_column_for! table_name, column_name
267
+ column = column_for(table_name, column_name)
240
268
  if !allow_null.nil? && allow_null == false && !default.nil?
241
269
  do_execute("UPDATE #{table_id} SET #{column_id}=#{quote(default)} WHERE #{column_id} IS NULL")
242
270
  end
243
- sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
271
+ sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, limit: column.limit, precision: column.precision, scale: column.scale}"
244
272
  sql << ' NOT NULL' if !allow_null.nil? && allow_null == false
245
273
  do_execute sql
246
274
  end
247
275
 
276
+ def create_schema_dumper(options)
277
+ SQLServer::SchemaDumper.create(self, options)
278
+ end
279
+
280
+ private
248
281
 
249
- protected
282
+ def data_source_sql(name = nil, type: nil)
283
+ scope = quoted_scope name, type: type
284
+ table_name = lowercase_schema_reflection_sql 'TABLE_NAME'
285
+ sql = "SELECT #{table_name}"
286
+ sql << ' FROM INFORMATION_SCHEMA.TABLES WITH (NOLOCK)'
287
+ sql << ' WHERE TABLE_CATALOG = DB_NAME()'
288
+ sql << " AND TABLE_SCHEMA = #{quote(scope[:schema])}"
289
+ sql << " AND TABLE_NAME = #{quote(scope[:name])}" if scope[:name]
290
+ sql << " AND TABLE_TYPE = #{quote(scope[:type])}" if scope[:type]
291
+ sql << " ORDER BY #{table_name}"
292
+ sql
293
+ end
294
+
295
+ def quoted_scope(name = nil, type: nil)
296
+ identifier = SQLServer::Utils.extract_identifiers(name)
297
+ {}.tap do |scope|
298
+ scope[:schema] = identifier.schema || 'dbo'
299
+ scope[:name] = identifier.object if identifier.object
300
+ scope[:type] = type if type
301
+ end
302
+ end
250
303
 
251
304
  # === SQLServer Specific ======================================== #
252
305
 
253
306
  def initialize_native_database_types
254
307
  {
255
- primary_key: 'int NOT NULL IDENTITY(1,1) PRIMARY KEY',
308
+ primary_key: 'bigint NOT NULL IDENTITY(1,1) PRIMARY KEY',
256
309
  primary_key_nonclustered: 'int NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED',
257
310
  integer: { name: 'int', limit: 4 },
258
311
  bigint: { name: 'bigint' },
@@ -286,21 +339,11 @@ module ActiveRecord
286
339
  }
287
340
  end
288
341
 
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
342
  COLUMN_DEFINITION_BIND_STRING_0 = defined?(JRUBY_VERSION) ? '?' : '@0'
296
343
  COLUMN_DEFINITION_BIND_STRING_1 = defined?(JRUBY_VERSION) ? '?' : '@1'
297
344
 
298
345
  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
346
+ identifier = database_prefix_identifier(table_name)
304
347
  database = identifier.fully_qualified_database_quoted
305
348
  view_exists = view_exists?(table_name)
306
349
  view_tblnm = view_table_name(table_name) if view_exists
@@ -386,11 +429,13 @@ module ActiveRecord
386
429
  ci[:default_function] = begin
387
430
  default = ci[:default_value]
388
431
  if default.nil? && view_exists
389
- default = select_value "
432
+ default = select_value %{
390
433
  SELECT c.COLUMN_DEFAULT
391
434
  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'
435
+ WHERE
436
+ c.TABLE_NAME = '#{view_tblnm}'
437
+ AND c.COLUMN_NAME = '#{views_real_column_name(table_name, ci[:name])}'
438
+ }.squish, 'SCHEMA'
394
439
  end
395
440
  case default
396
441
  when nil
@@ -409,7 +454,7 @@ module ActiveRecord
409
454
  else ci[:type]
410
455
  end
411
456
  value = default.match(/\A\((.*)\)\Z/m)[1]
412
- value = select_value "SELECT CAST(#{value} AS #{type}) AS value", 'SCHEMA'
457
+ value = select_value("SELECT CAST(#{value} AS #{type}) AS value", 'SCHEMA')
413
458
  [value, nil]
414
459
  end
415
460
  end
@@ -460,13 +505,6 @@ module ActiveRecord
460
505
  "DF_#{table_name}_#{column_name}"
461
506
  end
462
507
 
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
508
  def lowercase_schema_reflection_sql(node)
471
509
  lowercase_schema_reflection ? "LOWER(#{node})" : node
472
510
  end
@@ -482,7 +520,7 @@ module ActiveRecord
482
520
  @view_information ||= {}
483
521
  @view_information[table_name] ||= begin
484
522
  identifier = SQLServer::Utils.extract_identifiers(table_name)
485
- view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{identifier.object}'", 'SCHEMA'
523
+ view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", 'SCHEMA'
486
524
  if view_info
487
525
  view_info = view_info.with_indifferent_access
488
526
  if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
@@ -505,8 +543,6 @@ module ActiveRecord
505
543
  match_data ? match_data[1] : column_name
506
544
  end
507
545
 
508
- private
509
-
510
546
  def create_table_definition(*args)
511
547
  SQLServer::TableDefinition.new(*args)
512
548
  end