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
|
@@ -4,10 +4,6 @@ module ActiveRecord
|
|
|
4
4
|
module ConnectionAdapters
|
|
5
5
|
module SQLServer
|
|
6
6
|
module SchemaStatements
|
|
7
|
-
def native_database_types
|
|
8
|
-
@native_database_types ||= initialize_native_database_types.freeze
|
|
9
|
-
end
|
|
10
|
-
|
|
11
7
|
def create_table(table_name, **options)
|
|
12
8
|
res = super
|
|
13
9
|
clear_cache!
|
|
@@ -36,19 +32,24 @@ module ActiveRecord
|
|
|
36
32
|
end
|
|
37
33
|
|
|
38
34
|
def indexes(table_name)
|
|
39
|
-
data =
|
|
35
|
+
data = begin
|
|
36
|
+
select("EXEC sp_helpindex #{quote(table_name)}", "SCHEMA")
|
|
37
|
+
rescue
|
|
38
|
+
[]
|
|
39
|
+
end
|
|
40
40
|
|
|
41
41
|
data.reduce([]) do |indexes, index|
|
|
42
|
-
if index[
|
|
42
|
+
if index["index_description"].match?(/primary key/)
|
|
43
43
|
indexes
|
|
44
44
|
else
|
|
45
|
-
name
|
|
46
|
-
unique
|
|
47
|
-
where
|
|
48
|
-
|
|
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 = {}
|
|
49
50
|
columns = []
|
|
50
51
|
|
|
51
|
-
index[
|
|
52
|
+
index["index_keys"].split(",").each do |column|
|
|
52
53
|
column.strip!
|
|
53
54
|
|
|
54
55
|
if column.end_with?("(-)")
|
|
@@ -59,43 +60,75 @@ module ActiveRecord
|
|
|
59
60
|
columns << column
|
|
60
61
|
end
|
|
61
62
|
|
|
62
|
-
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)
|
|
63
64
|
end
|
|
64
65
|
end
|
|
65
66
|
end
|
|
66
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
|
+
|
|
67
88
|
def columns(table_name)
|
|
68
89
|
return [] if table_name.blank?
|
|
69
90
|
|
|
70
|
-
column_definitions(table_name)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
new_column(
|
|
74
|
-
ci[:name],
|
|
75
|
-
ci[:default_value],
|
|
76
|
-
sql_type_metadata,
|
|
77
|
-
ci[:null],
|
|
78
|
-
ci[:default_function],
|
|
79
|
-
ci[:collation],
|
|
80
|
-
nil,
|
|
81
|
-
sqlserver_options
|
|
82
|
-
)
|
|
91
|
+
definitions = column_definitions(table_name)
|
|
92
|
+
definitions.map do |field|
|
|
93
|
+
new_column_from_field(table_name, field, definitions)
|
|
83
94
|
end
|
|
84
95
|
end
|
|
85
96
|
|
|
86
|
-
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
|
+
|
|
87
108
|
SQLServer::Column.new(
|
|
88
|
-
name,
|
|
89
|
-
|
|
109
|
+
field[:name],
|
|
110
|
+
lookup_cast_type(field[:type]),
|
|
111
|
+
field[:default_value],
|
|
90
112
|
sql_type_metadata,
|
|
91
|
-
null,
|
|
113
|
+
field[:null],
|
|
92
114
|
default_function,
|
|
93
|
-
collation: collation,
|
|
94
|
-
comment:
|
|
115
|
+
collation: field[:collation],
|
|
116
|
+
comment: nil,
|
|
117
|
+
generated_type: generated_type,
|
|
95
118
|
**sqlserver_options
|
|
96
119
|
)
|
|
97
120
|
end
|
|
98
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
|
+
|
|
99
132
|
def primary_keys(table_name)
|
|
100
133
|
primaries = primary_keys_select(table_name)
|
|
101
134
|
primaries.present? ? primaries : identity_columns(table_name).map(&:name)
|
|
@@ -104,8 +137,8 @@ module ActiveRecord
|
|
|
104
137
|
def primary_keys_select(table_name)
|
|
105
138
|
identifier = database_prefix_identifier(table_name)
|
|
106
139
|
database = identifier.fully_qualified_database_quoted
|
|
107
|
-
sql = %
|
|
108
|
-
SELECT #{lowercase_schema_reflection_sql(
|
|
140
|
+
sql = %(
|
|
141
|
+
SELECT #{lowercase_schema_reflection_sql("KCU.COLUMN_NAME")} AS [name]
|
|
109
142
|
FROM #{database}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
|
|
110
143
|
LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
|
|
111
144
|
ON KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
|
|
@@ -113,11 +146,15 @@ module ActiveRecord
|
|
|
113
146
|
AND KCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG
|
|
114
147
|
AND KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
|
|
115
148
|
AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
|
|
116
|
-
WHERE KCU.TABLE_NAME = #{prepared_statements ?
|
|
117
|
-
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}
|
|
118
155
|
AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
|
|
119
156
|
ORDER BY KCU.ORDINAL_POSITION ASC
|
|
120
|
-
|
|
157
|
+
).gsub(/[[:space:]]/, " ")
|
|
121
158
|
|
|
122
159
|
binds = []
|
|
123
160
|
nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
|
|
@@ -160,10 +197,10 @@ module ActiveRecord
|
|
|
160
197
|
column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s }
|
|
161
198
|
without_constraints = options.key?(:default) || options.key?(:limit)
|
|
162
199
|
default = if !options.key?(:default) && column_object
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
200
|
+
column_object.default
|
|
201
|
+
else
|
|
202
|
+
options[:default]
|
|
203
|
+
end
|
|
167
204
|
|
|
168
205
|
if without_constraints || (column_object && column_object.type != type.to_sym)
|
|
169
206
|
remove_default_constraint(table_name, column_name)
|
|
@@ -213,7 +250,7 @@ module ActiveRecord
|
|
|
213
250
|
end
|
|
214
251
|
|
|
215
252
|
def rename_index(table_name, old_name, new_name)
|
|
216
|
-
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
|
|
217
254
|
|
|
218
255
|
identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{old_name}")
|
|
219
256
|
execute_procedure :sp_rename, identifier.quoted, new_name, "INDEX"
|
|
@@ -241,7 +278,7 @@ module ActiveRecord
|
|
|
241
278
|
identifier = SQLServer::Utils.extract_identifiers(table_name)
|
|
242
279
|
fk_info = execute_procedure :sp_fkeys, nil, identifier.schema, nil, identifier.object, identifier.schema
|
|
243
280
|
|
|
244
|
-
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
|
|
245
282
|
grouped_fk.map do |group|
|
|
246
283
|
row = group.first
|
|
247
284
|
options = {
|
|
@@ -293,20 +330,20 @@ module ActiveRecord
|
|
|
293
330
|
end
|
|
294
331
|
|
|
295
332
|
def type_to_sql(type, limit: nil, precision: nil, scale: nil, **)
|
|
296
|
-
type_limitable = %w
|
|
333
|
+
type_limitable = %w[string integer float char nchar varchar nvarchar binary_basic].include?(type.to_s)
|
|
297
334
|
limit = nil unless type_limitable
|
|
298
335
|
|
|
299
336
|
case type.to_s
|
|
300
337
|
when "integer"
|
|
301
338
|
case limit
|
|
302
|
-
when 1
|
|
303
|
-
when 2
|
|
304
|
-
when 3..4, nil
|
|
305
|
-
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"
|
|
306
343
|
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
|
307
344
|
end
|
|
308
345
|
when "time" # https://learn.microsoft.com/en-us/sql/t-sql/data-types/time-transact-sql
|
|
309
|
-
column_type_sql = type.to_s
|
|
346
|
+
column_type_sql = type.to_s.dup
|
|
310
347
|
if precision
|
|
311
348
|
if (0..7) === precision
|
|
312
349
|
column_type_sql << "(#{precision})"
|
|
@@ -343,18 +380,18 @@ module ActiveRecord
|
|
|
343
380
|
# In SQL Server only the first column added should have the `ADD` keyword.
|
|
344
381
|
def add_timestamps(table_name, **options)
|
|
345
382
|
fragments = add_timestamps_for_alter(table_name, **options)
|
|
346
|
-
fragments[1..].each { |fragment| fragment.sub!(
|
|
347
|
-
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(", ")}"
|
|
348
385
|
end
|
|
349
386
|
|
|
350
387
|
def columns_for_distinct(columns, orders)
|
|
351
388
|
order_columns = orders.reject(&:blank?).map { |s|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
389
|
+
s = visitor.compile(s) unless s.is_a?(String)
|
|
390
|
+
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
|
|
391
|
+
.gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
|
|
392
|
+
}
|
|
393
|
+
.reject(&:blank?)
|
|
394
|
+
.reject { |s| columns.include?(s) }
|
|
358
395
|
|
|
359
396
|
order_columns_aliased = order_columns.map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
|
360
397
|
|
|
@@ -402,24 +439,33 @@ module ActiveRecord
|
|
|
402
439
|
# Returns an array of schema names.
|
|
403
440
|
def schema_names
|
|
404
441
|
sql = <<~SQL.squish
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
442
|
+
SELECT name
|
|
443
|
+
FROM sys.schemas
|
|
444
|
+
WHERE
|
|
445
|
+
name NOT LIKE 'db_%' AND
|
|
446
|
+
name NOT IN ('INFORMATION_SCHEMA', 'sys', 'guest')
|
|
410
447
|
SQL
|
|
411
448
|
|
|
412
449
|
query_values(sql, "SCHEMA")
|
|
413
450
|
end
|
|
414
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
|
+
|
|
415
461
|
private
|
|
416
462
|
|
|
417
463
|
def data_source_sql(name = nil, type: nil)
|
|
418
464
|
scope = quoted_scope(name, type: type)
|
|
419
465
|
|
|
420
|
-
table_schema
|
|
421
|
-
table_name
|
|
422
|
-
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]}." : ""
|
|
423
469
|
table_catalog = scope[:database].present? ? quote(scope[:database]) : "DB_NAME()"
|
|
424
470
|
|
|
425
471
|
sql = "SELECT "
|
|
@@ -449,46 +495,10 @@ module ActiveRecord
|
|
|
449
495
|
|
|
450
496
|
# === SQLServer Specific ======================================== #
|
|
451
497
|
|
|
452
|
-
def initialize_native_database_types
|
|
453
|
-
{
|
|
454
|
-
primary_key: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY",
|
|
455
|
-
primary_key_nonclustered: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED",
|
|
456
|
-
integer: { name: "int", limit: 4 },
|
|
457
|
-
bigint: { name: "bigint" },
|
|
458
|
-
boolean: { name: "bit" },
|
|
459
|
-
decimal: { name: "decimal" },
|
|
460
|
-
money: { name: "money" },
|
|
461
|
-
smallmoney: { name: "smallmoney" },
|
|
462
|
-
float: { name: "float" },
|
|
463
|
-
real: { name: "real" },
|
|
464
|
-
date: { name: "date" },
|
|
465
|
-
datetime: { name: "datetime" },
|
|
466
|
-
datetime2: { name: "datetime2" },
|
|
467
|
-
datetimeoffset: { name: "datetimeoffset" },
|
|
468
|
-
smalldatetime: { name: "smalldatetime" },
|
|
469
|
-
timestamp: { name: "datetime2(6)" },
|
|
470
|
-
time: { name: "time" },
|
|
471
|
-
char: { name: "char" },
|
|
472
|
-
varchar: { name: "varchar", limit: 8000 },
|
|
473
|
-
varchar_max: { name: "varchar(max)" },
|
|
474
|
-
text_basic: { name: "text" },
|
|
475
|
-
nchar: { name: "nchar" },
|
|
476
|
-
string: { name: "nvarchar", limit: 4000 },
|
|
477
|
-
text: { name: "nvarchar(max)" },
|
|
478
|
-
ntext: { name: "ntext" },
|
|
479
|
-
binary_basic: { name: "binary" },
|
|
480
|
-
varbinary: { name: "varbinary", limit: 8000 },
|
|
481
|
-
binary: { name: "varbinary(max)" },
|
|
482
|
-
uuid: { name: "uniqueidentifier" },
|
|
483
|
-
ss_timestamp: { name: "timestamp" },
|
|
484
|
-
json: { name: "nvarchar(max)" }
|
|
485
|
-
}
|
|
486
|
-
end
|
|
487
|
-
|
|
488
498
|
def column_definitions(table_name)
|
|
489
|
-
identifier
|
|
490
|
-
database
|
|
491
|
-
view_exists
|
|
499
|
+
identifier = database_prefix_identifier(table_name)
|
|
500
|
+
database = identifier.fully_qualified_database_quoted
|
|
501
|
+
view_exists = view_exists?(table_name)
|
|
492
502
|
|
|
493
503
|
if view_exists
|
|
494
504
|
sql = <<~SQL
|
|
@@ -510,40 +520,30 @@ module ActiveRecord
|
|
|
510
520
|
results = internal_exec_query(sql, "SCHEMA", binds)
|
|
511
521
|
raise ActiveRecord::StatementInvalid, "Table '#{table_name}' doesn't exist" if results.empty?
|
|
512
522
|
|
|
513
|
-
|
|
514
|
-
col =
|
|
515
|
-
name: ci["name"],
|
|
516
|
-
numeric_scale: ci["numeric_scale"],
|
|
517
|
-
numeric_precision: ci["numeric_precision"],
|
|
518
|
-
datetime_precision: ci["datetime_precision"],
|
|
519
|
-
collation: ci["collation"],
|
|
520
|
-
ordinal_position: ci["ordinal_position"],
|
|
521
|
-
length: ci["length"]
|
|
522
|
-
}
|
|
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
|
|
523
525
|
|
|
524
526
|
col[:table_name] = view_exists ? view_table_name(table_name) : table_name
|
|
525
527
|
col[:type] = column_type(ci: ci)
|
|
526
|
-
col[:default_value], col[:default_function] = default_value_and_function(default: ci[
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
col[:null] = ci[
|
|
535
|
-
col[:is_primary] = ci[
|
|
536
|
-
|
|
537
|
-
if [true, false].include?(ci[
|
|
538
|
-
|
|
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"]
|
|
539
541
|
else
|
|
540
|
-
|
|
542
|
+
ci["is_identity"].to_i == 1
|
|
541
543
|
end
|
|
542
544
|
|
|
543
545
|
col
|
|
544
546
|
end
|
|
545
|
-
|
|
546
|
-
columns
|
|
547
547
|
end
|
|
548
548
|
|
|
549
549
|
def default_value_and_function(default:, name:, type:, original_type:, view_exists:, table_name:, default_functions:)
|
|
@@ -565,9 +565,9 @@ module ActiveRecord
|
|
|
565
565
|
[nil, nil]
|
|
566
566
|
else
|
|
567
567
|
type = case type
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
568
|
+
when /smallint|int|bigint/ then original_type
|
|
569
|
+
else type
|
|
570
|
+
end
|
|
571
571
|
value = default.match(/\A\((.*)\)\Z/m)[1]
|
|
572
572
|
value = select_value("SELECT CAST(#{value} AS #{type}) AS value", "SCHEMA")
|
|
573
573
|
[value, nil]
|
|
@@ -575,21 +575,21 @@ module ActiveRecord
|
|
|
575
575
|
end
|
|
576
576
|
|
|
577
577
|
def column_type(ci:)
|
|
578
|
-
case ci[
|
|
578
|
+
case ci["type"]
|
|
579
579
|
when /^bit|image|text|ntext|datetime$/
|
|
580
|
-
ci[
|
|
580
|
+
ci["type"]
|
|
581
581
|
when /^datetime2|datetimeoffset$/i
|
|
582
|
-
"#{ci[
|
|
582
|
+
"#{ci["type"]}(#{ci["datetime_precision"]})"
|
|
583
583
|
when /^time$/i
|
|
584
|
-
"#{ci[
|
|
584
|
+
"#{ci["type"]}(#{ci["datetime_precision"]})"
|
|
585
585
|
when /^numeric|decimal$/i
|
|
586
|
-
"#{ci[
|
|
586
|
+
"#{ci["type"]}(#{ci["numeric_precision"]},#{ci["numeric_scale"]})"
|
|
587
587
|
when /^float|real$/i
|
|
588
|
-
|
|
588
|
+
ci["type"]
|
|
589
589
|
when /^char|nchar|varchar|nvarchar|binary|varbinary|bigint|int|smallint$/
|
|
590
|
-
ci[
|
|
590
|
+
(ci["length"].to_i == -1) ? "#{ci["type"]}(max)" : "#{ci["type"]}(#{ci["length"]})"
|
|
591
591
|
else
|
|
592
|
-
ci[
|
|
592
|
+
ci["type"]
|
|
593
593
|
end
|
|
594
594
|
end
|
|
595
595
|
|
|
@@ -610,8 +610,8 @@ module ActiveRecord
|
|
|
610
610
|
|
|
611
611
|
%{
|
|
612
612
|
SELECT
|
|
613
|
-
#{lowercase_schema_reflection_sql(
|
|
614
|
-
#{lowercase_schema_reflection_sql(
|
|
613
|
+
#{lowercase_schema_reflection_sql("o.name")} AS [table_name],
|
|
614
|
+
#{lowercase_schema_reflection_sql("c.name")} AS [name],
|
|
615
615
|
t.name AS [type],
|
|
616
616
|
d.definition AS [default_value],
|
|
617
617
|
CASE
|
|
@@ -641,7 +641,10 @@ module ActiveRecord
|
|
|
641
641
|
WHEN ic.object_id IS NOT NULL
|
|
642
642
|
THEN 1
|
|
643
643
|
END AS [is_primary],
|
|
644
|
-
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]
|
|
645
648
|
FROM #{database}.sys.columns c
|
|
646
649
|
INNER JOIN #{database}.sys.objects o
|
|
647
650
|
ON c.object_id = o.object_id
|
|
@@ -660,6 +663,9 @@ module ActiveRecord
|
|
|
660
663
|
ON k.parent_object_id = ic.object_id
|
|
661
664
|
AND k.unique_index_id = ic.index_id
|
|
662
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
|
|
663
669
|
WHERE
|
|
664
670
|
o.Object_ID = Object_ID(#{object_id_arg})
|
|
665
671
|
AND s.name = #{schema_name}
|
|
@@ -687,7 +693,7 @@ module ActiveRecord
|
|
|
687
693
|
execute_procedure(:sp_helpconstraint, table_name, "nomsg").flatten.select do |row|
|
|
688
694
|
row["constraint_type"] == "DEFAULT on column #{column_name}"
|
|
689
695
|
end.each do |row|
|
|
690
|
-
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"]}"
|
|
691
697
|
end
|
|
692
698
|
end
|
|
693
699
|
|
|
@@ -718,11 +724,11 @@ module ActiveRecord
|
|
|
718
724
|
.split(/\bSELECT\b(?![^\[]*\])/i)[0]
|
|
719
725
|
.match(/\s*([^(]*)/i)[0]
|
|
720
726
|
elsif s.match?(/^\s*UPDATE\s+.*/i)
|
|
721
|
-
s.match(/UPDATE\s+([
|
|
727
|
+
s.match(/UPDATE\s+([^(\s]+)\s*/i)[1]
|
|
722
728
|
elsif s.match?(/^\s*MERGE INTO.*/i)
|
|
723
729
|
s.match(/^\s*MERGE\s+INTO\s+(\[?[a-z0-9_ -]+\]?\.?\[?[a-z0-9_ -]+\]?)\s+(AS|WITH|USING)/i)[1]
|
|
724
730
|
else
|
|
725
|
-
s.match(/FROM[\s
|
|
731
|
+
s.match(/FROM[\s|(]+((\[[^(\]]+\])|[^(\s]+)\s*/i)[1]
|
|
726
732
|
end.strip
|
|
727
733
|
end
|
|
728
734
|
|
|
@@ -746,18 +752,18 @@ module ActiveRecord
|
|
|
746
752
|
|
|
747
753
|
@view_information[table_name] ||= begin
|
|
748
754
|
identifier = SQLServer::Utils.extract_identifiers(table_name)
|
|
749
|
-
information_query_table = identifier.database.present? ? "[#{identifier.database}].[INFORMATION_SCHEMA].[VIEWS]" :
|
|
755
|
+
information_query_table = identifier.database.present? ? "[#{identifier.database}].[INFORMATION_SCHEMA].[VIEWS]" : "[INFORMATION_SCHEMA].[VIEWS]"
|
|
750
756
|
|
|
751
757
|
view_info = select_one("SELECT * FROM #{information_query_table} WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", "SCHEMA").to_h
|
|
752
758
|
|
|
753
759
|
if view_info.present?
|
|
754
|
-
if view_info[
|
|
755
|
-
view_info[
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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
|
|
761
767
|
end
|
|
762
768
|
end
|
|
763
769
|
|
|
@@ -766,11 +772,11 @@ module ActiveRecord
|
|
|
766
772
|
end
|
|
767
773
|
|
|
768
774
|
def views_real_column_name(table_name, column_name)
|
|
769
|
-
view_definition = view_information(table_name)[
|
|
775
|
+
view_definition = view_information(table_name)["VIEW_DEFINITION"]
|
|
770
776
|
return column_name if view_definition.blank?
|
|
771
777
|
|
|
772
778
|
# Remove "CREATE VIEW ... AS SELECT ..." and then match the column name.
|
|
773
|
-
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)
|
|
774
780
|
match_data ? match_data[1] : column_name
|
|
775
781
|
end
|
|
776
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
|
|
|
@@ -35,16 +35,16 @@ module ActiveRecord
|
|
|
35
35
|
|
|
36
36
|
self.class == other.class && value == other.value
|
|
37
37
|
end
|
|
38
|
-
|
|
38
|
+
alias_method :==, :eql?
|
|
39
39
|
|
|
40
40
|
def self.from_msgpack_ext(string)
|
|
41
|
-
type, value = string.chomp!("msgpack_ext").split(
|
|
41
|
+
type, value = string.chomp!("msgpack_ext").split(",")
|
|
42
42
|
|
|
43
43
|
Data.new(value, type.constantize)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def to_msgpack_ext
|
|
47
|
-
[type.class.to_s, value].join(
|
|
47
|
+
[type.class.to_s, value].join(",") + "msgpack_ext"
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
end
|
|
@@ -9,12 +9,12 @@ module ActiveRecord
|
|
|
9
9
|
"date"
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def serialize(
|
|
12
|
+
def serialize(_value)
|
|
13
13
|
value = super
|
|
14
14
|
return value unless value.acts_like?(:date)
|
|
15
15
|
|
|
16
|
-
date = super
|
|
17
|
-
Data.new
|
|
16
|
+
date = super.to_formatted_s(:_sqlserver_dateformat)
|
|
17
|
+
Data.new(date, self)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def deserialize(value)
|