activerecord-sqlserver-adapter 8.0.12 → 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 +13 -84
- data/Dockerfile.ci +1 -1
- data/Gemfile +9 -12
- 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 +170 -172
- 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 +119 -67
- 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 -61
- data/test/cases/change_column_null_test_sqlserver.rb +16 -46
- 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 +8 -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 -39
- 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 +13 -108
|
@@ -4,13 +4,6 @@ module ActiveRecord
|
|
|
4
4
|
module ConnectionAdapters
|
|
5
5
|
module SQLServer
|
|
6
6
|
module SchemaStatements
|
|
7
|
-
MERGE_TARGET_IDENTIFIER = /(?:\[[^\]]+\]|[a-z0-9_-]+)/i # :nodoc:
|
|
8
|
-
MERGE_TARGET_TABLE_NAME = /\A\s*MERGE\s+INTO\s+(#{MERGE_TARGET_IDENTIFIER}(?:\s*\.#{MERGE_TARGET_IDENTIFIER}){0,2})\s+(?:AS|WITH|USING)/i # :nodoc:
|
|
9
|
-
|
|
10
|
-
def native_database_types
|
|
11
|
-
@native_database_types ||= initialize_native_database_types.freeze
|
|
12
|
-
end
|
|
13
|
-
|
|
14
7
|
def create_table(table_name, **options)
|
|
15
8
|
res = super
|
|
16
9
|
clear_cache!
|
|
@@ -39,19 +32,24 @@ module ActiveRecord
|
|
|
39
32
|
end
|
|
40
33
|
|
|
41
34
|
def indexes(table_name)
|
|
42
|
-
data =
|
|
35
|
+
data = begin
|
|
36
|
+
select("EXEC sp_helpindex #{quote(table_name)}", "SCHEMA")
|
|
37
|
+
rescue
|
|
38
|
+
[]
|
|
39
|
+
end
|
|
43
40
|
|
|
44
41
|
data.reduce([]) do |indexes, index|
|
|
45
|
-
if index[
|
|
42
|
+
if index["index_description"].match?(/primary key/)
|
|
46
43
|
indexes
|
|
47
44
|
else
|
|
48
|
-
name
|
|
49
|
-
unique
|
|
50
|
-
where
|
|
51
|
-
|
|
45
|
+
name = index["index_name"]
|
|
46
|
+
unique = index["index_description"].match?(/unique/)
|
|
47
|
+
where = select_value("SELECT [filter_definition] FROM sys.indexes WHERE name = #{quote(name)}", "SCHEMA")
|
|
48
|
+
include_columns = index_include_columns(table_name, name)
|
|
49
|
+
orders = {}
|
|
52
50
|
columns = []
|
|
53
51
|
|
|
54
|
-
index[
|
|
52
|
+
index["index_keys"].split(",").each do |column|
|
|
55
53
|
column.strip!
|
|
56
54
|
|
|
57
55
|
if column.end_with?("(-)")
|
|
@@ -62,43 +60,75 @@ module ActiveRecord
|
|
|
62
60
|
columns << column
|
|
63
61
|
end
|
|
64
62
|
|
|
65
|
-
indexes << IndexDefinition.new(table_name, name, unique, columns, where: where, orders: orders)
|
|
63
|
+
indexes << IndexDefinition.new(table_name, name, unique, columns, where: where, orders: orders, include: include_columns.presence)
|
|
66
64
|
end
|
|
67
65
|
end
|
|
68
66
|
end
|
|
69
67
|
|
|
68
|
+
def index_include_columns(table_name, index_name)
|
|
69
|
+
sql = <<~SQL
|
|
70
|
+
SELECT
|
|
71
|
+
ic.index_id,
|
|
72
|
+
c.name AS column_name
|
|
73
|
+
FROM
|
|
74
|
+
sys.indexes i
|
|
75
|
+
JOIN
|
|
76
|
+
sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
|
77
|
+
JOIN
|
|
78
|
+
sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
|
79
|
+
WHERE
|
|
80
|
+
i.object_id = OBJECT_ID('#{table_name}')
|
|
81
|
+
AND i.name = '#{index_name}'
|
|
82
|
+
AND ic.is_included_column = 1;
|
|
83
|
+
SQL
|
|
84
|
+
|
|
85
|
+
select_all(sql, "SCHEMA").map { |row| row["column_name"] }
|
|
86
|
+
end
|
|
87
|
+
|
|
70
88
|
def columns(table_name)
|
|
71
89
|
return [] if table_name.blank?
|
|
72
90
|
|
|
73
|
-
column_definitions(table_name)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
new_column(
|
|
77
|
-
ci[:name],
|
|
78
|
-
ci[:default_value],
|
|
79
|
-
sql_type_metadata,
|
|
80
|
-
ci[:null],
|
|
81
|
-
ci[:default_function],
|
|
82
|
-
ci[:collation],
|
|
83
|
-
nil,
|
|
84
|
-
sqlserver_options
|
|
85
|
-
)
|
|
91
|
+
definitions = column_definitions(table_name)
|
|
92
|
+
definitions.map do |field|
|
|
93
|
+
new_column_from_field(table_name, field, definitions)
|
|
86
94
|
end
|
|
87
95
|
end
|
|
88
96
|
|
|
89
|
-
def
|
|
97
|
+
def new_column_from_field(_table_name, field, _definitions)
|
|
98
|
+
sqlserver_options = field.slice(:ordinal_position, :is_primary, :is_identity, :table_name)
|
|
99
|
+
sql_type_metadata = fetch_type_metadata(field[:type], sqlserver_options)
|
|
100
|
+
generated_type = extract_generated_type(field)
|
|
101
|
+
|
|
102
|
+
default_function = if generated_type.present?
|
|
103
|
+
field[:computed_formula]
|
|
104
|
+
else
|
|
105
|
+
field[:default_function]
|
|
106
|
+
end
|
|
107
|
+
|
|
90
108
|
SQLServer::Column.new(
|
|
91
|
-
name,
|
|
92
|
-
|
|
109
|
+
field[:name],
|
|
110
|
+
lookup_cast_type(field[:type]),
|
|
111
|
+
field[:default_value],
|
|
93
112
|
sql_type_metadata,
|
|
94
|
-
null,
|
|
113
|
+
field[:null],
|
|
95
114
|
default_function,
|
|
96
|
-
collation: collation,
|
|
97
|
-
comment:
|
|
115
|
+
collation: field[:collation],
|
|
116
|
+
comment: nil,
|
|
117
|
+
generated_type: generated_type,
|
|
98
118
|
**sqlserver_options
|
|
99
119
|
)
|
|
100
120
|
end
|
|
101
121
|
|
|
122
|
+
def extract_generated_type(field)
|
|
123
|
+
if field[:is_computed]
|
|
124
|
+
if field[:is_persisted]
|
|
125
|
+
:stored
|
|
126
|
+
else
|
|
127
|
+
:virtual
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
102
132
|
def primary_keys(table_name)
|
|
103
133
|
primaries = primary_keys_select(table_name)
|
|
104
134
|
primaries.present? ? primaries : identity_columns(table_name).map(&:name)
|
|
@@ -107,8 +137,8 @@ module ActiveRecord
|
|
|
107
137
|
def primary_keys_select(table_name)
|
|
108
138
|
identifier = database_prefix_identifier(table_name)
|
|
109
139
|
database = identifier.fully_qualified_database_quoted
|
|
110
|
-
sql = %
|
|
111
|
-
SELECT #{lowercase_schema_reflection_sql(
|
|
140
|
+
sql = %(
|
|
141
|
+
SELECT #{lowercase_schema_reflection_sql("KCU.COLUMN_NAME")} AS [name]
|
|
112
142
|
FROM #{database}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
|
|
113
143
|
LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
|
|
114
144
|
ON KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
|
|
@@ -116,11 +146,15 @@ module ActiveRecord
|
|
|
116
146
|
AND KCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG
|
|
117
147
|
AND KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
|
|
118
148
|
AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
|
|
119
|
-
WHERE KCU.TABLE_NAME = #{prepared_statements ?
|
|
120
|
-
AND KCU.TABLE_SCHEMA = #{identifier.schema.blank?
|
|
149
|
+
WHERE KCU.TABLE_NAME = #{prepared_statements ? "@0" : quote(identifier.object)}
|
|
150
|
+
AND KCU.TABLE_SCHEMA = #{if identifier.schema.blank?
|
|
151
|
+
"schema_name()"
|
|
152
|
+
else
|
|
153
|
+
(prepared_statements ? "@1" : quote(identifier.schema))
|
|
154
|
+
end}
|
|
121
155
|
AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
|
|
122
156
|
ORDER BY KCU.ORDINAL_POSITION ASC
|
|
123
|
-
|
|
157
|
+
).gsub(/[[:space:]]/, " ")
|
|
124
158
|
|
|
125
159
|
binds = []
|
|
126
160
|
nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
|
|
@@ -163,10 +197,10 @@ module ActiveRecord
|
|
|
163
197
|
column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s }
|
|
164
198
|
without_constraints = options.key?(:default) || options.key?(:limit)
|
|
165
199
|
default = if !options.key?(:default) && column_object
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
200
|
+
column_object.default
|
|
201
|
+
else
|
|
202
|
+
options[:default]
|
|
203
|
+
end
|
|
170
204
|
|
|
171
205
|
if without_constraints || (column_object && column_object.type != type.to_sym)
|
|
172
206
|
remove_default_constraint(table_name, column_name)
|
|
@@ -216,7 +250,7 @@ module ActiveRecord
|
|
|
216
250
|
end
|
|
217
251
|
|
|
218
252
|
def rename_index(table_name, old_name, new_name)
|
|
219
|
-
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
|
|
253
|
+
raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long (#{new_name.length} characters); the limit is #{index_name_length} characters" if new_name.length > index_name_length
|
|
220
254
|
|
|
221
255
|
identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{old_name}")
|
|
222
256
|
execute_procedure :sp_rename, identifier.quoted, new_name, "INDEX"
|
|
@@ -244,7 +278,7 @@ module ActiveRecord
|
|
|
244
278
|
identifier = SQLServer::Utils.extract_identifiers(table_name)
|
|
245
279
|
fk_info = execute_procedure :sp_fkeys, nil, identifier.schema, nil, identifier.object, identifier.schema
|
|
246
280
|
|
|
247
|
-
grouped_fk = fk_info.group_by { |row| row["FK_NAME"] }.values.each { |group| group.sort_by! { |row| row["KEY_SEQ"] } }
|
|
281
|
+
grouped_fk = fk_info.group_by { |row| row["FK_NAME"] }.values.each { |group| group.sort_by! { |row| row["KEY_SEQ"] } }.reverse
|
|
248
282
|
grouped_fk.map do |group|
|
|
249
283
|
row = group.first
|
|
250
284
|
options = {
|
|
@@ -296,20 +330,20 @@ module ActiveRecord
|
|
|
296
330
|
end
|
|
297
331
|
|
|
298
332
|
def type_to_sql(type, limit: nil, precision: nil, scale: nil, **)
|
|
299
|
-
type_limitable = %w
|
|
333
|
+
type_limitable = %w[string integer float char nchar varchar nvarchar binary_basic].include?(type.to_s)
|
|
300
334
|
limit = nil unless type_limitable
|
|
301
335
|
|
|
302
336
|
case type.to_s
|
|
303
337
|
when "integer"
|
|
304
338
|
case limit
|
|
305
|
-
when 1
|
|
306
|
-
when 2
|
|
307
|
-
when 3..4, nil
|
|
308
|
-
when 5..8
|
|
339
|
+
when 1 then "tinyint"
|
|
340
|
+
when 2 then "smallint"
|
|
341
|
+
when 3..4, nil then "integer"
|
|
342
|
+
when 5..8 then "bigint"
|
|
309
343
|
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
|
310
344
|
end
|
|
311
345
|
when "time" # https://learn.microsoft.com/en-us/sql/t-sql/data-types/time-transact-sql
|
|
312
|
-
column_type_sql = type.to_s
|
|
346
|
+
column_type_sql = type.to_s.dup
|
|
313
347
|
if precision
|
|
314
348
|
if (0..7) === precision
|
|
315
349
|
column_type_sql << "(#{precision})"
|
|
@@ -346,18 +380,18 @@ module ActiveRecord
|
|
|
346
380
|
# In SQL Server only the first column added should have the `ADD` keyword.
|
|
347
381
|
def add_timestamps(table_name, **options)
|
|
348
382
|
fragments = add_timestamps_for_alter(table_name, **options)
|
|
349
|
-
fragments[1..].each { |fragment| fragment.sub!(
|
|
350
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(
|
|
383
|
+
fragments[1..].each { |fragment| fragment.sub!("ADD ", "") }
|
|
384
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(", ")}"
|
|
351
385
|
end
|
|
352
386
|
|
|
353
387
|
def columns_for_distinct(columns, orders)
|
|
354
388
|
order_columns = orders.reject(&:blank?).map { |s|
|
|
355
389
|
s = visitor.compile(s) unless s.is_a?(String)
|
|
356
390
|
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
|
|
357
|
-
|
|
391
|
+
.gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
|
|
358
392
|
}
|
|
359
|
-
|
|
360
|
-
|
|
393
|
+
.reject(&:blank?)
|
|
394
|
+
.reject { |s| columns.include?(s) }
|
|
361
395
|
|
|
362
396
|
order_columns_aliased = order_columns.map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
|
363
397
|
|
|
@@ -377,12 +411,7 @@ module ActiveRecord
|
|
|
377
411
|
if !null.nil? && null == false && !default.nil?
|
|
378
412
|
execute("UPDATE #{table_id} SET #{column_id}=#{quote(default)} WHERE #{column_id} IS NULL")
|
|
379
413
|
end
|
|
380
|
-
|
|
381
|
-
# If datetime column has precision then it should be datetime2 column.
|
|
382
|
-
column_type = column.type
|
|
383
|
-
column_type = :datetime2 if column.type.to_s == "datetime" && !column.precision.nil?
|
|
384
|
-
|
|
385
|
-
sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column_type, limit: column.limit, precision: column.precision, scale: column.scale}"
|
|
414
|
+
sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, limit: column.limit, precision: column.precision, scale: column.scale}"
|
|
386
415
|
sql += " NOT NULL" if !null.nil? && null == false
|
|
387
416
|
|
|
388
417
|
execute sql
|
|
@@ -410,24 +439,33 @@ module ActiveRecord
|
|
|
410
439
|
# Returns an array of schema names.
|
|
411
440
|
def schema_names
|
|
412
441
|
sql = <<~SQL.squish
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
442
|
+
SELECT name
|
|
443
|
+
FROM sys.schemas
|
|
444
|
+
WHERE
|
|
445
|
+
name NOT LIKE 'db_%' AND
|
|
446
|
+
name NOT IN ('INFORMATION_SCHEMA', 'sys', 'guest')
|
|
418
447
|
SQL
|
|
419
448
|
|
|
420
449
|
query_values(sql, "SCHEMA")
|
|
421
450
|
end
|
|
422
451
|
|
|
452
|
+
def quoted_include_columns_for_index(column_names) # :nodoc:
|
|
453
|
+
return quote_column_name(column_names) if column_names.is_a?(Symbol)
|
|
454
|
+
|
|
455
|
+
quoted_columns = column_names.each_with_object({}) do |name, result|
|
|
456
|
+
result[name.to_sym] = quote_column_name(name).dup
|
|
457
|
+
end
|
|
458
|
+
add_options_for_index_columns(quoted_columns).values.join(", ")
|
|
459
|
+
end
|
|
460
|
+
|
|
423
461
|
private
|
|
424
462
|
|
|
425
463
|
def data_source_sql(name = nil, type: nil)
|
|
426
464
|
scope = quoted_scope(name, type: type)
|
|
427
465
|
|
|
428
|
-
table_schema
|
|
429
|
-
table_name
|
|
430
|
-
database
|
|
466
|
+
table_schema = lowercase_schema_reflection_sql("TABLE_SCHEMA")
|
|
467
|
+
table_name = lowercase_schema_reflection_sql("TABLE_NAME")
|
|
468
|
+
database = scope[:database].present? ? "#{scope[:database]}." : ""
|
|
431
469
|
table_catalog = scope[:database].present? ? quote(scope[:database]) : "DB_NAME()"
|
|
432
470
|
|
|
433
471
|
sql = "SELECT "
|
|
@@ -457,46 +495,10 @@ module ActiveRecord
|
|
|
457
495
|
|
|
458
496
|
# === SQLServer Specific ======================================== #
|
|
459
497
|
|
|
460
|
-
def initialize_native_database_types
|
|
461
|
-
{
|
|
462
|
-
primary_key: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY",
|
|
463
|
-
primary_key_nonclustered: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED",
|
|
464
|
-
integer: { name: "int", limit: 4 },
|
|
465
|
-
bigint: { name: "bigint" },
|
|
466
|
-
boolean: { name: "bit" },
|
|
467
|
-
decimal: { name: "decimal" },
|
|
468
|
-
money: { name: "money" },
|
|
469
|
-
smallmoney: { name: "smallmoney" },
|
|
470
|
-
float: { name: "float" },
|
|
471
|
-
real: { name: "real" },
|
|
472
|
-
date: { name: "date" },
|
|
473
|
-
datetime: { name: "datetime" },
|
|
474
|
-
datetime2: { name: "datetime2" },
|
|
475
|
-
datetimeoffset: { name: "datetimeoffset" },
|
|
476
|
-
smalldatetime: { name: "smalldatetime" },
|
|
477
|
-
timestamp: { name: "datetime2(6)" },
|
|
478
|
-
time: { name: "time" },
|
|
479
|
-
char: { name: "char" },
|
|
480
|
-
varchar: { name: "varchar", limit: 8000 },
|
|
481
|
-
varchar_max: { name: "varchar(max)" },
|
|
482
|
-
text_basic: { name: "text" },
|
|
483
|
-
nchar: { name: "nchar" },
|
|
484
|
-
string: { name: "nvarchar", limit: 4000 },
|
|
485
|
-
text: { name: "nvarchar(max)" },
|
|
486
|
-
ntext: { name: "ntext" },
|
|
487
|
-
binary_basic: { name: "binary" },
|
|
488
|
-
varbinary: { name: "varbinary", limit: 8000 },
|
|
489
|
-
binary: { name: "varbinary(max)" },
|
|
490
|
-
uuid: { name: "uniqueidentifier" },
|
|
491
|
-
ss_timestamp: { name: "timestamp" },
|
|
492
|
-
json: { name: "nvarchar(max)" }
|
|
493
|
-
}
|
|
494
|
-
end
|
|
495
|
-
|
|
496
498
|
def column_definitions(table_name)
|
|
497
|
-
identifier
|
|
498
|
-
database
|
|
499
|
-
view_exists
|
|
499
|
+
identifier = database_prefix_identifier(table_name)
|
|
500
|
+
database = identifier.fully_qualified_database_quoted
|
|
501
|
+
view_exists = view_exists?(table_name)
|
|
500
502
|
|
|
501
503
|
if view_exists
|
|
502
504
|
sql = <<~SQL
|
|
@@ -518,40 +520,30 @@ module ActiveRecord
|
|
|
518
520
|
results = internal_exec_query(sql, "SCHEMA", binds)
|
|
519
521
|
raise ActiveRecord::StatementInvalid, "Table '#{table_name}' doesn't exist" if results.empty?
|
|
520
522
|
|
|
521
|
-
|
|
522
|
-
col =
|
|
523
|
-
name: ci["name"],
|
|
524
|
-
numeric_scale: ci["numeric_scale"],
|
|
525
|
-
numeric_precision: ci["numeric_precision"],
|
|
526
|
-
datetime_precision: ci["datetime_precision"],
|
|
527
|
-
collation: ci["collation"],
|
|
528
|
-
ordinal_position: ci["ordinal_position"],
|
|
529
|
-
length: ci["length"]
|
|
530
|
-
}
|
|
523
|
+
results.map do |ci|
|
|
524
|
+
col = ci.slice("name", "numeric_scale", "numeric_precision", "datetime_precision", "collation", "ordinal_position", "length", "is_computed", "is_persisted", "computed_formula").symbolize_keys
|
|
531
525
|
|
|
532
526
|
col[:table_name] = view_exists ? view_table_name(table_name) : table_name
|
|
533
527
|
col[:type] = column_type(ci: ci)
|
|
534
|
-
col[:default_value], col[:default_function] = default_value_and_function(default: ci[
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
col[:null] = ci[
|
|
543
|
-
col[:is_primary] = ci[
|
|
544
|
-
|
|
545
|
-
if [true, false].include?(ci[
|
|
546
|
-
|
|
528
|
+
col[:default_value], col[:default_function] = default_value_and_function(default: ci["default_value"],
|
|
529
|
+
name: ci["name"],
|
|
530
|
+
type: col[:type],
|
|
531
|
+
original_type: ci["type"],
|
|
532
|
+
view_exists: view_exists,
|
|
533
|
+
table_name: table_name,
|
|
534
|
+
default_functions: default_functions)
|
|
535
|
+
|
|
536
|
+
col[:null] = ci["is_nullable"].to_i == 1
|
|
537
|
+
col[:is_primary] = ci["is_primary"].to_i == 1
|
|
538
|
+
|
|
539
|
+
col[:is_identity] = if [true, false].include?(ci["is_identity"])
|
|
540
|
+
ci["is_identity"]
|
|
547
541
|
else
|
|
548
|
-
|
|
542
|
+
ci["is_identity"].to_i == 1
|
|
549
543
|
end
|
|
550
544
|
|
|
551
545
|
col
|
|
552
546
|
end
|
|
553
|
-
|
|
554
|
-
columns
|
|
555
547
|
end
|
|
556
548
|
|
|
557
549
|
def default_value_and_function(default:, name:, type:, original_type:, view_exists:, table_name:, default_functions:)
|
|
@@ -573,9 +565,9 @@ module ActiveRecord
|
|
|
573
565
|
[nil, nil]
|
|
574
566
|
else
|
|
575
567
|
type = case type
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
568
|
+
when /smallint|int|bigint/ then original_type
|
|
569
|
+
else type
|
|
570
|
+
end
|
|
579
571
|
value = default.match(/\A\((.*)\)\Z/m)[1]
|
|
580
572
|
value = select_value("SELECT CAST(#{value} AS #{type}) AS value", "SCHEMA")
|
|
581
573
|
[value, nil]
|
|
@@ -583,21 +575,21 @@ module ActiveRecord
|
|
|
583
575
|
end
|
|
584
576
|
|
|
585
577
|
def column_type(ci:)
|
|
586
|
-
case ci[
|
|
578
|
+
case ci["type"]
|
|
587
579
|
when /^bit|image|text|ntext|datetime$/
|
|
588
|
-
ci[
|
|
580
|
+
ci["type"]
|
|
589
581
|
when /^datetime2|datetimeoffset$/i
|
|
590
|
-
"#{ci[
|
|
582
|
+
"#{ci["type"]}(#{ci["datetime_precision"]})"
|
|
591
583
|
when /^time$/i
|
|
592
|
-
"#{ci[
|
|
584
|
+
"#{ci["type"]}(#{ci["datetime_precision"]})"
|
|
593
585
|
when /^numeric|decimal$/i
|
|
594
|
-
"#{ci[
|
|
586
|
+
"#{ci["type"]}(#{ci["numeric_precision"]},#{ci["numeric_scale"]})"
|
|
595
587
|
when /^float|real$/i
|
|
596
|
-
|
|
588
|
+
ci["type"]
|
|
597
589
|
when /^char|nchar|varchar|nvarchar|binary|varbinary|bigint|int|smallint$/
|
|
598
|
-
ci[
|
|
590
|
+
(ci["length"].to_i == -1) ? "#{ci["type"]}(max)" : "#{ci["type"]}(#{ci["length"]})"
|
|
599
591
|
else
|
|
600
|
-
ci[
|
|
592
|
+
ci["type"]
|
|
601
593
|
end
|
|
602
594
|
end
|
|
603
595
|
|
|
@@ -618,8 +610,8 @@ module ActiveRecord
|
|
|
618
610
|
|
|
619
611
|
%{
|
|
620
612
|
SELECT
|
|
621
|
-
#{lowercase_schema_reflection_sql(
|
|
622
|
-
#{lowercase_schema_reflection_sql(
|
|
613
|
+
#{lowercase_schema_reflection_sql("o.name")} AS [table_name],
|
|
614
|
+
#{lowercase_schema_reflection_sql("c.name")} AS [name],
|
|
623
615
|
t.name AS [type],
|
|
624
616
|
d.definition AS [default_value],
|
|
625
617
|
CASE
|
|
@@ -649,7 +641,10 @@ module ActiveRecord
|
|
|
649
641
|
WHEN ic.object_id IS NOT NULL
|
|
650
642
|
THEN 1
|
|
651
643
|
END AS [is_primary],
|
|
652
|
-
c.is_identity AS [is_identity]
|
|
644
|
+
c.is_identity AS [is_identity],
|
|
645
|
+
c.is_computed AS [is_computed],
|
|
646
|
+
cc.is_persisted AS [is_persisted],
|
|
647
|
+
cc.definition AS [computed_formula]
|
|
653
648
|
FROM #{database}.sys.columns c
|
|
654
649
|
INNER JOIN #{database}.sys.objects o
|
|
655
650
|
ON c.object_id = o.object_id
|
|
@@ -668,6 +663,9 @@ module ActiveRecord
|
|
|
668
663
|
ON k.parent_object_id = ic.object_id
|
|
669
664
|
AND k.unique_index_id = ic.index_id
|
|
670
665
|
AND c.column_id = ic.column_id
|
|
666
|
+
LEFT OUTER JOIN #{database}.sys.computed_columns cc
|
|
667
|
+
ON c.object_id = cc.object_id
|
|
668
|
+
AND c.column_id = cc.column_id
|
|
671
669
|
WHERE
|
|
672
670
|
o.Object_ID = Object_ID(#{object_id_arg})
|
|
673
671
|
AND s.name = #{schema_name}
|
|
@@ -695,7 +693,7 @@ module ActiveRecord
|
|
|
695
693
|
execute_procedure(:sp_helpconstraint, table_name, "nomsg").flatten.select do |row|
|
|
696
694
|
row["constraint_type"] == "DEFAULT on column #{column_name}"
|
|
697
695
|
end.each do |row|
|
|
698
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row[
|
|
696
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row["constraint_name"]}"
|
|
699
697
|
end
|
|
700
698
|
end
|
|
701
699
|
|
|
@@ -726,11 +724,11 @@ module ActiveRecord
|
|
|
726
724
|
.split(/\bSELECT\b(?![^\[]*\])/i)[0]
|
|
727
725
|
.match(/\s*([^(]*)/i)[0]
|
|
728
726
|
elsif s.match?(/^\s*UPDATE\s+.*/i)
|
|
729
|
-
s.match(/UPDATE\s+([
|
|
727
|
+
s.match(/UPDATE\s+([^(\s]+)\s*/i)[1]
|
|
730
728
|
elsif s.match?(/^\s*MERGE INTO.*/i)
|
|
731
|
-
s.match(
|
|
729
|
+
s.match(/^\s*MERGE\s+INTO\s+(\[?[a-z0-9_ -]+\]?\.?\[?[a-z0-9_ -]+\]?)\s+(AS|WITH|USING)/i)[1]
|
|
732
730
|
else
|
|
733
|
-
s.match(/FROM[\s
|
|
731
|
+
s.match(/FROM[\s|(]+((\[[^(\]]+\])|[^(\s]+)\s*/i)[1]
|
|
734
732
|
end.strip
|
|
735
733
|
end
|
|
736
734
|
|
|
@@ -753,32 +751,32 @@ module ActiveRecord
|
|
|
753
751
|
@view_information ||= {}
|
|
754
752
|
|
|
755
753
|
@view_information[table_name] ||= begin
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
754
|
+
identifier = SQLServer::Utils.extract_identifiers(table_name)
|
|
755
|
+
information_query_table = identifier.database.present? ? "[#{identifier.database}].[INFORMATION_SCHEMA].[VIEWS]" : "[INFORMATION_SCHEMA].[VIEWS]"
|
|
756
|
+
|
|
757
|
+
view_info = select_one("SELECT * FROM #{information_query_table} WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", "SCHEMA").to_h
|
|
758
|
+
|
|
759
|
+
if view_info.present?
|
|
760
|
+
if view_info["VIEW_DEFINITION"].blank? || view_info["VIEW_DEFINITION"].length == 4000
|
|
761
|
+
view_info["VIEW_DEFINITION"] = begin
|
|
762
|
+
select_values("EXEC sp_helptext #{identifier.object_quoted}", "SCHEMA").join
|
|
763
|
+
rescue
|
|
764
|
+
warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
|
|
765
|
+
nil
|
|
766
|
+
end
|
|
767
|
+
end
|
|
768
|
+
end
|
|
771
769
|
|
|
772
|
-
|
|
773
|
-
|
|
770
|
+
view_info
|
|
771
|
+
end
|
|
774
772
|
end
|
|
775
773
|
|
|
776
774
|
def views_real_column_name(table_name, column_name)
|
|
777
|
-
view_definition = view_information(table_name)[
|
|
775
|
+
view_definition = view_information(table_name)["VIEW_DEFINITION"]
|
|
778
776
|
return column_name if view_definition.blank?
|
|
779
777
|
|
|
780
778
|
# Remove "CREATE VIEW ... AS SELECT ..." and then match the column name.
|
|
781
|
-
match_data = view_definition.sub(/CREATE\s+VIEW.*AS\s+SELECT\s/,
|
|
779
|
+
match_data = view_definition.sub(/CREATE\s+VIEW.*AS\s+SELECT\s/, "").match(/([\w-]*)\s+AS\s+#{column_name}\W/im)
|
|
782
780
|
match_data ? match_data[1] : column_name
|
|
783
781
|
end
|
|
784
782
|
|
|
@@ -36,7 +36,7 @@ module ActiveRecord
|
|
|
36
36
|
result.columns.each_with_index do |column, i|
|
|
37
37
|
cells_in_column = [column] + result.rows.map { |r| cast_item(r[i]) }
|
|
38
38
|
computed_width = cells_in_column.map(&:length).max
|
|
39
|
-
final_width = computed_width > max_column_width ? max_column_width : computed_width
|
|
39
|
+
final_width = (computed_width > max_column_width) ? max_column_width : computed_width
|
|
40
40
|
computed_widths << final_width
|
|
41
41
|
end
|
|
42
42
|
end
|
|
@@ -51,7 +51,7 @@ module ActiveRecord
|
|
|
51
51
|
items.each_with_index do |item, i|
|
|
52
52
|
cells << cast_item(item).ljust(@widths[i])
|
|
53
53
|
end
|
|
54
|
-
"| #{cells.join(
|
|
54
|
+
"| #{cells.join(" | ")} |"
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def cast_item(item)
|
|
@@ -7,9 +7,9 @@ module ActiveRecord
|
|
|
7
7
|
module ConnectionAdapters
|
|
8
8
|
module SQLServer
|
|
9
9
|
module Showplan
|
|
10
|
-
OPTION_ALL
|
|
10
|
+
OPTION_ALL = "SHOWPLAN_ALL"
|
|
11
11
|
OPTION_TEXT = "SHOWPLAN_TEXT"
|
|
12
|
-
OPTION_XML
|
|
12
|
+
OPTION_XML = "SHOWPLAN_XML"
|
|
13
13
|
OPTIONS = [OPTION_ALL, OPTION_TEXT, OPTION_XML]
|
|
14
14
|
|
|
15
15
|
def explain(arel, binds = [], options = [])
|
|
@@ -30,10 +30,10 @@ module ActiveRecord
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def set_showplan_option(enable = true)
|
|
33
|
-
sql = "SET #{showplan_option} #{enable ?
|
|
33
|
+
sql = "SET #{showplan_option} #{enable ? "ON" : "OFF"}"
|
|
34
34
|
raw_execute(sql, "SCHEMA")
|
|
35
|
-
rescue
|
|
36
|
-
raise ActiveRecordError, "#{showplan_option} could not be turned #{enable ?
|
|
35
|
+
rescue
|
|
36
|
+
raise ActiveRecordError, "#{showplan_option} could not be turned #{enable ? "ON" : "OFF"}, perhaps you do not have SHOWPLAN permissions?"
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def showplan_option
|
|
@@ -26,15 +26,10 @@ module ActiveRecord
|
|
|
26
26
|
table_name == other.table_name &&
|
|
27
27
|
ordinal_position == other.ordinal_position
|
|
28
28
|
end
|
|
29
|
-
|
|
29
|
+
alias_method :eql?, :==
|
|
30
30
|
|
|
31
31
|
def hash
|
|
32
|
-
TypeMetadata.hash
|
|
33
|
-
__getobj__.hash ^
|
|
34
|
-
is_identity.hash ^
|
|
35
|
-
is_primary.hash ^
|
|
36
|
-
table_name.hash ^
|
|
37
|
-
ordinal_position.hash
|
|
32
|
+
[TypeMetadata, __getobj__, is_identity, is_primary, table_name, ordinal_position].hash
|
|
38
33
|
end
|
|
39
34
|
|
|
40
35
|
private
|
|
@@ -109,6 +109,8 @@ module ActiveRecord
|
|
|
109
109
|
type = :datetime2 unless options[:precision].nil?
|
|
110
110
|
when :primary_key
|
|
111
111
|
options[:is_identity] = true
|
|
112
|
+
when :virtual
|
|
113
|
+
type = options[:type]
|
|
112
114
|
end
|
|
113
115
|
|
|
114
116
|
super
|
|
@@ -117,7 +119,7 @@ module ActiveRecord
|
|
|
117
119
|
private
|
|
118
120
|
|
|
119
121
|
def valid_column_definition_options
|
|
120
|
-
super + [:is_identity]
|
|
122
|
+
super + [:is_identity, :as, :stored]
|
|
121
123
|
end
|
|
122
124
|
end
|
|
123
125
|
|