activerecord-sqlserver-adapter 6.0.0.rc1 → 6.1.0.0.rc1

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 (149) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +26 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +29 -0
  5. data/CHANGELOG.md +18 -23
  6. data/Gemfile +11 -5
  7. data/Guardfile +9 -8
  8. data/README.md +32 -3
  9. data/RUNNING_UNIT_TESTS.md +1 -1
  10. data/Rakefile +12 -16
  11. data/VERSION +1 -1
  12. data/activerecord-sqlserver-adapter.gemspec +4 -4
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +0 -4
  14. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +1 -4
  15. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +3 -13
  16. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +8 -5
  17. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +2 -3
  18. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +2 -3
  19. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
  20. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +58 -43
  21. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +7 -12
  22. data/lib/active_record/connection_adapters/sqlserver/errors.rb +0 -3
  23. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +15 -15
  24. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -3
  25. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +16 -10
  26. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +131 -105
  27. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +6 -8
  28. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +2 -2
  29. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +1 -1
  30. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +25 -7
  31. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +1 -5
  32. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +6 -10
  33. data/lib/active_record/connection_adapters/sqlserver/type.rb +36 -35
  34. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +0 -2
  35. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +0 -2
  36. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +0 -2
  37. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +2 -2
  38. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +0 -2
  39. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +2 -3
  40. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +2 -3
  41. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +0 -2
  42. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +0 -2
  43. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +0 -2
  44. data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
  45. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +0 -2
  46. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +0 -2
  47. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +0 -1
  48. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +0 -2
  49. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +0 -2
  50. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +0 -2
  51. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +0 -2
  52. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +0 -2
  53. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +0 -2
  54. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +0 -2
  55. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +2 -3
  56. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +6 -9
  57. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +0 -2
  58. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +0 -2
  59. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +1 -3
  60. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +0 -2
  61. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +0 -2
  62. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +0 -2
  63. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +0 -2
  64. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +1 -2
  65. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +1 -3
  66. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +0 -2
  67. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +1 -3
  68. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +0 -2
  69. data/lib/active_record/connection_adapters/sqlserver/utils.rb +8 -11
  70. data/lib/active_record/connection_adapters/sqlserver/version.rb +0 -2
  71. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +170 -136
  72. data/lib/active_record/connection_adapters/sqlserver_column.rb +16 -1
  73. data/lib/active_record/sqlserver_base.rb +9 -15
  74. data/lib/active_record/tasks/sqlserver_database_tasks.rb +36 -39
  75. data/lib/activerecord-sqlserver-adapter.rb +1 -1
  76. data/lib/arel/visitors/sqlserver.rb +126 -50
  77. data/lib/arel_sqlserver.rb +2 -2
  78. data/test/cases/adapter_test_sqlserver.rb +203 -190
  79. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  80. data/test/cases/change_column_null_test_sqlserver.rb +12 -12
  81. data/test/cases/coerced_tests.rb +656 -318
  82. data/test/cases/column_test_sqlserver.rb +285 -284
  83. data/test/cases/connection_test_sqlserver.rb +15 -20
  84. data/test/cases/disconnected_test_sqlserver.rb +39 -0
  85. data/test/cases/execute_procedure_test_sqlserver.rb +26 -19
  86. data/test/cases/fetch_test_sqlserver.rb +14 -22
  87. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +12 -18
  88. data/test/cases/helper_sqlserver.rb +13 -15
  89. data/test/cases/in_clause_test_sqlserver.rb +36 -9
  90. data/test/cases/index_test_sqlserver.rb +13 -15
  91. data/test/cases/json_test_sqlserver.rb +23 -25
  92. data/test/cases/lateral_test_sqlserver.rb +35 -0
  93. data/test/cases/migration_test_sqlserver.rb +71 -26
  94. data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
  95. data/test/cases/order_test_sqlserver.rb +57 -53
  96. data/test/cases/pessimistic_locking_test_sqlserver.rb +25 -33
  97. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  98. data/test/cases/rake_test_sqlserver.rb +33 -46
  99. data/test/cases/schema_dumper_test_sqlserver.rb +121 -108
  100. data/test/cases/schema_test_sqlserver.rb +18 -26
  101. data/test/cases/scratchpad_test_sqlserver.rb +2 -4
  102. data/test/cases/showplan_test_sqlserver.rb +24 -33
  103. data/test/cases/specific_schema_test_sqlserver.rb +66 -65
  104. data/test/cases/transaction_test_sqlserver.rb +16 -19
  105. data/test/cases/trigger_test_sqlserver.rb +12 -12
  106. data/test/cases/utils_test_sqlserver.rb +68 -70
  107. data/test/cases/uuid_test_sqlserver.rb +11 -13
  108. data/test/debug.rb +6 -6
  109. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  110. data/test/migrations/create_clients_and_change_column_null.rb +1 -1
  111. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +2 -4
  112. data/test/models/sqlserver/booking.rb +1 -1
  113. data/test/models/sqlserver/customers_view.rb +1 -1
  114. data/test/models/sqlserver/dollar_table_name.rb +1 -1
  115. data/test/models/sqlserver/edge_schema.rb +1 -3
  116. data/test/models/sqlserver/fk_has_fk.rb +1 -1
  117. data/test/models/sqlserver/fk_has_pk.rb +1 -1
  118. data/test/models/sqlserver/natural_pk_data.rb +2 -2
  119. data/test/models/sqlserver/natural_pk_int_data.rb +1 -1
  120. data/test/models/sqlserver/no_pk_data.rb +1 -1
  121. data/test/models/sqlserver/object_default.rb +1 -1
  122. data/test/models/sqlserver/quoted_table.rb +2 -2
  123. data/test/models/sqlserver/quoted_view_1.rb +1 -1
  124. data/test/models/sqlserver/quoted_view_2.rb +1 -1
  125. data/test/models/sqlserver/sst_memory.rb +1 -1
  126. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  127. data/test/models/sqlserver/string_default.rb +1 -1
  128. data/test/models/sqlserver/string_defaults_big_view.rb +1 -1
  129. data/test/models/sqlserver/string_defaults_view.rb +1 -1
  130. data/test/models/sqlserver/tinyint_pk.rb +1 -1
  131. data/test/models/sqlserver/trigger.rb +2 -2
  132. data/test/models/sqlserver/trigger_history.rb +1 -1
  133. data/test/models/sqlserver/upper.rb +1 -1
  134. data/test/models/sqlserver/uppered.rb +1 -1
  135. data/test/models/sqlserver/uuid.rb +1 -1
  136. data/test/schema/sqlserver_specific_schema.rb +36 -21
  137. data/test/support/coerceable_test_sqlserver.rb +1 -4
  138. data/test/support/connection_reflection.rb +1 -2
  139. data/test/support/core_ext/query_cache.rb +1 -1
  140. data/test/support/load_schema_sqlserver.rb +3 -5
  141. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  142. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  143. data/test/support/minitest_sqlserver.rb +1 -1
  144. data/test/support/paths_sqlserver.rb +9 -11
  145. data/test/support/rake_helpers.rb +12 -10
  146. data/test/support/sql_counter_sqlserver.rb +14 -16
  147. data/test/support/test_in_memory_oltp.rb +7 -7
  148. metadata +31 -11
  149. data/.travis.yml +0 -23
@@ -4,21 +4,20 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLServer
6
6
  module Quoting
7
-
8
- QUOTED_TRUE = '1'.freeze
9
- QUOTED_FALSE = '0'.freeze
10
- QUOTED_STRING_PREFIX = 'N'.freeze
7
+ QUOTED_TRUE = "1".freeze
8
+ QUOTED_FALSE = "0".freeze
9
+ QUOTED_STRING_PREFIX = "N".freeze
11
10
 
12
11
  def fetch_type_metadata(sql_type, sqlserver_options = {})
13
12
  cast_type = lookup_cast_type(sql_type)
14
- SQLServer::SqlTypeMetadata.new(
13
+ simple_type = SqlTypeMetadata.new(
15
14
  sql_type: sql_type,
16
15
  type: cast_type.type,
17
16
  limit: cast_type.limit,
18
17
  precision: cast_type.precision,
19
- scale: cast_type.scale,
20
- sqlserver_options: sqlserver_options
18
+ scale: cast_type.scale
21
19
  )
20
+ SQLServer::TypeMetadata.new(simple_type, sqlserver_options: sqlserver_options)
22
21
  end
23
22
 
24
23
  def quote_string(s)
@@ -63,10 +62,12 @@ module ActiveRecord
63
62
  end
64
63
 
65
64
  def quoted_date(value)
66
- if value.acts_like?(:date)
67
- Type::Date.new.serialize(value)
68
- else value.acts_like?(:time)
65
+ if value.acts_like?(:time)
69
66
  Type::DateTime.new.serialize(value)
67
+ elsif value.acts_like?(:date)
68
+ Type::Date.new.serialize(value)
69
+ else
70
+ value
70
71
  end
71
72
  end
72
73
 
@@ -82,8 +83,8 @@ module ActiveRecord
82
83
  \A
83
84
  (
84
85
  (?:
85
- # [table_name].[column_name] | function(one or no argument)
86
- ((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
86
+ # [database_name].[database_owner].[table_name].[column_name] | function(one or no argument)
87
+ ((?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
87
88
  )
88
89
  (?:\s+AS\s+(?:\w+|\[\w+\]))?
89
90
  )
@@ -95,8 +96,8 @@ module ActiveRecord
95
96
  \A
96
97
  (
97
98
  (?:
98
- # [table_name].[column_name] | function(one or no argument)
99
- ((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
99
+ # [database_name].[database_owner].[table_name].[column_name] | function(one or no argument)
100
+ ((?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
100
101
  )
101
102
  (?:\s+ASC|\s+DESC)?
102
103
  (?:\s+NULLS\s+(?:FIRST|LAST))?
@@ -130,7 +131,6 @@ module ActiveRecord
130
131
  super
131
132
  end
132
133
  end
133
-
134
134
  end
135
135
  end
136
136
  end
@@ -3,10 +3,13 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLServer
6
- class SchemaCreation < AbstractAdapter::SchemaCreation
7
-
6
+ class SchemaCreation < SchemaCreation
8
7
  private
9
8
 
9
+ def supports_index_using?
10
+ false
11
+ end
12
+
10
13
  def visit_TableDefinition(o)
11
14
  if_not_exists = o.if_not_exists
12
15
 
@@ -30,11 +33,28 @@ module ActiveRecord
30
33
  sql
31
34
  end
32
35
 
36
+ def visit_CreateIndexDefinition(o)
37
+ if_not_exists = o.if_not_exists
38
+
39
+ o.if_not_exists = false
40
+
41
+ sql = super
42
+
43
+ if if_not_exists
44
+ sql = "IF NOT EXISTS (SELECT name FROM sysindexes WHERE name = '#{o.index.name}') #{sql}"
45
+ end
46
+
47
+ sql
48
+ end
49
+
33
50
  def add_column_options!(sql, options)
34
51
  sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
35
52
  if options[:null] == false
36
53
  sql << " NOT NULL"
37
54
  end
55
+ if options[:collation].present?
56
+ sql << " COLLATE #{options[:collation]}"
57
+ end
38
58
  if options[:is_identity] == true
39
59
  sql << " IDENTITY(1,1)"
40
60
  end
@@ -63,7 +83,6 @@ module ActiveRecord
63
83
  def options_primary_key_with_nil_default?(options)
64
84
  options[:primary_key] && options.include?(:default) && options[:default].nil?
65
85
  end
66
-
67
86
  end
68
87
  end
69
88
  end
@@ -4,35 +4,41 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLServer
6
6
  class SchemaDumper < ConnectionAdapters::SchemaDumper
7
-
8
7
  SQLSEVER_NO_LIMIT_TYPES = [
9
- 'text',
10
- 'ntext',
11
- 'varchar(max)',
12
- 'nvarchar(max)',
13
- 'varbinary(max)'
8
+ "text",
9
+ "ntext",
10
+ "varchar(max)",
11
+ "nvarchar(max)",
12
+ "varbinary(max)"
14
13
  ].freeze
15
14
 
16
15
  private
17
16
 
18
17
  def explicit_primary_key_default?(column)
19
- column.is_primary? && !column.is_identity?
18
+ column.type == :integer && !column.is_identity?
20
19
  end
21
20
 
22
21
  def schema_limit(column)
23
22
  return if SQLSEVER_NO_LIMIT_TYPES.include?(column.sql_type)
23
+
24
24
  super
25
25
  end
26
26
 
27
27
  def schema_collation(column)
28
28
  return unless column.collation
29
- column.collation if column.collation != @connection.collation
29
+
30
+ # use inspect to ensure collation is dumped as string. Without this it's dumped as
31
+ # a constant ('collation: SQL_Latin1_General_CP1_CI_AS')
32
+ collation = column.collation.inspect
33
+ # use inspect to ensure string comparison
34
+ default_collation = @connection.collation.inspect
35
+
36
+ collation if collation != default_collation
30
37
  end
31
38
 
32
39
  def default_primary_key?(column)
33
- super && column.is_primary? && column.is_identity?
40
+ super && column.is_identity?
34
41
  end
35
-
36
42
  end
37
43
  end
38
44
  end
@@ -4,7 +4,6 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLServer
6
6
  module SchemaStatements
7
-
8
7
  def native_database_types
9
8
  @native_database_types ||= initialize_native_database_types.freeze
10
9
  end
@@ -19,16 +18,16 @@ module ActiveRecord
19
18
  # Mimic CASCADE option as best we can.
20
19
  if options[:force] == :cascade
21
20
  execute_procedure(:sp_fkeys, pktable_name: table_name).each do |fkdata|
22
- fktable = fkdata['FKTABLE_NAME']
23
- fkcolmn = fkdata['FKCOLUMN_NAME']
24
- pktable = fkdata['PKTABLE_NAME']
25
- pkcolmn = fkdata['PKCOLUMN_NAME']
26
- 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"]
27
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)} )"
28
27
  end
29
28
  end
30
29
  if options[:if_exists] && @version_year < 2016
31
- execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}"
30
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}", "SCHEMA"
32
31
  else
33
32
  super
34
33
  end
@@ -49,11 +48,11 @@ module ActiveRecord
49
48
  orders = {}
50
49
  columns = []
51
50
 
52
- index[:index_keys].split(',').each do |column|
51
+ index[:index_keys].split(",").each do |column|
53
52
  column.strip!
54
53
 
55
- if column.ends_with?('(-)')
56
- column.gsub! '(-)', ''
54
+ if column.end_with?("(-)")
55
+ column.gsub! "(-)", ""
57
56
  orders[column] = :desc
58
57
  end
59
58
 
@@ -67,6 +66,7 @@ module ActiveRecord
67
66
 
68
67
  def columns(table_name)
69
68
  return [] if table_name.blank?
69
+
70
70
  column_definitions(table_name).map do |ci|
71
71
  sqlserver_options = ci.slice :ordinal_position, :is_primary, :is_identity, :table_name
72
72
  sql_type_metadata = fetch_type_metadata ci[:type], sqlserver_options
@@ -105,7 +105,7 @@ module ActiveRecord
105
105
  identifier = database_prefix_identifier(table_name)
106
106
  database = identifier.fully_qualified_database_quoted
107
107
  sql = %{
108
- SELECT KCU.COLUMN_NAME AS [name]
108
+ SELECT #{lowercase_schema_reflection_sql('KCU.COLUMN_NAME')} AS [name]
109
109
  FROM #{database}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
110
110
  LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
111
111
  ON KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
@@ -117,12 +117,12 @@ module ActiveRecord
117
117
  AND KCU.TABLE_SCHEMA = #{identifier.schema.blank? ? 'schema_name()' : (prepared_statements ? '@1' : quote(identifier.schema))}
118
118
  AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
119
119
  ORDER BY KCU.ORDINAL_POSITION ASC
120
- }.gsub(/[[:space:]]/, ' ')
120
+ }.gsub(/[[:space:]]/, " ")
121
121
  binds = []
122
122
  nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
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'] }
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"] }
126
126
  end
127
127
 
128
128
  def rename_table(table_name, new_name)
@@ -130,8 +130,10 @@ module ActiveRecord
130
130
  rename_table_indexes(table_name, new_name)
131
131
  end
132
132
 
133
- def remove_column(table_name, column_name, type = nil, options = {})
134
- raise ArgumentError.new('You must specify at least one column name. Example: remove_column(:people, :first_name)') if column_name.is_a? Array
133
+ def remove_column(table_name, column_name, type = nil, **options)
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
+ return if options[:if_exists] == true && !column_exists?(table_name, column_name)
136
+
135
137
  remove_check_constraints(table_name, column_name)
136
138
  remove_default_constraint(table_name, column_name)
137
139
  remove_indexes(table_name, column_name)
@@ -144,10 +146,10 @@ module ActiveRecord
144
146
  column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s }
145
147
  without_constraints = options.key?(:default) || options.key?(:limit)
146
148
  default = if !options.key?(:default) && column_object
147
- column_object.default
148
- else
149
- options[:default]
150
- end
149
+ column_object.default
150
+ else
151
+ options[:default]
152
+ end
151
153
  if without_constraints || (column_object && column_object.type != type.to_sym)
152
154
  remove_default_constraint(table_name, column_name)
153
155
  indexes = indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }
@@ -155,7 +157,8 @@ module ActiveRecord
155
157
  end
156
158
  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?
157
159
  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])}"
158
- alter_command += ' NOT NULL' if !options[:null].nil? && options[:null] == false
160
+ alter_command += " COLLATE #{options[:collation]}" if options[:collation].present?
161
+ alter_command += " NOT NULL" if !options[:null].nil? && options[:null] == false
159
162
  sql_commands << alter_command
160
163
  if without_constraints
161
164
  default = quote_default_expression(default, column_object || column_for(table_name, column_name))
@@ -173,6 +176,7 @@ module ActiveRecord
173
176
  clear_cache!
174
177
  column = column_for(table_name, column_name)
175
178
  return unless column
179
+
176
180
  remove_default_constraint(table_name, column_name)
177
181
  default = extract_new_default_value(default_or_changes)
178
182
  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)}"
@@ -182,15 +186,16 @@ module ActiveRecord
182
186
  def rename_column(table_name, column_name, new_column_name)
183
187
  clear_cache!
184
188
  identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{column_name}")
185
- execute_procedure :sp_rename, identifier.quoted, new_column_name, 'COLUMN'
189
+ execute_procedure :sp_rename, identifier.quoted, new_column_name, "COLUMN"
186
190
  rename_column_indexes(table_name, column_name, new_column_name)
187
191
  clear_cache!
188
192
  end
189
193
 
190
194
  def rename_index(table_name, old_name, new_name)
191
- 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
195
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters" if new_name.length > index_name_length
196
+
192
197
  identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{old_name}")
193
- execute_procedure :sp_rename, identifier.quoted, new_name, 'INDEX'
198
+ execute_procedure :sp_rename, identifier.quoted, new_name, "INDEX"
194
199
  end
195
200
 
196
201
  def remove_index!(table_name, index_name)
@@ -202,13 +207,13 @@ module ActiveRecord
202
207
  fk_info = execute_procedure :sp_fkeys, nil, identifier.schema, nil, identifier.object, identifier.schema
203
208
  fk_info.map do |row|
204
209
  from_table = identifier.object
205
- to_table = row['PKTABLE_NAME']
210
+ to_table = row["PKTABLE_NAME"]
206
211
  options = {
207
- name: row['FK_NAME'],
208
- column: row['FKCOLUMN_NAME'],
209
- primary_key: row['PKCOLUMN_NAME'],
210
- on_update: extract_foreign_key_action('update', row['FK_NAME']),
211
- on_delete: extract_foreign_key_action('delete', row['FK_NAME'])
212
+ name: row["FK_NAME"],
213
+ column: row["FKCOLUMN_NAME"],
214
+ primary_key: row["PKCOLUMN_NAME"],
215
+ on_update: extract_foreign_key_action("update", row["FK_NAME"]),
216
+ on_delete: extract_foreign_key_action("delete", row["FK_NAME"])
212
217
  }
213
218
  ForeignKeyDefinition.new from_table, to_table, options
214
219
  end
@@ -216,8 +221,8 @@ module ActiveRecord
216
221
 
217
222
  def extract_foreign_key_action(action, fk_name)
218
223
  case select_value("SELECT #{action}_referential_action_desc FROM sys.foreign_keys WHERE name = '#{fk_name}'")
219
- when 'CASCADE' then :cascade
220
- when 'SET_NULL' then :nullify
224
+ when "CASCADE" then :cascade
225
+ when "SET_NULL" then :nullify
221
226
  end
222
227
  end
223
228
 
@@ -225,15 +230,15 @@ module ActiveRecord
225
230
  type_limitable = %w(string integer float char nchar varchar nvarchar).include?(type.to_s)
226
231
  limit = nil unless type_limitable
227
232
  case type.to_s
228
- when 'integer'
233
+ when "integer"
229
234
  case limit
230
- when 1 then 'tinyint'
231
- when 2 then 'smallint'
232
- when 3..4, nil then 'integer'
233
- when 5..8 then 'bigint'
235
+ when 1 then "tinyint"
236
+ when 2 then "smallint"
237
+ when 3..4, nil then "integer"
238
+ when 5..8 then "bigint"
234
239
  else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
235
240
  end
236
- when 'datetime2'
241
+ when "datetime2"
237
242
  column_type_sql = super
238
243
  if precision
239
244
  if (0..7) === precision
@@ -249,11 +254,11 @@ module ActiveRecord
249
254
  end
250
255
 
251
256
  def columns_for_distinct(columns, orders)
252
- order_columns = orders.reject(&:blank?).map{ |s|
253
- s = s.to_sql unless s.is_a?(String)
254
- s.gsub(/\s+(?:ASC|DESC)\b/i, '')
255
- .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
256
- }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
257
+ order_columns = orders.reject(&:blank?).map { |s|
258
+ s = s.to_sql unless s.is_a?(String)
259
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
260
+ .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
261
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
257
262
 
258
263
  (order_columns << super).join(", ")
259
264
  end
@@ -270,7 +275,7 @@ module ActiveRecord
270
275
  do_execute("UPDATE #{table_id} SET #{column_id}=#{quote(default)} WHERE #{column_id} IS NULL")
271
276
  end
272
277
  sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, limit: column.limit, precision: column.precision, scale: column.scale}"
273
- sql += ' NOT NULL' if !allow_null.nil? && allow_null == false
278
+ sql += " NOT NULL" if !allow_null.nil? && allow_null == false
274
279
  do_execute sql
275
280
  end
276
281
 
@@ -278,14 +283,33 @@ module ActiveRecord
278
283
  SQLServer::SchemaDumper.create(self, options)
279
284
  end
280
285
 
286
+ def create_schema(schema_name, authorization = nil)
287
+ sql = "CREATE SCHEMA [#{schema_name}]"
288
+ sql += " AUTHORIZATION [#{authorization}]" if authorization
289
+
290
+ execute sql
291
+ end
292
+
293
+ def change_table_schema(schema_name, table_name)
294
+ execute "ALTER SCHEMA [#{schema_name}] TRANSFER [#{table_name}]"
295
+ end
296
+
297
+ def drop_schema(schema_name)
298
+ execute "DROP SCHEMA [#{schema_name}]"
299
+ end
300
+
281
301
  private
282
302
 
283
303
  def data_source_sql(name = nil, type: nil)
284
304
  scope = quoted_scope name, type: type
305
+
285
306
  table_name = lowercase_schema_reflection_sql 'TABLE_NAME'
307
+ database = scope[:database].present? ? "#{scope[:database]}." : ""
308
+ table_catalog = scope[:database].present? ? quote(scope[:database]) : "DB_NAME()"
309
+
286
310
  sql = "SELECT #{table_name}"
287
- sql += ' FROM INFORMATION_SCHEMA.TABLES WITH (NOLOCK)'
288
- sql += ' WHERE TABLE_CATALOG = DB_NAME()'
311
+ sql += " FROM #{database}INFORMATION_SCHEMA.TABLES WITH (NOLOCK)"
312
+ sql += " WHERE TABLE_CATALOG = #{table_catalog}"
289
313
  sql += " AND TABLE_SCHEMA = #{quote(scope[:schema])}"
290
314
  sql += " AND TABLE_NAME = #{quote(scope[:name])}" if scope[:name]
291
315
  sql += " AND TABLE_TYPE = #{quote(scope[:type])}" if scope[:type]
@@ -296,7 +320,8 @@ module ActiveRecord
296
320
  def quoted_scope(name = nil, type: nil)
297
321
  identifier = SQLServer::Utils.extract_identifiers(name)
298
322
  {}.tap do |scope|
299
- scope[:schema] = identifier.schema || 'dbo'
323
+ scope[:database] = identifier.database if identifier.database
324
+ scope[:schema] = identifier.schema || "dbo"
300
325
  scope[:name] = identifier.object if identifier.object
301
326
  scope[:type] = type if type
302
327
  end
@@ -306,37 +331,37 @@ module ActiveRecord
306
331
 
307
332
  def initialize_native_database_types
308
333
  {
309
- primary_key: 'bigint NOT NULL IDENTITY(1,1) PRIMARY KEY',
310
- primary_key_nonclustered: 'int NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED',
311
- integer: { name: 'int', limit: 4 },
312
- bigint: { name: 'bigint' },
313
- boolean: { name: 'bit' },
314
- decimal: { name: 'decimal' },
315
- money: { name: 'money' },
316
- smallmoney: { name: 'smallmoney' },
317
- float: { name: 'float' },
318
- real: { name: 'real' },
319
- date: { name: 'date' },
320
- datetime: { name: 'datetime' },
321
- datetime2: { name: 'datetime2' },
322
- datetimeoffset: { name: 'datetimeoffset' },
323
- smalldatetime: { name: 'smalldatetime' },
324
- timestamp: { name: 'datetime' },
325
- time: { name: 'time' },
326
- char: { name: 'char' },
327
- varchar: { name: 'varchar', limit: 8000 },
328
- varchar_max: { name: 'varchar(max)' },
329
- text_basic: { name: 'text' },
330
- nchar: { name: 'nchar' },
331
- string: { name: 'nvarchar', limit: 4000 },
332
- text: { name: 'nvarchar(max)' },
333
- ntext: { name: 'ntext' },
334
- binary_basic: { name: 'binary' },
335
- varbinary: { name: 'varbinary', limit: 8000 },
336
- binary: { name: 'varbinary(max)' },
337
- uuid: { name: 'uniqueidentifier' },
338
- ss_timestamp: { name: 'timestamp' },
339
- json: { name: 'nvarchar(max)' }
334
+ primary_key: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY",
335
+ primary_key_nonclustered: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED",
336
+ integer: { name: "int", limit: 4 },
337
+ bigint: { name: "bigint" },
338
+ boolean: { name: "bit" },
339
+ decimal: { name: "decimal" },
340
+ money: { name: "money" },
341
+ smallmoney: { name: "smallmoney" },
342
+ float: { name: "float" },
343
+ real: { name: "real" },
344
+ date: { name: "date" },
345
+ datetime: { name: "datetime" },
346
+ datetime2: { name: "datetime2" },
347
+ datetimeoffset: { name: "datetimeoffset" },
348
+ smalldatetime: { name: "smalldatetime" },
349
+ timestamp: { name: "datetime" },
350
+ time: { name: "time" },
351
+ char: { name: "char" },
352
+ varchar: { name: "varchar", limit: 8000 },
353
+ varchar_max: { name: "varchar(max)" },
354
+ text_basic: { name: "text" },
355
+ nchar: { name: "nchar" },
356
+ string: { name: "nvarchar", limit: 4000 },
357
+ text: { name: "nvarchar(max)" },
358
+ ntext: { name: "ntext" },
359
+ binary_basic: { name: "binary" },
360
+ varbinary: { name: "varbinary", limit: 8000 },
361
+ binary: { name: "varbinary(max)" },
362
+ uuid: { name: "uniqueidentifier" },
363
+ ss_timestamp: { name: "timestamp" },
364
+ json: { name: "nvarchar(max)" }
340
365
  }
341
366
  end
342
367
 
@@ -350,9 +375,9 @@ module ActiveRecord
350
375
 
351
376
  binds = []
352
377
  nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
353
- binds << Relation::QueryAttribute.new('TABLE_NAME', identifier.object, nv128)
354
- binds << Relation::QueryAttribute.new('TABLE_SCHEMA', identifier.schema, nv128) unless identifier.schema.blank?
355
- results = sp_executesql(sql, 'SCHEMA', binds)
378
+ binds << Relation::QueryAttribute.new("TABLE_NAME", identifier.object, nv128)
379
+ binds << Relation::QueryAttribute.new("TABLE_SCHEMA", identifier.schema, nv128) unless identifier.schema.blank?
380
+ results = sp_executesql(sql, "SCHEMA", binds)
356
381
  results.map do |ci|
357
382
  ci = ci.symbolize_keys
358
383
  ci[:_type] = ci[:type]
@@ -383,7 +408,7 @@ module ActiveRecord
383
408
  WHERE
384
409
  c.TABLE_NAME = '#{view_tblnm}'
385
410
  AND c.COLUMN_NAME = '#{views_real_column_name(table_name, ci[:name])}'
386
- }.squish, 'SCHEMA'
411
+ }.squish, "SCHEMA"
387
412
  end
388
413
  case default
389
414
  when nil
@@ -402,7 +427,7 @@ module ActiveRecord
402
427
  else ci[:type]
403
428
  end
404
429
  value = default.match(/\A\((.*)\)\Z/m)[1]
405
- value = select_value("SELECT CAST(#{value} AS #{type}) AS value", 'SCHEMA')
430
+ value = select_value("SELECT CAST(#{value} AS #{type}) AS value", "SCHEMA")
406
431
  [value, nil]
407
432
  end
408
433
  end
@@ -415,11 +440,11 @@ module ActiveRecord
415
440
  end
416
441
 
417
442
  def column_definitions_sql(database, identifier)
418
- object_name = prepared_statements ? '@0' : quote(identifier.object)
419
- schema_name = if identifier.schema.blank?
420
- 'schema_name()'
443
+ object_name = prepared_statements ? "@0" : quote(identifier.object)
444
+ schema_name = if identifier.schema.blank?
445
+ "schema_name()"
421
446
  else
422
- prepared_statements ? '@1' : quote(identifier.schema)
447
+ prepared_statements ? "@1" : quote(identifier.schema)
423
448
  end
424
449
 
425
450
  %{
@@ -469,6 +494,7 @@ module ActiveRecord
469
494
  AND c.default_object_id = d.object_id
470
495
  LEFT OUTER JOIN #{database}.sys.key_constraints k
471
496
  ON c.object_id = k.parent_object_id
497
+ AND k.type = 'PK'
472
498
  LEFT OUTER JOIN #{database}.sys.index_columns ic
473
499
  ON k.parent_object_id = ic.object_id
474
500
  AND k.unique_index_id = ic.index_id
@@ -478,11 +504,11 @@ module ActiveRecord
478
504
  AND s.name = #{schema_name}
479
505
  ORDER BY
480
506
  c.column_id
481
- }.gsub(/[ \t\r\n]+/, ' ').strip
507
+ }.gsub(/[ \t\r\n]+/, " ").strip
482
508
  end
483
509
 
484
510
  def remove_check_constraints(table_name, column_name)
485
- 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'
511
+ 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"
486
512
  constraints.each do |constraint|
487
513
  do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
488
514
  end
@@ -490,8 +516,8 @@ module ActiveRecord
490
516
 
491
517
  def remove_default_constraint(table_name, column_name)
492
518
  # If their are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
493
- execute_procedure(:sp_helpconstraint, table_name, 'nomsg').flatten.select do |row|
494
- row['constraint_type'] == "DEFAULT on column #{column_name}"
519
+ execute_procedure(:sp_helpconstraint, table_name, "nomsg").flatten.select do |row|
520
+ row["constraint_type"] == "DEFAULT on column #{column_name}"
495
521
  end.each do |row|
496
522
  do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
497
523
  end
@@ -507,12 +533,12 @@ module ActiveRecord
507
533
 
508
534
  def get_table_name(sql)
509
535
  tn = if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
510
- Regexp.last_match[3] || Regexp.last_match[4]
511
- elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
512
- Regexp.last_match[1]
513
- else
514
- nil
515
- end
536
+ Regexp.last_match[3] || Regexp.last_match[4]
537
+ elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
538
+ Regexp.last_match[1]
539
+ else
540
+ nil
541
+ end
516
542
  SQLServer::Utils.extract_identifiers(tn).object
517
543
  end
518
544
 
@@ -528,22 +554,22 @@ module ActiveRecord
528
554
 
529
555
  def view_table_name(table_name)
530
556
  view_info = view_information(table_name)
531
- view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
557
+ view_info ? get_table_name(view_info["VIEW_DEFINITION"]) : table_name
532
558
  end
533
559
 
534
560
  def view_information(table_name)
535
561
  @view_information ||= {}
536
562
  @view_information[table_name] ||= begin
537
563
  identifier = SQLServer::Utils.extract_identifiers(table_name)
538
- view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", 'SCHEMA'
564
+ view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", "SCHEMA"
539
565
  if view_info
540
566
  view_info = view_info.with_indifferent_access
541
567
  if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
542
568
  view_info[:VIEW_DEFINITION] = begin
543
- select_values("EXEC sp_helptext #{identifier.object_quoted}", 'SCHEMA').join
544
- rescue
545
- warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
546
- nil
569
+ select_values("EXEC sp_helptext #{identifier.object_quoted}", "SCHEMA").join
570
+ rescue
571
+ warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
572
+ nil
547
573
  end
548
574
  end
549
575
  end
@@ -554,6 +580,7 @@ module ActiveRecord
554
580
  def views_real_column_name(table_name, column_name)
555
581
  view_definition = view_information(table_name)[:VIEW_DEFINITION]
556
582
  return column_name unless view_definition
583
+
557
584
  match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
558
585
  match_data ? match_data[1] : column_name
559
586
  end
@@ -561,7 +588,6 @@ module ActiveRecord
561
588
  def create_table_definition(*args, **options)
562
589
  SQLServer::TableDefinition.new(self, *args, **options)
563
590
  end
564
-
565
591
  end
566
592
  end
567
593
  end