activerecord-sqlserver-adapter 8.0.10 → 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 -68
- 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 +118 -83
- 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 +365 -161
- 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/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/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 +6 -6
- data/test/support/rake_helpers.rb +6 -10
- metadata +12 -107
|
@@ -19,47 +19,57 @@ module ActiveRecord
|
|
|
19
19
|
sql = sp_executesql_sql(sql, types, params, notification_payload[:name])
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
29
31
|
|
|
30
32
|
verified!
|
|
33
|
+
notification_payload[:affected_rows] = affected_rows
|
|
31
34
|
notification_payload[:row_count] = result.count
|
|
32
35
|
result
|
|
33
36
|
end
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
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
|
|
41
43
|
end
|
|
42
44
|
|
|
45
|
+
# Returns the affected rows from results.
|
|
43
46
|
def affected_rows(raw_result)
|
|
44
|
-
column_name = lowercase_schema_reflection ?
|
|
45
|
-
raw_result
|
|
47
|
+
column_name = lowercase_schema_reflection ? "affectedrows" : "AffectedRows"
|
|
48
|
+
raw_result&.first&.fetch(column_name, nil)
|
|
49
|
+
end
|
|
50
|
+
|
|
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
|
|
46
54
|
end
|
|
47
55
|
|
|
48
56
|
def internal_exec_sql_query(sql, conn)
|
|
49
57
|
handle = internal_raw_execute(sql, conn)
|
|
50
|
-
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)]
|
|
51
61
|
ensure
|
|
52
62
|
finish_statement_handle(handle)
|
|
53
63
|
end
|
|
54
64
|
|
|
55
65
|
def exec_delete(sql, name = nil, binds = [])
|
|
56
66
|
sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
|
|
57
|
-
super
|
|
67
|
+
super
|
|
58
68
|
end
|
|
59
69
|
|
|
60
70
|
def exec_update(sql, name = nil, binds = [])
|
|
61
71
|
sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
|
|
62
|
-
super
|
|
72
|
+
super
|
|
63
73
|
end
|
|
64
74
|
|
|
65
75
|
def begin_db_transaction
|
|
@@ -150,7 +160,6 @@ module ActiveRecord
|
|
|
150
160
|
end
|
|
151
161
|
end
|
|
152
162
|
|
|
153
|
-
|
|
154
163
|
def build_sql_for_merge_insert(insert:, insert_all:, columns_with_uniqueness_constraints:) # :nodoc:
|
|
155
164
|
insert_all.inserts.reverse! if insert.update_duplicates?
|
|
156
165
|
|
|
@@ -160,7 +169,7 @@ module ActiveRecord
|
|
|
160
169
|
SELECT *
|
|
161
170
|
FROM (
|
|
162
171
|
SELECT #{insert.send(:columns_list)}, #{partition_by_columns_with_uniqueness_constraints(columns_with_uniqueness_constraints:)}
|
|
163
|
-
FROM (#{insert
|
|
172
|
+
FROM (#{merge_insert_values_list(insert:, insert_all:)})
|
|
164
173
|
AS t1 (#{insert.send(:columns_list)})
|
|
165
174
|
) AS ranked_source
|
|
166
175
|
WHERE #{is_first_record_across_all_uniqueness_constraints(columns_with_uniqueness_constraints:)}
|
|
@@ -189,21 +198,50 @@ module ActiveRecord
|
|
|
189
198
|
sql
|
|
190
199
|
end
|
|
191
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
|
+
|
|
192
230
|
# === SQLServer Specific ======================================== #
|
|
193
231
|
|
|
194
232
|
def execute_procedure(proc_name, *variables)
|
|
195
233
|
vars = if variables.any? && variables.first.is_a?(Hash)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
234
|
+
variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
|
|
235
|
+
else
|
|
236
|
+
variables.map { |v| quote(v) }
|
|
237
|
+
end.join(", ")
|
|
200
238
|
sql = "EXEC #{proc_name} #{vars}".strip
|
|
201
239
|
|
|
202
240
|
log(sql, "Execute Procedure") do |notification_payload|
|
|
203
241
|
with_raw_connection do |conn|
|
|
204
242
|
result = internal_raw_execute(sql, conn)
|
|
205
243
|
verified!
|
|
206
|
-
options = {
|
|
244
|
+
options = {as: :hash, cache_rows: true, timezone: ActiveRecord.default_timezone || :utc}
|
|
207
245
|
|
|
208
246
|
result.each(options) do |row|
|
|
209
247
|
r = row.with_indifferent_access
|
|
@@ -240,7 +278,7 @@ module ActiveRecord
|
|
|
240
278
|
|
|
241
279
|
rows = select_rows("DBCC USEROPTIONS WITH NO_INFOMSGS", "SCHEMA")
|
|
242
280
|
rows = rows.first if rows.size == 2 && rows.last.empty?
|
|
243
|
-
rows.
|
|
281
|
+
rows.each_with_object(HashWithIndifferentAccess.new) do |row, values|
|
|
244
282
|
if row.instance_of? Hash
|
|
245
283
|
set_option = row.values[0].gsub(/\s+/, "_")
|
|
246
284
|
user_value = row.values[1]
|
|
@@ -249,7 +287,6 @@ module ActiveRecord
|
|
|
249
287
|
user_value = row[1]
|
|
250
288
|
end
|
|
251
289
|
values[set_option] = user_value
|
|
252
|
-
values
|
|
253
290
|
end
|
|
254
291
|
end
|
|
255
292
|
|
|
@@ -303,35 +340,35 @@ module ActiveRecord
|
|
|
303
340
|
end
|
|
304
341
|
|
|
305
342
|
sql = if pk && use_output_inserted? && !database_prefix_remote_server?
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
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
|
|
335
372
|
|
|
336
373
|
[sql, binds]
|
|
337
374
|
end
|
|
@@ -339,9 +376,9 @@ module ActiveRecord
|
|
|
339
376
|
# === SQLServer Specific ======================================== #
|
|
340
377
|
|
|
341
378
|
def set_identity_insert(table_name, conn, enable)
|
|
342
|
-
internal_raw_execute("SET IDENTITY_INSERT #{table_name} #{enable ?
|
|
343
|
-
rescue
|
|
344
|
-
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}"
|
|
345
382
|
end
|
|
346
383
|
|
|
347
384
|
# === SQLServer Specific (Executing) ============================ #
|
|
@@ -359,7 +396,7 @@ module ActiveRecord
|
|
|
359
396
|
|
|
360
397
|
def sp_executesql_sql_type(attr)
|
|
361
398
|
if attr.respond_to?(:type)
|
|
362
|
-
type = attr.type.is_a?(
|
|
399
|
+
type = attr.type.is_a?(ActiveModel::Attributes::Normalization::NormalizedValueType) ? attr.type.cast_type : attr.type
|
|
363
400
|
type = type.subtype if type.serialized?
|
|
364
401
|
|
|
365
402
|
return type.sqlserver_type if type.respond_to?(:sqlserver_type)
|
|
@@ -372,9 +409,9 @@ module ActiveRecord
|
|
|
372
409
|
value = active_model_attribute?(attr) ? attr.value_for_database : attr
|
|
373
410
|
|
|
374
411
|
if value.is_a?(Numeric)
|
|
375
|
-
value > 2_147_483_647 ? "bigint"
|
|
412
|
+
(value > 2_147_483_647) ? "bigint" : "int"
|
|
376
413
|
else
|
|
377
|
-
"nvarchar(max)"
|
|
414
|
+
"nvarchar(max)"
|
|
378
415
|
end
|
|
379
416
|
end
|
|
380
417
|
|
|
@@ -474,26 +511,24 @@ module ActiveRecord
|
|
|
474
511
|
end
|
|
475
512
|
results = handle.each(query_options)
|
|
476
513
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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
|
|
481
518
|
|
|
482
|
-
|
|
519
|
+
ActiveRecord::Result.new(columns, results, affected_rows: handle.affected_rows)
|
|
520
|
+
else
|
|
521
|
+
results
|
|
522
|
+
end
|
|
483
523
|
end
|
|
484
524
|
|
|
485
525
|
def finish_statement_handle(handle)
|
|
486
|
-
handle
|
|
526
|
+
handle&.cancel
|
|
487
527
|
handle
|
|
488
528
|
end
|
|
489
529
|
|
|
490
|
-
# TinyTDS returns false instead of raising an exception if connection fails.
|
|
491
|
-
# Getting around this by raising an exception ourselves while PR
|
|
492
|
-
# https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
|
|
493
530
|
def internal_raw_execute(sql, raw_connection, perform_do: false)
|
|
494
531
|
result = raw_connection.execute(sql)
|
|
495
|
-
raise TinyTds::Error, "failed to execute statement" if result.is_a?(FalseClass)
|
|
496
|
-
|
|
497
532
|
perform_do ? result.do : result
|
|
498
533
|
end
|
|
499
534
|
|
|
@@ -502,16 +537,16 @@ module ActiveRecord
|
|
|
502
537
|
return "" unless insert_all.returning
|
|
503
538
|
|
|
504
539
|
returning_values_sql = if insert_all.returning.is_a?(String)
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
|
515
550
|
|
|
516
551
|
" OUTPUT #{returning_values_sql}"
|
|
517
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
|