activerecord-sqlserver-adapter 5.2.0 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/issue_template.md +23 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +29 -0
  6. data/.travis.yml +6 -8
  7. data/CHANGELOG.md +46 -11
  8. data/{Dockerfile → Dockerfile.ci} +2 -2
  9. data/Gemfile +48 -41
  10. data/Guardfile +9 -8
  11. data/README.md +9 -37
  12. data/RUNNING_UNIT_TESTS.md +3 -0
  13. data/Rakefile +14 -16
  14. data/VERSION +1 -1
  15. data/activerecord-sqlserver-adapter.gemspec +25 -14
  16. data/appveyor.yml +24 -17
  17. data/docker-compose.ci.yml +7 -5
  18. data/guides/RELEASING.md +11 -0
  19. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -4
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +3 -4
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +22 -2
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +3 -3
  23. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
  24. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +44 -0
  25. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +36 -0
  26. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +28 -0
  27. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -0
  28. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +88 -44
  29. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +10 -12
  30. data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -3
  31. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +46 -8
  32. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +16 -5
  33. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -7
  34. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +197 -165
  35. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +8 -8
  36. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +4 -2
  37. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +3 -1
  38. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -2
  39. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +43 -44
  40. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +7 -9
  41. data/lib/active_record/connection_adapters/sqlserver/type.rb +37 -35
  42. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -3
  43. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -4
  44. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -3
  45. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +7 -4
  46. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +2 -2
  47. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +4 -3
  48. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +8 -8
  49. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -2
  50. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -2
  51. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -4
  52. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -3
  53. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
  54. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
  55. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
  56. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
  57. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
  58. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
  59. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
  60. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
  61. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
  62. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
  63. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
  64. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
  65. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
  66. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
  68. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
  69. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
  70. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
  71. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
  72. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
  73. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
  74. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
  75. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
  76. data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -11
  77. data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
  78. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +132 -92
  79. data/lib/active_record/connection_adapters/sqlserver_column.rb +9 -5
  80. data/lib/active_record/sqlserver_base.rb +9 -1
  81. data/lib/active_record/tasks/sqlserver_database_tasks.rb +28 -32
  82. data/lib/activerecord-sqlserver-adapter.rb +3 -1
  83. data/lib/arel/visitors/sqlserver.rb +58 -24
  84. data/lib/arel_sqlserver.rb +4 -2
  85. data/test/appveyor/dbsetup.ps1 +4 -4
  86. data/test/cases/adapter_test_sqlserver.rb +223 -180
  87. data/test/cases/change_column_null_test_sqlserver.rb +17 -15
  88. data/test/cases/coerced_tests.rb +654 -360
  89. data/test/cases/column_test_sqlserver.rb +635 -604
  90. data/test/cases/connection_test_sqlserver.rb +18 -21
  91. data/test/cases/execute_procedure_test_sqlserver.rb +20 -20
  92. data/test/cases/fetch_test_sqlserver.rb +17 -23
  93. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
  94. data/test/cases/helper_sqlserver.rb +20 -15
  95. data/test/cases/in_clause_test_sqlserver.rb +36 -0
  96. data/test/cases/index_test_sqlserver.rb +15 -15
  97. data/test/cases/json_test_sqlserver.rb +25 -25
  98. data/test/cases/migration_test_sqlserver.rb +30 -26
  99. data/test/cases/order_test_sqlserver.rb +53 -54
  100. data/test/cases/pessimistic_locking_test_sqlserver.rb +31 -37
  101. data/test/cases/rake_test_sqlserver.rb +44 -56
  102. data/test/cases/schema_dumper_test_sqlserver.rb +117 -112
  103. data/test/cases/schema_test_sqlserver.rb +20 -26
  104. data/test/cases/scratchpad_test_sqlserver.rb +4 -4
  105. data/test/cases/showplan_test_sqlserver.rb +32 -39
  106. data/test/cases/specific_schema_test_sqlserver.rb +75 -72
  107. data/test/cases/transaction_test_sqlserver.rb +27 -29
  108. data/test/cases/trigger_test_sqlserver.rb +18 -17
  109. data/test/cases/utils_test_sqlserver.rb +78 -78
  110. data/test/cases/uuid_test_sqlserver.rb +19 -20
  111. data/test/debug.rb +8 -6
  112. data/test/migrations/create_clients_and_change_column_null.rb +3 -1
  113. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +4 -4
  114. data/test/models/sqlserver/booking.rb +3 -1
  115. data/test/models/sqlserver/customers_view.rb +3 -1
  116. data/test/models/sqlserver/datatype.rb +2 -0
  117. data/test/models/sqlserver/datatype_migration.rb +2 -0
  118. data/test/models/sqlserver/dollar_table_name.rb +3 -1
  119. data/test/models/sqlserver/edge_schema.rb +3 -3
  120. data/test/models/sqlserver/fk_has_fk.rb +3 -1
  121. data/test/models/sqlserver/fk_has_pk.rb +3 -1
  122. data/test/models/sqlserver/natural_pk_data.rb +4 -2
  123. data/test/models/sqlserver/natural_pk_int_data.rb +3 -1
  124. data/test/models/sqlserver/no_pk_data.rb +3 -1
  125. data/test/models/sqlserver/object_default.rb +3 -1
  126. data/test/models/sqlserver/quoted_table.rb +4 -2
  127. data/test/models/sqlserver/quoted_view_1.rb +3 -1
  128. data/test/models/sqlserver/quoted_view_2.rb +3 -1
  129. data/test/models/sqlserver/sst_memory.rb +3 -1
  130. data/test/models/sqlserver/string_default.rb +3 -1
  131. data/test/models/sqlserver/string_defaults_big_view.rb +3 -1
  132. data/test/models/sqlserver/string_defaults_view.rb +3 -1
  133. data/test/models/sqlserver/tinyint_pk.rb +3 -1
  134. data/test/models/sqlserver/trigger.rb +4 -2
  135. data/test/models/sqlserver/trigger_history.rb +3 -1
  136. data/test/models/sqlserver/upper.rb +3 -1
  137. data/test/models/sqlserver/uppered.rb +3 -1
  138. data/test/models/sqlserver/uuid.rb +3 -1
  139. data/test/schema/datatypes/2012.sql +1 -0
  140. data/test/schema/sqlserver_specific_schema.rb +31 -21
  141. data/test/support/coerceable_test_sqlserver.rb +15 -9
  142. data/test/support/connection_reflection.rb +3 -2
  143. data/test/support/core_ext/query_cache.rb +4 -1
  144. data/test/support/load_schema_sqlserver.rb +5 -5
  145. data/test/support/minitest_sqlserver.rb +3 -1
  146. data/test/support/paths_sqlserver.rb +11 -11
  147. data/test/support/rake_helpers.rb +13 -10
  148. data/test/support/sql_counter_sqlserver.rb +3 -4
  149. data/test/support/test_in_memory_oltp.rb +9 -7
  150. metadata +23 -13
  151. data/BACKERS.md +0 -32
  152. data/circle.yml +0 -38
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
4
6
  module DatabaseTasks
5
-
6
7
  def create_database(database, options = {})
7
8
  name = SQLServer::Utils.extract_identifiers(database)
8
9
  db_options = create_database_options(options)
@@ -12,11 +13,12 @@ module ActiveRecord
12
13
 
13
14
  def drop_database(database)
14
15
  name = SQLServer::Utils.extract_identifiers(database)
16
+ do_execute "ALTER DATABASE #{name} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
15
17
  do_execute "DROP DATABASE #{name}"
16
18
  end
17
19
 
18
20
  def current_database
19
- select_value 'SELECT DB_NAME()'
21
+ select_value "SELECT DB_NAME()"
20
22
  end
21
23
 
22
24
  def charset
@@ -29,20 +31,20 @@ module ActiveRecord
29
31
 
30
32
  private
31
33
 
32
- def create_database_options(options={})
34
+ def create_database_options(options = {})
33
35
  keys = [:collate]
34
36
  copts = @connection_options
35
37
  options = {
36
38
  collate: copts[:collation]
37
39
  }.merge(options.symbolize_keys).select { |_, v|
38
40
  v.present?
39
- }.slice(*keys).map { |k,v|
41
+ }.slice(*keys).map { |k, v|
40
42
  "#{k.to_s.upcase} #{v}"
41
- }.join(' ')
43
+ }.join(" ")
42
44
  options
43
45
  end
44
46
 
45
- def create_database_edition_options(options={})
47
+ def create_database_edition_options(options = {})
46
48
  keys = [:maxsize, :edition, :service_objective]
47
49
  copts = @connection_options
48
50
  edition_options = {
@@ -51,17 +53,13 @@ module ActiveRecord
51
53
  service_objective: copts[:azure_service_objective]
52
54
  }.merge(options.symbolize_keys).select { |_, v|
53
55
  v.present?
54
- }.slice(*keys).map { |k,v|
56
+ }.slice(*keys).map { |k, v|
55
57
  "#{k.to_s.upcase} = #{v}"
56
- }.join(', ')
58
+ }.join(", ")
57
59
  edition_options = "( #{edition_options} )" if edition_options.present?
58
60
  edition_options
59
61
  end
60
-
61
62
  end
62
63
  end
63
64
  end
64
65
  end
65
-
66
-
67
-
@@ -1,7 +1,6 @@
1
- module ActiveRecord
1
+ # frozen_string_literal: true
2
2
 
3
+ module ActiveRecord
3
4
  class DeadlockVictim < WrappedDatabaseException
4
5
  end
5
-
6
-
7
6
  end
@@ -1,11 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
4
6
  module Quoting
5
-
6
- QUOTED_TRUE = '1'.freeze
7
- QUOTED_FALSE = '0'.freeze
8
- QUOTED_STRING_PREFIX = 'N'.freeze
7
+ QUOTED_TRUE = "1".freeze
8
+ QUOTED_FALSE = "0".freeze
9
+ QUOTED_STRING_PREFIX = "N".freeze
9
10
 
10
11
  def fetch_type_metadata(sql_type, sqlserver_options = {})
11
12
  cast_type = lookup_cast_type(sql_type)
@@ -61,13 +62,51 @@ module ActiveRecord
61
62
  end
62
63
 
63
64
  def quoted_date(value)
64
- if value.acts_like?(:date)
65
- Type::Date.new.serialize(value)
66
- else value.acts_like?(:time)
65
+ if value.acts_like?(:time)
67
66
  Type::DateTime.new.serialize(value)
67
+ elsif value.acts_like?(:date)
68
+ Type::Date.new.serialize(value)
69
+ else
70
+ value
68
71
  end
69
72
  end
70
73
 
74
+ def column_name_matcher
75
+ COLUMN_NAME
76
+ end
77
+
78
+ def column_name_with_order_matcher
79
+ COLUMN_NAME_WITH_ORDER
80
+ end
81
+
82
+ COLUMN_NAME = /
83
+ \A
84
+ (
85
+ (?:
86
+ # [table_name].[column_name] | function(one or no argument)
87
+ ((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
88
+ )
89
+ (?:\s+AS\s+(?:\w+|\[\w+\]))?
90
+ )
91
+ (?:\s*,\s*\g<1>)*
92
+ \z
93
+ /ix
94
+
95
+ COLUMN_NAME_WITH_ORDER = /
96
+ \A
97
+ (
98
+ (?:
99
+ # [table_name].[column_name] | function(one or no argument)
100
+ ((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
101
+ )
102
+ (?:\s+ASC|\s+DESC)?
103
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
104
+ )
105
+ (?:\s*,\s*\g<1>)*
106
+ \z
107
+ /ix
108
+
109
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
71
110
 
72
111
  private
73
112
 
@@ -92,7 +131,6 @@ module ActiveRecord
92
131
  super
93
132
  end
94
133
  end
95
-
96
134
  end
97
135
  end
98
136
  end
@@ -1,20 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
4
6
  class SchemaCreation < AbstractAdapter::SchemaCreation
5
-
6
7
  private
7
8
 
8
9
  def visit_TableDefinition(o)
10
+ if_not_exists = o.if_not_exists
11
+
9
12
  if o.as
10
13
  table_name = quote_table_name(o.temporary ? "##{o.name}" : o.name)
11
14
  query = o.as.respond_to?(:to_sql) ? o.as.to_sql : o.as
12
15
  projections, source = query.match(%r{SELECT\s+(.*)?\s+FROM\s+(.*)?}).captures
13
- select_into = "SELECT #{projections} INTO #{table_name} FROM #{source}"
16
+ sql = "SELECT #{projections} INTO #{table_name} FROM #{source}"
14
17
  else
15
18
  o.instance_variable_set :@as, nil
16
- super
19
+ o.instance_variable_set :@if_not_exists, false
20
+ sql = super
17
21
  end
22
+
23
+ if if_not_exists
24
+ o.instance_variable_set :@if_not_exists, true
25
+ table_name = o.temporary ? "##{o.name}" : o.name
26
+ sql = "IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='#{table_name}' and xtype='U') #{sql}"
27
+ end
28
+
29
+ sql
18
30
  end
19
31
 
20
32
  def add_column_options!(sql, options)
@@ -34,7 +46,7 @@ module ActiveRecord
34
46
  def action_sql(action, dependency)
35
47
  case dependency
36
48
  when :restrict
37
- raise ArgumentError, <<-MSG.strip_heredoc
49
+ raise ArgumentError, <<~MSG.squish
38
50
  '#{dependency}' is not supported for :on_update or :on_delete.
39
51
  Supported values are: :nullify, :cascade
40
52
  MSG
@@ -50,7 +62,6 @@ module ActiveRecord
50
62
  def options_primary_key_with_nil_default?(options)
51
63
  options[:primary_key] && options.include?(:default) && options[:default].nil?
52
64
  end
53
-
54
65
  end
55
66
  end
56
67
  end
@@ -1,14 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
4
6
  class SchemaDumper < ConnectionAdapters::SchemaDumper
5
-
6
7
  SQLSEVER_NO_LIMIT_TYPES = [
7
- 'text',
8
- 'ntext',
9
- 'varchar(max)',
10
- 'nvarchar(max)',
11
- 'varbinary(max)'
8
+ "text",
9
+ "ntext",
10
+ "varchar(max)",
11
+ "nvarchar(max)",
12
+ "varbinary(max)"
12
13
  ].freeze
13
14
 
14
15
  private
@@ -19,18 +20,19 @@ module ActiveRecord
19
20
 
20
21
  def schema_limit(column)
21
22
  return if SQLSEVER_NO_LIMIT_TYPES.include?(column.sql_type)
23
+
22
24
  super
23
25
  end
24
26
 
25
27
  def schema_collation(column)
26
28
  return unless column.collation
29
+
27
30
  column.collation if column.collation != @connection.collation
28
31
  end
29
32
 
30
33
  def default_primary_key?(column)
31
34
  super && column.is_primary? && column.is_identity?
32
35
  end
33
-
34
36
  end
35
37
  end
36
38
  end
@@ -1,27 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
4
6
  module SchemaStatements
5
-
6
7
  def native_database_types
7
8
  @native_database_types ||= initialize_native_database_types.freeze
8
9
  end
9
10
 
10
- def create_table(table_name, comment: nil, **options)
11
+ def create_table(table_name, **options)
11
12
  res = super
12
13
  clear_cache!
13
14
  res
14
15
  end
15
16
 
16
- def drop_table(table_name, options = {})
17
+ def drop_table(table_name, **options)
17
18
  # Mimic CASCADE option as best we can.
18
19
  if options[:force] == :cascade
19
20
  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']
21
+ fktable = fkdata["FKTABLE_NAME"]
22
+ fkcolmn = fkdata["FKCOLUMN_NAME"]
23
+ pktable = fkdata["PKTABLE_NAME"]
24
+ pkcolmn = fkdata["PKCOLUMN_NAME"]
25
+ remove_foreign_key fktable, name: fkdata["FK_NAME"]
25
26
  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
27
  end
27
28
  end
@@ -47,11 +48,11 @@ module ActiveRecord
47
48
  orders = {}
48
49
  columns = []
49
50
 
50
- index[:index_keys].split(',').each do |column|
51
+ index[:index_keys].split(",").each do |column|
51
52
  column.strip!
52
53
 
53
- if column.ends_with?('(-)')
54
- column.gsub! '(-)', ''
54
+ if column.ends_with?("(-)")
55
+ column.gsub! "(-)", ""
55
56
  orders[column] = :desc
56
57
  end
57
58
 
@@ -65,15 +66,15 @@ module ActiveRecord
65
66
 
66
67
  def columns(table_name)
67
68
  return [] if table_name.blank?
69
+
68
70
  column_definitions(table_name).map do |ci|
69
- sqlserver_options = ci.slice :ordinal_position, :is_primary, :is_identity
71
+ sqlserver_options = ci.slice :ordinal_position, :is_primary, :is_identity, :table_name
70
72
  sql_type_metadata = fetch_type_metadata ci[:type], sqlserver_options
71
73
  new_column(
72
74
  ci[:name],
73
75
  ci[:default_value],
74
76
  sql_type_metadata,
75
77
  ci[:null],
76
- ci[:table_name],
77
78
  ci[:default_function],
78
79
  ci[:collation],
79
80
  nil,
@@ -82,16 +83,16 @@ module ActiveRecord
82
83
  end
83
84
  end
84
85
 
85
- def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil, comment = nil, sqlserver_options = {})
86
+ def new_column(name, default, sql_type_metadata, null, default_function = nil, collation = nil, comment = nil, sqlserver_options = {})
86
87
  SQLServerColumn.new(
87
88
  name,
88
89
  default,
89
90
  sql_type_metadata,
90
- null, table_name,
91
+ null,
91
92
  default_function,
92
- collation,
93
- comment,
94
- sqlserver_options
93
+ collation: collation,
94
+ comment: comment,
95
+ **sqlserver_options
95
96
  )
96
97
  end
97
98
 
@@ -104,7 +105,7 @@ module ActiveRecord
104
105
  identifier = database_prefix_identifier(table_name)
105
106
  database = identifier.fully_qualified_database_quoted
106
107
  sql = %{
107
- SELECT KCU.COLUMN_NAME AS [name]
108
+ SELECT #{lowercase_schema_reflection_sql('KCU.COLUMN_NAME')} AS [name]
108
109
  FROM #{database}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
109
110
  LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
110
111
  ON KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
@@ -116,12 +117,12 @@ module ActiveRecord
116
117
  AND KCU.TABLE_SCHEMA = #{identifier.schema.blank? ? 'schema_name()' : (prepared_statements ? '@1' : quote(identifier.schema))}
117
118
  AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
118
119
  ORDER BY KCU.ORDINAL_POSITION ASC
119
- }.gsub(/[[:space:]]/, ' ')
120
+ }.gsub(/[[:space:]]/, " ")
120
121
  binds = []
121
122
  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
- sp_executesql(sql, 'SCHEMA', binds).map { |r| r['name'] }
123
+ binds << Relation::QueryAttribute.new("TABLE_NAME", identifier.object, nv128)
124
+ binds << Relation::QueryAttribute.new("TABLE_SCHEMA", identifier.schema, nv128) unless identifier.schema.blank?
125
+ sp_executesql(sql, "SCHEMA", binds).map { |r| r["name"] }
125
126
  end
126
127
 
127
128
  def rename_table(table_name, new_name)
@@ -130,7 +131,8 @@ module ActiveRecord
130
131
  end
131
132
 
132
133
  def remove_column(table_name, column_name, type = nil, options = {})
133
- raise ArgumentError.new('You must specify at least one column name. Example: remove_column(:people, :first_name)') if column_name.is_a? Array
134
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_name.is_a? Array
135
+
134
136
  remove_check_constraints(table_name, column_name)
135
137
  remove_default_constraint(table_name, column_name)
136
138
  remove_indexes(table_name, column_name)
@@ -141,18 +143,24 @@ module ActiveRecord
141
143
  sql_commands = []
142
144
  indexes = []
143
145
  column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s }
144
- if options_include_default?(options) || (column_object && column_object.type != type.to_sym)
146
+ without_constraints = options.key?(:default) || options.key?(:limit)
147
+ default = if !options.key?(:default) && column_object
148
+ column_object.default
149
+ else
150
+ options[:default]
151
+ end
152
+ if without_constraints || (column_object && column_object.type != type.to_sym)
145
153
  remove_default_constraint(table_name, column_name)
146
154
  indexes = indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }
147
155
  remove_indexes(table_name, column_name)
148
156
  end
149
157
  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?
150
- 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])}"
151
- sql_commands.last << ' NOT NULL' if !options[:null].nil? && options[:null] == false
152
- if options.key?(:default) && default_constraint_name(table_name, column_name).present?
153
- change_column_default(table_name, column_name, options[:default])
154
- elsif options_include_default?(options)
155
- 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)}"
158
+ alter_command = "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])}"
159
+ alter_command += " NOT NULL" if !options[:null].nil? && options[:null] == false
160
+ sql_commands << alter_command
161
+ if without_constraints
162
+ default = quote_default_expression(default, column_object || column_for(table_name, column_name))
163
+ 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)}"
156
164
  end
157
165
  # Add any removed indexes back
158
166
  indexes.each do |index|
@@ -166,6 +174,7 @@ module ActiveRecord
166
174
  clear_cache!
167
175
  column = column_for(table_name, column_name)
168
176
  return unless column
177
+
169
178
  remove_default_constraint(table_name, column_name)
170
179
  default = extract_new_default_value(default_or_changes)
171
180
  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)}"
@@ -175,15 +184,16 @@ module ActiveRecord
175
184
  def rename_column(table_name, column_name, new_column_name)
176
185
  clear_cache!
177
186
  identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{column_name}")
178
- execute_procedure :sp_rename, identifier.quoted, new_column_name, 'COLUMN'
187
+ execute_procedure :sp_rename, identifier.quoted, new_column_name, "COLUMN"
179
188
  rename_column_indexes(table_name, column_name, new_column_name)
180
189
  clear_cache!
181
190
  end
182
191
 
183
192
  def rename_index(table_name, old_name, new_name)
184
193
  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
194
+
185
195
  identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{old_name}")
186
- execute_procedure :sp_rename, identifier.quoted, new_name, 'INDEX'
196
+ execute_procedure :sp_rename, identifier.quoted, new_name, "INDEX"
187
197
  end
188
198
 
189
199
  def remove_index!(table_name, index_name)
@@ -195,13 +205,13 @@ module ActiveRecord
195
205
  fk_info = execute_procedure :sp_fkeys, nil, identifier.schema, nil, identifier.object, identifier.schema
196
206
  fk_info.map do |row|
197
207
  from_table = identifier.object
198
- to_table = row['PKTABLE_NAME']
208
+ to_table = row["PKTABLE_NAME"]
199
209
  options = {
200
- name: row['FK_NAME'],
201
- column: row['FKCOLUMN_NAME'],
202
- primary_key: row['PKCOLUMN_NAME'],
203
- on_update: extract_foreign_key_action('update', row['FK_NAME']),
204
- on_delete: extract_foreign_key_action('delete', row['FK_NAME'])
210
+ name: row["FK_NAME"],
211
+ column: row["FKCOLUMN_NAME"],
212
+ primary_key: row["PKCOLUMN_NAME"],
213
+ on_update: extract_foreign_key_action("update", row["FK_NAME"]),
214
+ on_delete: extract_foreign_key_action("delete", row["FK_NAME"])
205
215
  }
206
216
  ForeignKeyDefinition.new from_table, to_table, options
207
217
  end
@@ -209,8 +219,8 @@ module ActiveRecord
209
219
 
210
220
  def extract_foreign_key_action(action, fk_name)
211
221
  case select_value("SELECT #{action}_referential_action_desc FROM sys.foreign_keys WHERE name = '#{fk_name}'")
212
- when 'CASCADE' then :cascade
213
- when 'SET_NULL' then :nullify
222
+ when "CASCADE" then :cascade
223
+ when "SET_NULL" then :nullify
214
224
  end
215
225
  end
216
226
 
@@ -218,21 +228,21 @@ module ActiveRecord
218
228
  type_limitable = %w(string integer float char nchar varchar nvarchar).include?(type.to_s)
219
229
  limit = nil unless type_limitable
220
230
  case type.to_s
221
- when 'integer'
231
+ when "integer"
222
232
  case limit
223
- when 1 then 'tinyint'
224
- when 2 then 'smallint'
225
- when 3..4, nil then 'integer'
226
- when 5..8 then 'bigint'
233
+ when 1 then "tinyint"
234
+ when 2 then "smallint"
235
+ when 3..4, nil then "integer"
236
+ when 5..8 then "bigint"
227
237
  else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
228
238
  end
229
- when 'datetime2'
239
+ when "datetime2"
230
240
  column_type_sql = super
231
241
  if precision
232
242
  if (0..7) === precision
233
243
  column_type_sql << "(#{precision})"
234
244
  else
235
- raise(ActiveRecordError, "The dattime2 type has precision of #{precision}. The allowed range of precision is from 0 to 7")
245
+ raise(ActiveRecordError, "The datetime2 type has precision of #{precision}. The allowed range of precision is from 0 to 7")
236
246
  end
237
247
  end
238
248
  column_type_sql
@@ -242,11 +252,11 @@ module ActiveRecord
242
252
  end
243
253
 
244
254
  def columns_for_distinct(columns, orders)
245
- order_columns = orders.reject(&:blank?).map{ |s|
246
- s = s.to_sql unless s.is_a?(String)
247
- s.gsub(/\s+(?:ASC|DESC)\b/i, '')
248
- .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
249
- }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
255
+ order_columns = orders.reject(&:blank?).map { |s|
256
+ s = s.to_sql unless s.is_a?(String)
257
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
258
+ .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
259
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
250
260
 
251
261
  (order_columns << super).join(", ")
252
262
  end
@@ -263,7 +273,7 @@ module ActiveRecord
263
273
  do_execute("UPDATE #{table_id} SET #{column_id}=#{quote(default)} WHERE #{column_id} IS NULL")
264
274
  end
265
275
  sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, limit: column.limit, precision: column.precision, scale: column.scale}"
266
- sql << ' NOT NULL' if !allow_null.nil? && allow_null == false
276
+ sql += " NOT NULL" if !allow_null.nil? && allow_null == false
267
277
  do_execute sql
268
278
  end
269
279
 
@@ -275,21 +285,21 @@ module ActiveRecord
275
285
 
276
286
  def data_source_sql(name = nil, type: nil)
277
287
  scope = quoted_scope name, type: type
278
- table_name = lowercase_schema_reflection_sql 'TABLE_NAME'
288
+ table_name = lowercase_schema_reflection_sql "TABLE_NAME"
279
289
  sql = "SELECT #{table_name}"
280
- sql << ' FROM INFORMATION_SCHEMA.TABLES WITH (NOLOCK)'
281
- sql << ' WHERE TABLE_CATALOG = DB_NAME()'
282
- sql << " AND TABLE_SCHEMA = #{quote(scope[:schema])}"
283
- sql << " AND TABLE_NAME = #{quote(scope[:name])}" if scope[:name]
284
- sql << " AND TABLE_TYPE = #{quote(scope[:type])}" if scope[:type]
285
- sql << " ORDER BY #{table_name}"
290
+ sql += " FROM INFORMATION_SCHEMA.TABLES WITH (NOLOCK)"
291
+ sql += " WHERE TABLE_CATALOG = DB_NAME()"
292
+ sql += " AND TABLE_SCHEMA = #{quote(scope[:schema])}"
293
+ sql += " AND TABLE_NAME = #{quote(scope[:name])}" if scope[:name]
294
+ sql += " AND TABLE_TYPE = #{quote(scope[:type])}" if scope[:type]
295
+ sql += " ORDER BY #{table_name}"
286
296
  sql
287
297
  end
288
298
 
289
299
  def quoted_scope(name = nil, type: nil)
290
300
  identifier = SQLServer::Utils.extract_identifiers(name)
291
301
  {}.tap do |scope|
292
- scope[:schema] = identifier.schema || 'dbo'
302
+ scope[:schema] = identifier.schema || "dbo"
293
303
  scope[:name] = identifier.object if identifier.object
294
304
  scope[:type] = type if type
295
305
  end
@@ -299,37 +309,37 @@ module ActiveRecord
299
309
 
300
310
  def initialize_native_database_types
301
311
  {
302
- primary_key: 'bigint NOT NULL IDENTITY(1,1) PRIMARY KEY',
303
- primary_key_nonclustered: 'int NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED',
304
- integer: { name: 'int', limit: 4 },
305
- bigint: { name: 'bigint' },
306
- boolean: { name: 'bit' },
307
- decimal: { name: 'decimal' },
308
- money: { name: 'money' },
309
- smallmoney: { name: 'smallmoney' },
310
- float: { name: 'float' },
311
- real: { name: 'real' },
312
- date: { name: 'date' },
313
- datetime: { name: 'datetime' },
314
- datetime2: { name: 'datetime2' },
315
- datetimeoffset: { name: 'datetimeoffset' },
316
- smalldatetime: { name: 'smalldatetime' },
317
- timestamp: { name: 'datetime' },
318
- time: { name: 'time' },
319
- char: { name: 'char' },
320
- varchar: { name: 'varchar', limit: 8000 },
321
- varchar_max: { name: 'varchar(max)' },
322
- text_basic: { name: 'text' },
323
- nchar: { name: 'nchar' },
324
- string: { name: 'nvarchar', limit: 4000 },
325
- text: { name: 'nvarchar(max)' },
326
- ntext: { name: 'ntext' },
327
- binary_basic: { name: 'binary' },
328
- varbinary: { name: 'varbinary', limit: 8000 },
329
- binary: { name: 'varbinary(max)' },
330
- uuid: { name: 'uniqueidentifier' },
331
- ss_timestamp: { name: 'timestamp' },
332
- json: { name: 'nvarchar(max)' }
312
+ primary_key: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY",
313
+ primary_key_nonclustered: "int NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED",
314
+ integer: { name: "int", limit: 4 },
315
+ bigint: { name: "bigint" },
316
+ boolean: { name: "bit" },
317
+ decimal: { name: "decimal" },
318
+ money: { name: "money" },
319
+ smallmoney: { name: "smallmoney" },
320
+ float: { name: "float" },
321
+ real: { name: "real" },
322
+ date: { name: "date" },
323
+ datetime: { name: "datetime" },
324
+ datetime2: { name: "datetime2" },
325
+ datetimeoffset: { name: "datetimeoffset" },
326
+ smalldatetime: { name: "smalldatetime" },
327
+ timestamp: { name: "datetime" },
328
+ time: { name: "time" },
329
+ char: { name: "char" },
330
+ varchar: { name: "varchar", limit: 8000 },
331
+ varchar_max: { name: "varchar(max)" },
332
+ text_basic: { name: "text" },
333
+ nchar: { name: "nchar" },
334
+ string: { name: "nvarchar", limit: 4000 },
335
+ text: { name: "nvarchar(max)" },
336
+ ntext: { name: "ntext" },
337
+ binary_basic: { name: "binary" },
338
+ varbinary: { name: "varbinary", limit: 8000 },
339
+ binary: { name: "varbinary(max)" },
340
+ uuid: { name: "uniqueidentifier" },
341
+ ss_timestamp: { name: "timestamp" },
342
+ json: { name: "nvarchar(max)" }
333
343
  }
334
344
  end
335
345
 
@@ -338,60 +348,14 @@ module ActiveRecord
338
348
  database = identifier.fully_qualified_database_quoted
339
349
  view_exists = view_exists?(table_name)
340
350
  view_tblnm = view_table_name(table_name) if view_exists
341
- sql = %{
342
- SELECT DISTINCT
343
- #{lowercase_schema_reflection_sql('columns.TABLE_NAME')} AS table_name,
344
- #{lowercase_schema_reflection_sql('columns.COLUMN_NAME')} AS name,
345
- columns.DATA_TYPE AS type,
346
- columns.COLUMN_DEFAULT AS default_value,
347
- columns.NUMERIC_SCALE AS numeric_scale,
348
- columns.NUMERIC_PRECISION AS numeric_precision,
349
- columns.DATETIME_PRECISION AS datetime_precision,
350
- columns.COLLATION_NAME AS [collation],
351
- columns.ordinal_position,
352
- CASE
353
- WHEN columns.DATA_TYPE IN ('nchar','nvarchar','char','varchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
354
- ELSE COL_LENGTH('#{database}.'+columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
355
- END AS [length],
356
- CASE
357
- WHEN columns.IS_NULLABLE = 'YES' THEN 1
358
- ELSE NULL
359
- END AS [is_nullable],
360
- CASE
361
- WHEN KCU.COLUMN_NAME IS NOT NULL AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY' THEN 1
362
- ELSE NULL
363
- END AS [is_primary],
364
- c.is_identity AS [is_identity]
365
- FROM #{database}.INFORMATION_SCHEMA.COLUMNS columns
366
- LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
367
- ON TC.TABLE_NAME = columns.TABLE_NAME
368
- AND TC.TABLE_SCHEMA = columns.TABLE_SCHEMA
369
- AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
370
- LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
371
- ON KCU.COLUMN_NAME = columns.COLUMN_NAME
372
- AND KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
373
- AND KCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG
374
- AND KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
375
- INNER JOIN #{database}.sys.schemas AS s
376
- ON s.name = columns.TABLE_SCHEMA
377
- AND s.schema_id = s.schema_id
378
- INNER JOIN #{database}.sys.objects AS o
379
- ON s.schema_id = o.schema_id
380
- AND o.is_ms_shipped = 0
381
- AND o.type IN ('U', 'V')
382
- AND o.name = columns.TABLE_NAME
383
- INNER JOIN #{database}.sys.columns AS c
384
- ON o.object_id = c.object_id
385
- AND c.name = columns.COLUMN_NAME
386
- WHERE columns.TABLE_NAME = #{prepared_statements ? '@0' : quote(identifier.object)}
387
- AND columns.TABLE_SCHEMA = #{identifier.schema.blank? ? 'schema_name()' : (prepared_statements ? '@1' : quote(identifier.schema))}
388
- ORDER BY columns.ordinal_position
389
- }.gsub(/[ \t\r\n]+/, ' ').strip
351
+
352
+ sql = column_definitions_sql(database, identifier)
353
+
390
354
  binds = []
391
355
  nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
392
- binds << Relation::QueryAttribute.new('TABLE_NAME', identifier.object, nv128)
393
- binds << Relation::QueryAttribute.new('TABLE_SCHEMA', identifier.schema, nv128) unless identifier.schema.blank?
394
- results = sp_executesql(sql, 'SCHEMA', binds)
356
+ binds << Relation::QueryAttribute.new("TABLE_NAME", identifier.object, nv128)
357
+ binds << Relation::QueryAttribute.new("TABLE_SCHEMA", identifier.schema, nv128) unless identifier.schema.blank?
358
+ results = sp_executesql(sql, "SCHEMA", binds)
395
359
  results.map do |ci|
396
360
  ci = ci.symbolize_keys
397
361
  ci[:_type] = ci[:type]
@@ -422,7 +386,7 @@ module ActiveRecord
422
386
  WHERE
423
387
  c.TABLE_NAME = '#{view_tblnm}'
424
388
  AND c.COLUMN_NAME = '#{views_real_column_name(table_name, ci[:name])}'
425
- }.squish, 'SCHEMA'
389
+ }.squish, "SCHEMA"
426
390
  end
427
391
  case default
428
392
  when nil
@@ -441,7 +405,7 @@ module ActiveRecord
441
405
  else ci[:type]
442
406
  end
443
407
  value = default.match(/\A\((.*)\)\Z/m)[1]
444
- value = select_value("SELECT CAST(#{value} AS #{type}) AS value", 'SCHEMA')
408
+ value = select_value("SELECT CAST(#{value} AS #{type}) AS value", "SCHEMA")
445
409
  [value, nil]
446
410
  end
447
411
  end
@@ -453,8 +417,76 @@ module ActiveRecord
453
417
  end
454
418
  end
455
419
 
420
+ def column_definitions_sql(database, identifier)
421
+ object_name = prepared_statements ? "@0" : quote(identifier.object)
422
+ schema_name = if identifier.schema.blank?
423
+ "schema_name()"
424
+ else
425
+ prepared_statements ? "@1" : quote(identifier.schema)
426
+ end
427
+
428
+ %{
429
+ SELECT
430
+ #{lowercase_schema_reflection_sql('o.name')} AS [table_name],
431
+ #{lowercase_schema_reflection_sql('c.name')} AS [name],
432
+ t.name AS [type],
433
+ d.definition AS [default_value],
434
+ CASE
435
+ WHEN t.name IN ('decimal', 'bigint', 'int', 'money', 'numeric', 'smallint', 'smallmoney', 'tinyint')
436
+ THEN c.scale
437
+ END AS [numeric_scale],
438
+ CASE
439
+ WHEN t.name IN ('decimal', 'bigint', 'int', 'money', 'numeric', 'smallint', 'smallmoney', 'tinyint', 'real', 'float')
440
+ THEN c.precision
441
+ END AS [numeric_precision],
442
+ CASE
443
+ WHEN t.name IN ('date', 'datetime', 'datetime2', 'datetimeoffset', 'smalldatetime', 'time')
444
+ THEN c.scale
445
+ END AS [datetime_precision],
446
+ c.collation_name AS [collation],
447
+ ROW_NUMBER() OVER (ORDER BY c.column_id) AS [ordinal_position],
448
+ CASE
449
+ WHEN t.name IN ('nchar', 'nvarchar') AND c.max_length > 0
450
+ THEN c.max_length / 2
451
+ ELSE c.max_length
452
+ END AS [length],
453
+ CASE c.is_nullable
454
+ WHEN 1
455
+ THEN 1
456
+ END AS [is_nullable],
457
+ CASE
458
+ WHEN ic.object_id IS NOT NULL
459
+ THEN 1
460
+ END AS [is_primary],
461
+ c.is_identity AS [is_identity]
462
+ FROM #{database}.sys.columns c
463
+ INNER JOIN #{database}.sys.objects o
464
+ ON c.object_id = o.object_id
465
+ INNER JOIN #{database}.sys.schemas s
466
+ ON o.schema_id = s.schema_id
467
+ INNER JOIN #{database}.sys.types t
468
+ ON c.system_type_id = t.system_type_id
469
+ AND c.user_type_id = t.user_type_id
470
+ LEFT OUTER JOIN #{database}.sys.default_constraints d
471
+ ON c.object_id = d.parent_object_id
472
+ AND c.default_object_id = d.object_id
473
+ LEFT OUTER JOIN #{database}.sys.key_constraints k
474
+ ON c.object_id = k.parent_object_id
475
+ AND k.type = 'PK'
476
+ LEFT OUTER JOIN #{database}.sys.index_columns ic
477
+ ON k.parent_object_id = ic.object_id
478
+ AND k.unique_index_id = ic.index_id
479
+ AND c.column_id = ic.column_id
480
+ WHERE
481
+ o.name = #{object_name}
482
+ AND s.name = #{schema_name}
483
+ ORDER BY
484
+ c.column_id
485
+ }.gsub(/[ \t\r\n]+/, " ").strip
486
+ end
487
+
456
488
  def remove_check_constraints(table_name, column_name)
457
- 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'
489
+ 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"
458
490
  constraints.each do |constraint|
459
491
  do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
460
492
  end
@@ -462,8 +494,8 @@ module ActiveRecord
462
494
 
463
495
  def remove_default_constraint(table_name, column_name)
464
496
  # If their are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
465
- execute_procedure(:sp_helpconstraint, table_name, 'nomsg').flatten.select do |row|
466
- row['constraint_type'] == "DEFAULT on column #{column_name}"
497
+ execute_procedure(:sp_helpconstraint, table_name, "nomsg").flatten.select do |row|
498
+ row["constraint_type"] == "DEFAULT on column #{column_name}"
467
499
  end.each do |row|
468
500
  do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
469
501
  end
@@ -479,12 +511,12 @@ module ActiveRecord
479
511
 
480
512
  def get_table_name(sql)
481
513
  tn = if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
482
- Regexp.last_match[3] || Regexp.last_match[4]
483
- elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
484
- Regexp.last_match[1]
485
- else
486
- nil
487
- end
514
+ Regexp.last_match[3] || Regexp.last_match[4]
515
+ elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
516
+ Regexp.last_match[1]
517
+ else
518
+ nil
519
+ end
488
520
  SQLServer::Utils.extract_identifiers(tn).object
489
521
  end
490
522
 
@@ -500,22 +532,22 @@ module ActiveRecord
500
532
 
501
533
  def view_table_name(table_name)
502
534
  view_info = view_information(table_name)
503
- view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
535
+ view_info ? get_table_name(view_info["VIEW_DEFINITION"]) : table_name
504
536
  end
505
537
 
506
538
  def view_information(table_name)
507
539
  @view_information ||= {}
508
540
  @view_information[table_name] ||= begin
509
541
  identifier = SQLServer::Utils.extract_identifiers(table_name)
510
- view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", 'SCHEMA'
542
+ view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", "SCHEMA"
511
543
  if view_info
512
544
  view_info = view_info.with_indifferent_access
513
545
  if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
514
546
  view_info[:VIEW_DEFINITION] = begin
515
- select_values("EXEC sp_helptext #{identifier.object_quoted}", 'SCHEMA').join
516
- rescue
517
- warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
518
- nil
547
+ select_values("EXEC sp_helptext #{identifier.object_quoted}", "SCHEMA").join
548
+ rescue
549
+ warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
550
+ nil
519
551
  end
520
552
  end
521
553
  end
@@ -526,14 +558,14 @@ module ActiveRecord
526
558
  def views_real_column_name(table_name, column_name)
527
559
  view_definition = view_information(table_name)[:VIEW_DEFINITION]
528
560
  return column_name unless view_definition
561
+
529
562
  match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
530
563
  match_data ? match_data[1] : column_name
531
564
  end
532
565
 
533
- def create_table_definition(*args)
534
- SQLServer::TableDefinition.new(*args)
566
+ def create_table_definition(*args, **options)
567
+ SQLServer::TableDefinition.new(self, *args, **options)
535
568
  end
536
-
537
569
  end
538
570
  end
539
571
  end