activerecord-sqlserver-adapter 8.0.9 → 8.1.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.
- checksums.yaml +4 -4
- data/.devcontainer/Dockerfile +1 -1
- data/.github/workflows/ci.yml +34 -3
- data/CHANGELOG.md +14 -62
- data/Dockerfile.ci +1 -1
- data/Gemfile +7 -9
- data/Guardfile +2 -2
- data/README.md +33 -13
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +15 -16
- data/compose.ci.yaml +8 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +1 -2
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +121 -90
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +3 -4
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +7 -7
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +24 -12
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +17 -8
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +162 -156
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +5 -5
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -7
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +3 -4
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +4 -6
- data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -12
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +118 -66
- data/lib/active_record/connection_adapters/sqlserver_column.rb +17 -9
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +5 -5
- data/lib/arel/visitors/sqlserver.rb +55 -26
- data/test/cases/active_schema_test_sqlserver.rb +45 -23
- data/test/cases/adapter_test_sqlserver.rb +72 -59
- data/test/cases/coerced_tests.rb +396 -343
- data/test/cases/column_test_sqlserver.rb +328 -316
- data/test/cases/connection_test_sqlserver.rb +15 -11
- data/test/cases/enum_test_sqlserver.rb +8 -9
- data/test/cases/execute_procedure_test_sqlserver.rb +1 -1
- data/test/cases/fetch_test_sqlserver.rb +1 -1
- data/test/cases/helper_sqlserver.rb +7 -3
- data/test/cases/index_test_sqlserver.rb +8 -6
- data/test/cases/insert_all_test_sqlserver.rb +3 -28
- data/test/cases/json_test_sqlserver.rb +8 -8
- data/test/cases/lateral_test_sqlserver.rb +2 -2
- data/test/cases/migration_test_sqlserver.rb +12 -12
- data/test/cases/optimizer_hints_test_sqlserver.rb +1 -1
- data/test/cases/pessimistic_locking_test_sqlserver.rb +6 -6
- data/test/cases/primary_keys_test_sqlserver.rb +4 -4
- data/test/cases/rake_test_sqlserver.rb +15 -7
- data/test/cases/schema_dumper_test_sqlserver.rb +109 -113
- data/test/cases/schema_test_sqlserver.rb +7 -7
- data/test/cases/showplan_test_sqlserver.rb +2 -2
- data/test/cases/specific_schema_test_sqlserver.rb +6 -6
- data/test/cases/transaction_test_sqlserver.rb +6 -8
- data/test/cases/trigger_test_sqlserver.rb +1 -1
- data/test/cases/utils_test_sqlserver.rb +3 -3
- data/test/cases/view_test_sqlserver.rb +12 -8
- data/test/cases/virtual_column_test_sqlserver.rb +113 -0
- data/test/migrations/create_clients_and_change_column_collation.rb +2 -2
- data/test/models/sqlserver/edge_schema.rb +2 -2
- data/test/schema/sqlserver_specific_schema.rb +49 -37
- data/test/support/coerceable_test_sqlserver.rb +10 -10
- data/test/support/connection_reflection.rb +0 -5
- data/test/support/core_ext/backtrace_cleaner.rb +36 -0
- data/test/support/query_assertions.rb +25 -3
- data/test/support/rake_helpers.rb +6 -10
- metadata +12 -107
|
@@ -14,56 +14,62 @@ module ActiveRecord
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch:)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
unless binds.nil? || binds.empty?
|
|
18
|
+
types, params = sp_executesql_types_and_parameters(binds)
|
|
19
|
+
sql = sp_executesql_sql(sql, types, params, notification_payload[:name])
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
id_insert_table_name = query_requires_identity_insert?(sql)
|
|
23
|
+
|
|
24
|
+
result, affected_rows = if id_insert_table_name
|
|
25
|
+
with_identity_insert_enabled(id_insert_table_name, raw_connection) do
|
|
26
|
+
internal_exec_sql_query(sql, raw_connection)
|
|
27
|
+
end
|
|
28
|
+
else
|
|
29
|
+
internal_exec_sql_query(sql, raw_connection)
|
|
30
|
+
end
|
|
24
31
|
|
|
25
32
|
verified!
|
|
33
|
+
notification_payload[:affected_rows] = affected_rows
|
|
26
34
|
notification_payload[:row_count] = result.count
|
|
27
35
|
result
|
|
28
36
|
end
|
|
29
37
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
end
|
|
38
|
+
# Method `perform_query` already returns an `ActiveRecord::Result` so we have nothing to cast here. This is
|
|
39
|
+
# different to the MySQL/PostgreSQL adapters where the raw result is converted to `ActiveRecord::Result` in
|
|
40
|
+
# `cast_result`.
|
|
41
|
+
def cast_result(result)
|
|
42
|
+
result
|
|
36
43
|
end
|
|
37
44
|
|
|
45
|
+
# Returns the affected rows from results.
|
|
38
46
|
def affected_rows(raw_result)
|
|
39
|
-
column_name = lowercase_schema_reflection ?
|
|
40
|
-
raw_result
|
|
47
|
+
column_name = lowercase_schema_reflection ? "affectedrows" : "AffectedRows"
|
|
48
|
+
raw_result&.first&.fetch(column_name, nil)
|
|
41
49
|
end
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
sql = sp_executesql_sql(sql, types, params, name)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
super
|
|
51
|
+
# Returns the affected rows from results or handle.
|
|
52
|
+
def affected_rows_from_results_or_handle(raw_result, handle)
|
|
53
|
+
affected_rows(raw_result) || handle.affected_rows
|
|
50
54
|
end
|
|
51
55
|
|
|
52
56
|
def internal_exec_sql_query(sql, conn)
|
|
53
57
|
handle = internal_raw_execute(sql, conn)
|
|
54
|
-
handle_to_names_and_values(handle, ar_result: true)
|
|
58
|
+
results = handle_to_names_and_values(handle, ar_result: true)
|
|
59
|
+
|
|
60
|
+
[results, affected_rows_from_results_or_handle(results, handle)]
|
|
55
61
|
ensure
|
|
56
62
|
finish_statement_handle(handle)
|
|
57
63
|
end
|
|
58
64
|
|
|
59
65
|
def exec_delete(sql, name = nil, binds = [])
|
|
60
66
|
sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
|
|
61
|
-
super
|
|
67
|
+
super
|
|
62
68
|
end
|
|
63
69
|
|
|
64
70
|
def exec_update(sql, name = nil, binds = [])
|
|
65
71
|
sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
|
|
66
|
-
super
|
|
72
|
+
super
|
|
67
73
|
end
|
|
68
74
|
|
|
69
75
|
def begin_db_transaction
|
|
@@ -154,7 +160,6 @@ module ActiveRecord
|
|
|
154
160
|
end
|
|
155
161
|
end
|
|
156
162
|
|
|
157
|
-
|
|
158
163
|
def build_sql_for_merge_insert(insert:, insert_all:, columns_with_uniqueness_constraints:) # :nodoc:
|
|
159
164
|
insert_all.inserts.reverse! if insert.update_duplicates?
|
|
160
165
|
|
|
@@ -164,7 +169,7 @@ module ActiveRecord
|
|
|
164
169
|
SELECT *
|
|
165
170
|
FROM (
|
|
166
171
|
SELECT #{insert.send(:columns_list)}, #{partition_by_columns_with_uniqueness_constraints(columns_with_uniqueness_constraints:)}
|
|
167
|
-
FROM (#{insert
|
|
172
|
+
FROM (#{merge_insert_values_list(insert:, insert_all:)})
|
|
168
173
|
AS t1 (#{insert.send(:columns_list)})
|
|
169
174
|
) AS ranked_source
|
|
170
175
|
WHERE #{is_first_record_across_all_uniqueness_constraints(columns_with_uniqueness_constraints:)}
|
|
@@ -193,21 +198,50 @@ module ActiveRecord
|
|
|
193
198
|
sql
|
|
194
199
|
end
|
|
195
200
|
|
|
201
|
+
# For `nil` identity columns we need to ensure that the values do not match so that they are all inserted.
|
|
202
|
+
# Method is a combination of `ActiveRecord::InsertAll#values_list` and `ActiveRecord::ConnectionAdapters::SQLServer::DatabaseStatements#default_insert_value`.
|
|
203
|
+
def merge_insert_values_list(insert:, insert_all:)
|
|
204
|
+
connection = insert.send(:connection)
|
|
205
|
+
identity_index = 0
|
|
206
|
+
|
|
207
|
+
types = insert.send(:extract_types_for, insert.keys_including_timestamps)
|
|
208
|
+
|
|
209
|
+
values_list = insert_all.map_key_with_value do |key, value|
|
|
210
|
+
if Arel::Nodes::SqlLiteral === value
|
|
211
|
+
value
|
|
212
|
+
elsif insert.primary_keys.include?(key) && value.nil?
|
|
213
|
+
column = insert.model.columns_hash[key]
|
|
214
|
+
|
|
215
|
+
if column.is_identity?
|
|
216
|
+
identity_index += 1
|
|
217
|
+
table_name = quote(quote_table_name(column.table_name))
|
|
218
|
+
Arel.sql("IDENT_CURRENT(#{table_name}) + (IDENT_INCR(#{table_name}) * #{identity_index})")
|
|
219
|
+
else
|
|
220
|
+
connection.default_insert_value(column)
|
|
221
|
+
end
|
|
222
|
+
else
|
|
223
|
+
ActiveModel::Type::SerializeCastValue.serialize(type = types[key], type.cast(value))
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list))
|
|
228
|
+
end
|
|
229
|
+
|
|
196
230
|
# === SQLServer Specific ======================================== #
|
|
197
231
|
|
|
198
232
|
def execute_procedure(proc_name, *variables)
|
|
199
233
|
vars = if variables.any? && variables.first.is_a?(Hash)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
234
|
+
variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
|
|
235
|
+
else
|
|
236
|
+
variables.map { |v| quote(v) }
|
|
237
|
+
end.join(", ")
|
|
204
238
|
sql = "EXEC #{proc_name} #{vars}".strip
|
|
205
239
|
|
|
206
240
|
log(sql, "Execute Procedure") do |notification_payload|
|
|
207
241
|
with_raw_connection do |conn|
|
|
208
242
|
result = internal_raw_execute(sql, conn)
|
|
209
243
|
verified!
|
|
210
|
-
options = {
|
|
244
|
+
options = {as: :hash, cache_rows: true, timezone: ActiveRecord.default_timezone || :utc}
|
|
211
245
|
|
|
212
246
|
result.each(options) do |row|
|
|
213
247
|
r = row.with_indifferent_access
|
|
@@ -244,7 +278,7 @@ module ActiveRecord
|
|
|
244
278
|
|
|
245
279
|
rows = select_rows("DBCC USEROPTIONS WITH NO_INFOMSGS", "SCHEMA")
|
|
246
280
|
rows = rows.first if rows.size == 2 && rows.last.empty?
|
|
247
|
-
rows.
|
|
281
|
+
rows.each_with_object(HashWithIndifferentAccess.new) do |row, values|
|
|
248
282
|
if row.instance_of? Hash
|
|
249
283
|
set_option = row.values[0].gsub(/\s+/, "_")
|
|
250
284
|
user_value = row.values[1]
|
|
@@ -253,7 +287,6 @@ module ActiveRecord
|
|
|
253
287
|
user_value = row[1]
|
|
254
288
|
end
|
|
255
289
|
values[set_option] = user_value
|
|
256
|
-
values
|
|
257
290
|
end
|
|
258
291
|
end
|
|
259
292
|
|
|
@@ -307,35 +340,35 @@ module ActiveRecord
|
|
|
307
340
|
end
|
|
308
341
|
|
|
309
342
|
sql = if pk && use_output_inserted? && !database_prefix_remote_server?
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
343
|
+
table_name ||= get_table_name(sql)
|
|
344
|
+
exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)
|
|
345
|
+
|
|
346
|
+
if exclude_output_inserted
|
|
347
|
+
pk_and_types = Array(pk).map do |subkey|
|
|
348
|
+
{
|
|
349
|
+
quoted: SQLServer::Utils.extract_identifiers(subkey).quoted,
|
|
350
|
+
id_sql_type: exclude_output_inserted_id_sql_type(subkey, exclude_output_inserted)
|
|
351
|
+
}
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
<<~SQL.squish
|
|
355
|
+
DECLARE @ssaIdInsertTable table (#{pk_and_types.map { |pk_and_type| "#{pk_and_type[:quoted]} #{pk_and_type[:id_sql_type]}" }.join(", ")});
|
|
356
|
+
#{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/i), " OUTPUT #{pk_and_types.map { |pk_and_type| "INSERTED.#{pk_and_type[:quoted]}" }.join(", ")} INTO @ssaIdInsertTable"}
|
|
357
|
+
SELECT #{pk_and_types.map { |pk_and_type| "CAST(#{pk_and_type[:quoted]} AS #{pk_and_type[:id_sql_type]}) #{pk_and_type[:quoted]}" }.join(", ")} FROM @ssaIdInsertTable
|
|
358
|
+
SQL
|
|
359
|
+
else
|
|
360
|
+
returning_columns = returning || Array(pk)
|
|
361
|
+
|
|
362
|
+
if returning_columns.any?
|
|
363
|
+
returning_columns_statements = returning_columns.map { |c| " INSERTED.#{SQLServer::Utils.extract_identifiers(c).quoted}" }
|
|
364
|
+
sql.dup.insert sql.index(/ (DEFAULT )?VALUES/i), " OUTPUT" + returning_columns_statements.join(",")
|
|
365
|
+
else
|
|
366
|
+
sql
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
else
|
|
370
|
+
"#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
|
|
371
|
+
end
|
|
339
372
|
|
|
340
373
|
[sql, binds]
|
|
341
374
|
end
|
|
@@ -343,9 +376,9 @@ module ActiveRecord
|
|
|
343
376
|
# === SQLServer Specific ======================================== #
|
|
344
377
|
|
|
345
378
|
def set_identity_insert(table_name, conn, enable)
|
|
346
|
-
internal_raw_execute("SET IDENTITY_INSERT #{table_name} #{enable ?
|
|
347
|
-
rescue
|
|
348
|
-
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ?
|
|
379
|
+
internal_raw_execute("SET IDENTITY_INSERT #{table_name} #{enable ? "ON" : "OFF"}", conn, perform_do: true)
|
|
380
|
+
rescue
|
|
381
|
+
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? "ON" : "OFF"} for table #{table_name}"
|
|
349
382
|
end
|
|
350
383
|
|
|
351
384
|
# === SQLServer Specific (Executing) ============================ #
|
|
@@ -363,7 +396,7 @@ module ActiveRecord
|
|
|
363
396
|
|
|
364
397
|
def sp_executesql_sql_type(attr)
|
|
365
398
|
if attr.respond_to?(:type)
|
|
366
|
-
type = attr.type.is_a?(
|
|
399
|
+
type = attr.type.is_a?(ActiveModel::Attributes::Normalization::NormalizedValueType) ? attr.type.cast_type : attr.type
|
|
367
400
|
type = type.subtype if type.serialized?
|
|
368
401
|
|
|
369
402
|
return type.sqlserver_type if type.respond_to?(:sqlserver_type)
|
|
@@ -376,9 +409,9 @@ module ActiveRecord
|
|
|
376
409
|
value = active_model_attribute?(attr) ? attr.value_for_database : attr
|
|
377
410
|
|
|
378
411
|
if value.is_a?(Numeric)
|
|
379
|
-
value > 2_147_483_647 ? "bigint"
|
|
412
|
+
(value > 2_147_483_647) ? "bigint" : "int"
|
|
380
413
|
else
|
|
381
|
-
"nvarchar(max)"
|
|
414
|
+
"nvarchar(max)"
|
|
382
415
|
end
|
|
383
416
|
end
|
|
384
417
|
|
|
@@ -478,26 +511,24 @@ module ActiveRecord
|
|
|
478
511
|
end
|
|
479
512
|
results = handle.each(query_options)
|
|
480
513
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
514
|
+
if options[:ar_result]
|
|
515
|
+
columns = handle.fields
|
|
516
|
+
columns = columns.last if columns.any? && columns.all? { |e| e.is_a?(Array) } # If query returns multiple result sets, only return the columns of the last one.
|
|
517
|
+
columns = columns.map(&:downcase) if lowercase_schema_reflection
|
|
485
518
|
|
|
486
|
-
|
|
519
|
+
ActiveRecord::Result.new(columns, results, affected_rows: handle.affected_rows)
|
|
520
|
+
else
|
|
521
|
+
results
|
|
522
|
+
end
|
|
487
523
|
end
|
|
488
524
|
|
|
489
525
|
def finish_statement_handle(handle)
|
|
490
|
-
handle
|
|
526
|
+
handle&.cancel
|
|
491
527
|
handle
|
|
492
528
|
end
|
|
493
529
|
|
|
494
|
-
# TinyTDS returns false instead of raising an exception if connection fails.
|
|
495
|
-
# Getting around this by raising an exception ourselves while PR
|
|
496
|
-
# https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
|
|
497
530
|
def internal_raw_execute(sql, raw_connection, perform_do: false)
|
|
498
531
|
result = raw_connection.execute(sql)
|
|
499
|
-
raise TinyTds::Error, "failed to execute statement" if result.is_a?(FalseClass)
|
|
500
|
-
|
|
501
532
|
perform_do ? result.do : result
|
|
502
533
|
end
|
|
503
534
|
|
|
@@ -506,16 +537,16 @@ module ActiveRecord
|
|
|
506
537
|
return "" unless insert_all.returning
|
|
507
538
|
|
|
508
539
|
returning_values_sql = if insert_all.returning.is_a?(String)
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
540
|
+
insert_all.returning
|
|
541
|
+
else
|
|
542
|
+
Array(insert_all.returning).map do |attribute|
|
|
543
|
+
if insert.model.attribute_alias?(attribute)
|
|
544
|
+
"INSERTED.#{quote_column_name(insert.model.attribute_alias(attribute))} AS #{quote_column_name(attribute)}"
|
|
545
|
+
else
|
|
546
|
+
"INSERTED.#{quote_column_name(attribute)}"
|
|
547
|
+
end
|
|
548
|
+
end.join(",")
|
|
549
|
+
end
|
|
519
550
|
|
|
520
551
|
" OUTPUT #{returning_values_sql}"
|
|
521
552
|
end
|
|
@@ -32,20 +32,19 @@ module ActiveRecord
|
|
|
32
32
|
private
|
|
33
33
|
|
|
34
34
|
def create_database_options(options = {})
|
|
35
|
-
keys
|
|
35
|
+
keys = [:collate]
|
|
36
36
|
copts = @connection_parameters
|
|
37
|
-
|
|
37
|
+
{
|
|
38
38
|
collate: copts[:collation]
|
|
39
39
|
}.merge(options.symbolize_keys).select { |_, v|
|
|
40
40
|
v.present?
|
|
41
41
|
}.slice(*keys).map { |k, v|
|
|
42
42
|
"#{k.to_s.upcase} #{v}"
|
|
43
43
|
}.join(" ")
|
|
44
|
-
options
|
|
45
44
|
end
|
|
46
45
|
|
|
47
46
|
def create_database_edition_options(options = {})
|
|
48
|
-
keys
|
|
47
|
+
keys = [:maxsize, :edition, :service_objective]
|
|
49
48
|
copts = @connection_parameters
|
|
50
49
|
edition_options = {
|
|
51
50
|
maxsize: copts[:azure_maxsize],
|
|
@@ -55,11 +55,11 @@ module ActiveRecord
|
|
|
55
55
|
cast_type = lookup_cast_type(sql_type)
|
|
56
56
|
|
|
57
57
|
simple_type = SqlTypeMetadata.new(
|
|
58
|
-
sql_type:
|
|
59
|
-
type:
|
|
60
|
-
limit:
|
|
58
|
+
sql_type: sql_type,
|
|
59
|
+
type: cast_type.type,
|
|
60
|
+
limit: cast_type.limit,
|
|
61
61
|
precision: cast_type.precision,
|
|
62
|
-
scale:
|
|
62
|
+
scale: cast_type.scale
|
|
63
63
|
)
|
|
64
64
|
|
|
65
65
|
SQLServer::TypeMetadata.new(simple_type, **sqlserver_options)
|
|
@@ -79,7 +79,7 @@ module ActiveRecord
|
|
|
79
79
|
|
|
80
80
|
def quote_default_expression(value, column)
|
|
81
81
|
cast_type = lookup_cast_type(column.sql_type)
|
|
82
|
-
if cast_type.type == :uuid && value.is_a?(String) && value.include?(
|
|
82
|
+
if cast_type.type == :uuid && value.is_a?(String) && value.include?("()")
|
|
83
83
|
value
|
|
84
84
|
else
|
|
85
85
|
super
|
|
@@ -87,7 +87,7 @@ module ActiveRecord
|
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
def quoted_true
|
|
90
|
-
|
|
90
|
+
"1"
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
def unquoted_true
|
|
@@ -95,7 +95,7 @@ module ActiveRecord
|
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
def quoted_false
|
|
98
|
-
|
|
98
|
+
"0"
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
def unquoted_false
|
|
@@ -6,10 +6,18 @@ module ActiveRecord
|
|
|
6
6
|
class SchemaCreation < SchemaCreation
|
|
7
7
|
private
|
|
8
8
|
|
|
9
|
+
delegate :quoted_include_columns_for_index, to: :@conn
|
|
10
|
+
|
|
9
11
|
def supports_index_using?
|
|
10
12
|
false
|
|
11
13
|
end
|
|
12
14
|
|
|
15
|
+
def visit_ColumnDefinition(o)
|
|
16
|
+
column_sql = super
|
|
17
|
+
column_sql = column_sql.sub(" #{o.sql_type}", "") if o.options[:as].present?
|
|
18
|
+
column_sql
|
|
19
|
+
end
|
|
20
|
+
|
|
13
21
|
def visit_TableDefinition(o)
|
|
14
22
|
if_not_exists = o.if_not_exists
|
|
15
23
|
|
|
@@ -44,25 +52,29 @@ module ActiveRecord
|
|
|
44
52
|
sql << "INDEX"
|
|
45
53
|
sql << "#{quote_column_name(index.name)} ON #{quote_table_name(index.table)}"
|
|
46
54
|
sql << "(#{quoted_columns(index)})"
|
|
55
|
+
sql << "INCLUDE (#{quoted_include_columns(index.include)})" if supports_index_include? && index.include
|
|
47
56
|
sql << "WHERE #{index.where}" if index.where
|
|
48
57
|
|
|
49
58
|
sql.join(" ")
|
|
50
59
|
end
|
|
51
60
|
|
|
61
|
+
def quoted_include_columns(o)
|
|
62
|
+
(String === o) ? o : quoted_include_columns_for_index(o)
|
|
63
|
+
end
|
|
64
|
+
|
|
52
65
|
def add_column_options!(sql, options)
|
|
53
|
-
sql << " DEFAULT #{
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if options[:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if options[:
|
|
61
|
-
sql << "
|
|
62
|
-
|
|
63
|
-
if options[:primary_key] == true
|
|
64
|
-
sql << " PRIMARY KEY"
|
|
66
|
+
sql << " DEFAULT #{quote_default_expression_for_column_definition(options[:default], options[:column])}" if options_include_default?(options)
|
|
67
|
+
|
|
68
|
+
sql << " COLLATE #{options[:collation]}" if options[:collation].present?
|
|
69
|
+
sql << " NOT NULL" if options[:null] == false
|
|
70
|
+
sql << " IDENTITY(1,1)" if options[:is_identity] == true
|
|
71
|
+
sql << " PRIMARY KEY" if options[:primary_key] == true
|
|
72
|
+
|
|
73
|
+
if (as = options[:as])
|
|
74
|
+
sql << " AS #{as}"
|
|
75
|
+
sql << " PERSISTED" if options[:stored]
|
|
65
76
|
end
|
|
77
|
+
|
|
66
78
|
sql
|
|
67
79
|
end
|
|
68
80
|
|
|
@@ -4,22 +4,31 @@ module ActiveRecord
|
|
|
4
4
|
module ConnectionAdapters
|
|
5
5
|
module SQLServer
|
|
6
6
|
class SchemaDumper < ConnectionAdapters::SchemaDumper
|
|
7
|
-
|
|
8
|
-
"text",
|
|
9
|
-
"ntext",
|
|
10
|
-
"varchar(max)",
|
|
11
|
-
"nvarchar(max)",
|
|
12
|
-
"varbinary(max)"
|
|
13
|
-
].freeze
|
|
7
|
+
SQLSERVER_NO_LIMIT_TYPES = %w[text ntext varchar(max) nvarchar(max) varbinary(max)].freeze
|
|
14
8
|
|
|
15
9
|
private
|
|
16
10
|
|
|
11
|
+
def prepare_column_options(column)
|
|
12
|
+
spec = super
|
|
13
|
+
|
|
14
|
+
if @connection.supports_virtual_columns? && column.virtual?
|
|
15
|
+
spec[:as] = extract_expression_for_virtual_column(column)
|
|
16
|
+
spec[:stored] = column.virtual_stored?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
spec
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def extract_expression_for_virtual_column(column)
|
|
23
|
+
column.default_function.inspect
|
|
24
|
+
end
|
|
25
|
+
|
|
17
26
|
def explicit_primary_key_default?(column)
|
|
18
27
|
column.type == :integer && !column.is_identity?
|
|
19
28
|
end
|
|
20
29
|
|
|
21
30
|
def schema_limit(column)
|
|
22
|
-
return if
|
|
31
|
+
return if SQLSERVER_NO_LIMIT_TYPES.include?(column.sql_type)
|
|
23
32
|
|
|
24
33
|
super
|
|
25
34
|
end
|