activerecord-sqlserver-adapter 5.2.1 → 6.0.0

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/.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 +38 -24
  8. data/{Dockerfile → Dockerfile.ci} +1 -1
  9. data/Gemfile +48 -41
  10. data/Guardfile +9 -8
  11. data/README.md +9 -30
  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 +5 -4
  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 +8 -7
  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 +6 -4
  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 +9 -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 +190 -164
  35. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +4 -2
  36. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +3 -1
  37. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +8 -8
  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/big_integer.rb +3 -3
  42. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -4
  43. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -3
  44. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +7 -4
  45. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +2 -2
  46. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +4 -3
  47. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +8 -8
  48. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -2
  49. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -2
  50. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -4
  51. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -3
  52. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
  53. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
  54. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
  55. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
  56. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
  57. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
  58. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
  59. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
  60. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
  61. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
  62. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
  63. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
  64. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
  65. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
  66. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
  68. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
  69. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
  70. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
  71. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
  72. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
  73. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
  74. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
  75. data/lib/active_record/connection_adapters/sqlserver/type.rb +37 -35
  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 +128 -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 +214 -171
  87. data/test/cases/change_column_null_test_sqlserver.rb +14 -12
  88. data/test/cases/coerced_tests.rb +631 -356
  89. data/test/cases/column_test_sqlserver.rb +283 -284
  90. data/test/cases/connection_test_sqlserver.rb +17 -20
  91. data/test/cases/execute_procedure_test_sqlserver.rb +20 -20
  92. data/test/cases/fetch_test_sqlserver.rb +16 -22
  93. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
  94. data/test/cases/helper_sqlserver.rb +15 -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 +25 -29
  99. data/test/cases/order_test_sqlserver.rb +53 -54
  100. data/test/cases/pessimistic_locking_test_sqlserver.rb +27 -33
  101. data/test/cases/rake_test_sqlserver.rb +33 -45
  102. data/test/cases/schema_dumper_test_sqlserver.rb +107 -109
  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 +28 -35
  106. data/test/cases/specific_schema_test_sqlserver.rb +68 -65
  107. data/test/cases/transaction_test_sqlserver.rb +18 -20
  108. data/test/cases/trigger_test_sqlserver.rb +14 -13
  109. data/test/cases/utils_test_sqlserver.rb +70 -70
  110. data/test/cases/uuid_test_sqlserver.rb +13 -14
  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/sqlserver_specific_schema.rb +22 -22
  140. data/test/support/coerceable_test_sqlserver.rb +15 -9
  141. data/test/support/connection_reflection.rb +3 -2
  142. data/test/support/core_ext/query_cache.rb +4 -1
  143. data/test/support/load_schema_sqlserver.rb +5 -5
  144. data/test/support/minitest_sqlserver.rb +3 -1
  145. data/test/support/paths_sqlserver.rb +11 -11
  146. data/test/support/rake_helpers.rb +13 -10
  147. data/test/support/sql_counter_sqlserver.rb +3 -4
  148. data/test/support/test_in_memory_oltp.rb +9 -7
  149. metadata +17 -7
@@ -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)
@@ -143,18 +145,19 @@ module ActiveRecord
143
145
  column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s }
144
146
  without_constraints = options.key?(:default) || options.key?(:limit)
145
147
  default = if !options.key?(:default) && column_object
146
- column_object.default
147
- else
148
- options[:default]
149
- end
148
+ column_object.default
149
+ else
150
+ options[:default]
151
+ end
150
152
  if without_constraints || (column_object && column_object.type != type.to_sym)
151
153
  remove_default_constraint(table_name, column_name)
152
154
  indexes = indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }
153
155
  remove_indexes(table_name, column_name)
154
156
  end
155
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?
156
- 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])}"
157
- sql_commands.last << ' NOT NULL' if !options[:null].nil? && options[:null] == false
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
158
161
  if without_constraints
159
162
  default = quote_default_expression(default, column_object || column_for(table_name, column_name))
160
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)}"
@@ -171,6 +174,7 @@ module ActiveRecord
171
174
  clear_cache!
172
175
  column = column_for(table_name, column_name)
173
176
  return unless column
177
+
174
178
  remove_default_constraint(table_name, column_name)
175
179
  default = extract_new_default_value(default_or_changes)
176
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)}"
@@ -180,15 +184,16 @@ module ActiveRecord
180
184
  def rename_column(table_name, column_name, new_column_name)
181
185
  clear_cache!
182
186
  identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{column_name}")
183
- execute_procedure :sp_rename, identifier.quoted, new_column_name, 'COLUMN'
187
+ execute_procedure :sp_rename, identifier.quoted, new_column_name, "COLUMN"
184
188
  rename_column_indexes(table_name, column_name, new_column_name)
185
189
  clear_cache!
186
190
  end
187
191
 
188
192
  def rename_index(table_name, old_name, new_name)
189
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
+
190
195
  identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{old_name}")
191
- execute_procedure :sp_rename, identifier.quoted, new_name, 'INDEX'
196
+ execute_procedure :sp_rename, identifier.quoted, new_name, "INDEX"
192
197
  end
193
198
 
194
199
  def remove_index!(table_name, index_name)
@@ -200,13 +205,13 @@ module ActiveRecord
200
205
  fk_info = execute_procedure :sp_fkeys, nil, identifier.schema, nil, identifier.object, identifier.schema
201
206
  fk_info.map do |row|
202
207
  from_table = identifier.object
203
- to_table = row['PKTABLE_NAME']
208
+ to_table = row["PKTABLE_NAME"]
204
209
  options = {
205
- name: row['FK_NAME'],
206
- column: row['FKCOLUMN_NAME'],
207
- primary_key: row['PKCOLUMN_NAME'],
208
- on_update: extract_foreign_key_action('update', row['FK_NAME']),
209
- 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"])
210
215
  }
211
216
  ForeignKeyDefinition.new from_table, to_table, options
212
217
  end
@@ -214,8 +219,8 @@ module ActiveRecord
214
219
 
215
220
  def extract_foreign_key_action(action, fk_name)
216
221
  case select_value("SELECT #{action}_referential_action_desc FROM sys.foreign_keys WHERE name = '#{fk_name}'")
217
- when 'CASCADE' then :cascade
218
- when 'SET_NULL' then :nullify
222
+ when "CASCADE" then :cascade
223
+ when "SET_NULL" then :nullify
219
224
  end
220
225
  end
221
226
 
@@ -223,21 +228,21 @@ module ActiveRecord
223
228
  type_limitable = %w(string integer float char nchar varchar nvarchar).include?(type.to_s)
224
229
  limit = nil unless type_limitable
225
230
  case type.to_s
226
- when 'integer'
231
+ when "integer"
227
232
  case limit
228
- when 1 then 'tinyint'
229
- when 2 then 'smallint'
230
- when 3..4, nil then 'integer'
231
- 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"
232
237
  else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
233
238
  end
234
- when 'datetime2'
239
+ when "datetime2"
235
240
  column_type_sql = super
236
241
  if precision
237
242
  if (0..7) === precision
238
243
  column_type_sql << "(#{precision})"
239
244
  else
240
- 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")
241
246
  end
242
247
  end
243
248
  column_type_sql
@@ -247,11 +252,11 @@ module ActiveRecord
247
252
  end
248
253
 
249
254
  def columns_for_distinct(columns, orders)
250
- order_columns = orders.reject(&:blank?).map{ |s|
251
- s = s.to_sql unless s.is_a?(String)
252
- s.gsub(/\s+(?:ASC|DESC)\b/i, '')
253
- .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
254
- }.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}" }
255
260
 
256
261
  (order_columns << super).join(", ")
257
262
  end
@@ -268,7 +273,7 @@ module ActiveRecord
268
273
  do_execute("UPDATE #{table_id} SET #{column_id}=#{quote(default)} WHERE #{column_id} IS NULL")
269
274
  end
270
275
  sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, limit: column.limit, precision: column.precision, scale: column.scale}"
271
- sql << ' NOT NULL' if !allow_null.nil? && allow_null == false
276
+ sql += " NOT NULL" if !allow_null.nil? && allow_null == false
272
277
  do_execute sql
273
278
  end
274
279
 
@@ -280,21 +285,21 @@ module ActiveRecord
280
285
 
281
286
  def data_source_sql(name = nil, type: nil)
282
287
  scope = quoted_scope name, type: type
283
- table_name = lowercase_schema_reflection_sql 'TABLE_NAME'
288
+ table_name = lowercase_schema_reflection_sql "TABLE_NAME"
284
289
  sql = "SELECT #{table_name}"
285
- sql << ' FROM INFORMATION_SCHEMA.TABLES WITH (NOLOCK)'
286
- sql << ' WHERE TABLE_CATALOG = DB_NAME()'
287
- sql << " AND TABLE_SCHEMA = #{quote(scope[:schema])}"
288
- sql << " AND TABLE_NAME = #{quote(scope[:name])}" if scope[:name]
289
- sql << " AND TABLE_TYPE = #{quote(scope[:type])}" if scope[:type]
290
- 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}"
291
296
  sql
292
297
  end
293
298
 
294
299
  def quoted_scope(name = nil, type: nil)
295
300
  identifier = SQLServer::Utils.extract_identifiers(name)
296
301
  {}.tap do |scope|
297
- scope[:schema] = identifier.schema || 'dbo'
302
+ scope[:schema] = identifier.schema || "dbo"
298
303
  scope[:name] = identifier.object if identifier.object
299
304
  scope[:type] = type if type
300
305
  end
@@ -304,37 +309,37 @@ module ActiveRecord
304
309
 
305
310
  def initialize_native_database_types
306
311
  {
307
- primary_key: 'bigint NOT NULL IDENTITY(1,1) PRIMARY KEY',
308
- primary_key_nonclustered: 'int NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED',
309
- integer: { name: 'int', limit: 4 },
310
- bigint: { name: 'bigint' },
311
- boolean: { name: 'bit' },
312
- decimal: { name: 'decimal' },
313
- money: { name: 'money' },
314
- smallmoney: { name: 'smallmoney' },
315
- float: { name: 'float' },
316
- real: { name: 'real' },
317
- date: { name: 'date' },
318
- datetime: { name: 'datetime' },
319
- datetime2: { name: 'datetime2' },
320
- datetimeoffset: { name: 'datetimeoffset' },
321
- smalldatetime: { name: 'smalldatetime' },
322
- timestamp: { name: 'datetime' },
323
- time: { name: 'time' },
324
- char: { name: 'char' },
325
- varchar: { name: 'varchar', limit: 8000 },
326
- varchar_max: { name: 'varchar(max)' },
327
- text_basic: { name: 'text' },
328
- nchar: { name: 'nchar' },
329
- string: { name: 'nvarchar', limit: 4000 },
330
- text: { name: 'nvarchar(max)' },
331
- ntext: { name: 'ntext' },
332
- binary_basic: { name: 'binary' },
333
- varbinary: { name: 'varbinary', limit: 8000 },
334
- binary: { name: 'varbinary(max)' },
335
- uuid: { name: 'uniqueidentifier' },
336
- ss_timestamp: { name: 'timestamp' },
337
- 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)" }
338
343
  }
339
344
  end
340
345
 
@@ -343,60 +348,14 @@ module ActiveRecord
343
348
  database = identifier.fully_qualified_database_quoted
344
349
  view_exists = view_exists?(table_name)
345
350
  view_tblnm = view_table_name(table_name) if view_exists
346
- sql = %{
347
- SELECT DISTINCT
348
- #{lowercase_schema_reflection_sql('columns.TABLE_NAME')} AS table_name,
349
- #{lowercase_schema_reflection_sql('columns.COLUMN_NAME')} AS name,
350
- columns.DATA_TYPE AS type,
351
- columns.COLUMN_DEFAULT AS default_value,
352
- columns.NUMERIC_SCALE AS numeric_scale,
353
- columns.NUMERIC_PRECISION AS numeric_precision,
354
- columns.DATETIME_PRECISION AS datetime_precision,
355
- columns.COLLATION_NAME AS [collation],
356
- columns.ordinal_position,
357
- CASE
358
- WHEN columns.DATA_TYPE IN ('nchar','nvarchar','char','varchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
359
- ELSE COL_LENGTH('#{database}.'+columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
360
- END AS [length],
361
- CASE
362
- WHEN columns.IS_NULLABLE = 'YES' THEN 1
363
- ELSE NULL
364
- END AS [is_nullable],
365
- CASE
366
- WHEN KCU.COLUMN_NAME IS NOT NULL AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY' THEN 1
367
- ELSE NULL
368
- END AS [is_primary],
369
- c.is_identity AS [is_identity]
370
- FROM #{database}.INFORMATION_SCHEMA.COLUMNS columns
371
- LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
372
- ON TC.TABLE_NAME = columns.TABLE_NAME
373
- AND TC.TABLE_SCHEMA = columns.TABLE_SCHEMA
374
- AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
375
- LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
376
- ON KCU.COLUMN_NAME = columns.COLUMN_NAME
377
- AND KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
378
- AND KCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG
379
- AND KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
380
- INNER JOIN #{database}.sys.schemas AS s
381
- ON s.name = columns.TABLE_SCHEMA
382
- AND s.schema_id = s.schema_id
383
- INNER JOIN #{database}.sys.objects AS o
384
- ON s.schema_id = o.schema_id
385
- AND o.is_ms_shipped = 0
386
- AND o.type IN ('U', 'V')
387
- AND o.name = columns.TABLE_NAME
388
- INNER JOIN #{database}.sys.columns AS c
389
- ON o.object_id = c.object_id
390
- AND c.name = columns.COLUMN_NAME
391
- WHERE columns.TABLE_NAME = #{prepared_statements ? '@0' : quote(identifier.object)}
392
- AND columns.TABLE_SCHEMA = #{identifier.schema.blank? ? 'schema_name()' : (prepared_statements ? '@1' : quote(identifier.schema))}
393
- ORDER BY columns.ordinal_position
394
- }.gsub(/[ \t\r\n]+/, ' ').strip
351
+
352
+ sql = column_definitions_sql(database, identifier)
353
+
395
354
  binds = []
396
355
  nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
397
- binds << Relation::QueryAttribute.new('TABLE_NAME', identifier.object, nv128)
398
- binds << Relation::QueryAttribute.new('TABLE_SCHEMA', identifier.schema, nv128) unless identifier.schema.blank?
399
- 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)
400
359
  results.map do |ci|
401
360
  ci = ci.symbolize_keys
402
361
  ci[:_type] = ci[:type]
@@ -427,7 +386,7 @@ module ActiveRecord
427
386
  WHERE
428
387
  c.TABLE_NAME = '#{view_tblnm}'
429
388
  AND c.COLUMN_NAME = '#{views_real_column_name(table_name, ci[:name])}'
430
- }.squish, 'SCHEMA'
389
+ }.squish, "SCHEMA"
431
390
  end
432
391
  case default
433
392
  when nil
@@ -446,7 +405,7 @@ module ActiveRecord
446
405
  else ci[:type]
447
406
  end
448
407
  value = default.match(/\A\((.*)\)\Z/m)[1]
449
- value = select_value("SELECT CAST(#{value} AS #{type}) AS value", 'SCHEMA')
408
+ value = select_value("SELECT CAST(#{value} AS #{type}) AS value", "SCHEMA")
450
409
  [value, nil]
451
410
  end
452
411
  end
@@ -458,8 +417,75 @@ module ActiveRecord
458
417
  end
459
418
  end
460
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
+ LEFT OUTER JOIN #{database}.sys.index_columns ic
476
+ ON k.parent_object_id = ic.object_id
477
+ AND k.unique_index_id = ic.index_id
478
+ AND c.column_id = ic.column_id
479
+ WHERE
480
+ o.name = #{object_name}
481
+ AND s.name = #{schema_name}
482
+ ORDER BY
483
+ c.column_id
484
+ }.gsub(/[ \t\r\n]+/, " ").strip
485
+ end
486
+
461
487
  def remove_check_constraints(table_name, column_name)
462
- 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'
488
+ 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"
463
489
  constraints.each do |constraint|
464
490
  do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
465
491
  end
@@ -467,8 +493,8 @@ module ActiveRecord
467
493
 
468
494
  def remove_default_constraint(table_name, column_name)
469
495
  # If their are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
470
- execute_procedure(:sp_helpconstraint, table_name, 'nomsg').flatten.select do |row|
471
- row['constraint_type'] == "DEFAULT on column #{column_name}"
496
+ execute_procedure(:sp_helpconstraint, table_name, "nomsg").flatten.select do |row|
497
+ row["constraint_type"] == "DEFAULT on column #{column_name}"
472
498
  end.each do |row|
473
499
  do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
474
500
  end
@@ -484,12 +510,12 @@ module ActiveRecord
484
510
 
485
511
  def get_table_name(sql)
486
512
  tn = if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
487
- Regexp.last_match[3] || Regexp.last_match[4]
488
- elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
489
- Regexp.last_match[1]
490
- else
491
- nil
492
- end
513
+ Regexp.last_match[3] || Regexp.last_match[4]
514
+ elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
515
+ Regexp.last_match[1]
516
+ else
517
+ nil
518
+ end
493
519
  SQLServer::Utils.extract_identifiers(tn).object
494
520
  end
495
521
 
@@ -505,22 +531,22 @@ module ActiveRecord
505
531
 
506
532
  def view_table_name(table_name)
507
533
  view_info = view_information(table_name)
508
- view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
534
+ view_info ? get_table_name(view_info["VIEW_DEFINITION"]) : table_name
509
535
  end
510
536
 
511
537
  def view_information(table_name)
512
538
  @view_information ||= {}
513
539
  @view_information[table_name] ||= begin
514
540
  identifier = SQLServer::Utils.extract_identifiers(table_name)
515
- view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", 'SCHEMA'
541
+ view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", "SCHEMA"
516
542
  if view_info
517
543
  view_info = view_info.with_indifferent_access
518
544
  if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
519
545
  view_info[:VIEW_DEFINITION] = begin
520
- select_values("EXEC sp_helptext #{identifier.object_quoted}", 'SCHEMA').join
521
- rescue
522
- warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
523
- nil
546
+ select_values("EXEC sp_helptext #{identifier.object_quoted}", "SCHEMA").join
547
+ rescue
548
+ warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
549
+ nil
524
550
  end
525
551
  end
526
552
  end
@@ -531,14 +557,14 @@ module ActiveRecord
531
557
  def views_real_column_name(table_name, column_name)
532
558
  view_definition = view_information(table_name)[:VIEW_DEFINITION]
533
559
  return column_name unless view_definition
560
+
534
561
  match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
535
562
  match_data ? match_data[1] : column_name
536
563
  end
537
564
 
538
- def create_table_definition(*args)
539
- SQLServer::TableDefinition.new(*args)
565
+ def create_table_definition(*args, **options)
566
+ SQLServer::TableDefinition.new(self, *args, **options)
540
567
  end
541
-
542
568
  end
543
569
  end
544
570
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
@@ -41,7 +43,7 @@ module ActiveRecord
41
43
  end
42
44
 
43
45
  def build_separator
44
- '+' + @widths.map { |w| '-' * (w + (cell_padding * 2)) }.join('+') + '+'
46
+ "+" + @widths.map { |w| "-" * (w + (cell_padding * 2)) }.join("+") + "+"
45
47
  end
46
48
 
47
49
  def build_cells(items)
@@ -54,7 +56,7 @@ module ActiveRecord
54
56
 
55
57
  def cast_item(item)
56
58
  case item
57
- when NilClass then 'NULL'
59
+ when NilClass then "NULL"
58
60
  when Float then item.to_s.to(9)
59
61
  else item.to_s.truncate(max_column_width)
60
62
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
@@ -10,7 +12,7 @@ module ActiveRecord
10
12
  def pp
11
13
  xml = @result.rows.first.first
12
14
  if defined?(Nokogiri)
13
- Nokogiri::XML(xml).to_xml indent: 2, encoding: 'UTF-8'
15
+ Nokogiri::XML(xml).to_xml indent: 2, encoding: "UTF-8"
14
16
  else
15
17
  xml
16
18
  end