activerecord-jdbcsqlserver-adapter 50.0.0 → 52.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/.travis.yml +4 -5
- data/CHANGELOG.md +22 -101
- data/{Dockerfile → Dockerfile.ci} +0 -0
- data/Gemfile +1 -3
- data/README.md +5 -9
- data/VERSION +1 -1
- data/activerecord-jdbcsqlserver-adapter.gemspec +2 -2
- data/appveyor.yml +1 -1
- data/docker-compose.ci.yml +7 -5
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +51 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +18 -20
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +5 -3
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +43 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +26 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +13 -2
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +94 -28
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +1 -0
- data/lib/active_record/connection_adapters/sqlserver/jdbc_overrides.rb +5 -25
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +24 -1
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +23 -2
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +110 -74
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +15 -7
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +3 -4
- data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +0 -4
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +5 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +3 -6
- data/lib/active_record/connection_adapters/sqlserver/type/json.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +7 -0
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +47 -24
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +5 -3
- data/lib/activerecord-jdbcsqlserver-adapter.rb +4 -1
- data/lib/arel/visitors/sqlserver.rb +17 -4
- data/lib/arel_sqlserver.rb +0 -1
- data/lib/jdbc_mssql_driver_loader.rb +22 -0
- data/test/bin/install-freetds.sh +18 -0
- data/test/bin/setup.sh +19 -0
- data/test/cases/adapter_test_sqlserver.rb +43 -39
- data/test/cases/change_column_null_test_sqlserver.rb +42 -0
- data/test/cases/coerced_tests.rb +419 -39
- data/test/cases/column_test_sqlserver.rb +496 -462
- data/test/cases/connection_test_sqlserver.rb +2 -2
- data/test/cases/fetch_test_sqlserver.rb +5 -5
- data/test/cases/helper_sqlserver.rb +12 -1
- data/test/cases/json_test_sqlserver.rb +6 -6
- data/test/cases/migration_test_sqlserver.rb +13 -3
- data/test/cases/order_test_sqlserver.rb +19 -19
- data/test/cases/pessimistic_locking_test_sqlserver.rb +37 -20
- data/test/cases/rake_test_sqlserver.rb +20 -20
- data/test/cases/schema_dumper_test_sqlserver.rb +44 -43
- data/test/cases/schema_test_sqlserver.rb +2 -2
- data/test/cases/showplan_test_sqlserver.rb +25 -10
- data/test/cases/specific_schema_test_sqlserver.rb +11 -17
- data/test/cases/transaction_test_sqlserver.rb +9 -9
- data/test/cases/trigger_test_sqlserver.rb +31 -0
- data/test/cases/utils_test_sqlserver.rb +36 -36
- data/test/cases/uuid_test_sqlserver.rb +8 -8
- data/test/config.yml +2 -2
- data/test/migrations/create_clients_and_change_column_null.rb +23 -0
- data/test/models/sqlserver/trigger.rb +7 -0
- data/test/models/sqlserver/trigger_history.rb +3 -0
- data/test/schema/datatypes/2012.sql +1 -0
- data/test/schema/sqlserver_specific_schema.rb +47 -5
- data/test/support/core_ext/query_cache.rb +29 -0
- data/test/support/sql_counter_sqlserver.rb +1 -1
- metadata +32 -15
- data/RAILS5-TODO.md +0 -5
- data/test/models/sqlserver/dot_table_name.rb +0 -3
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'active_record/relation'
|
2
|
+
require 'active_record/version'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
module ConnectionAdapters
|
6
|
+
module SQLServer
|
7
|
+
module CoreExt
|
8
|
+
module QueryMethods
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# Copy of original from Rails master. This patch can be removed when adapter supports Rails 6.
|
13
|
+
def table_name_matches?(from)
|
14
|
+
table_name = Regexp.escape(table.name)
|
15
|
+
quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
|
16
|
+
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
ActiveSupport.on_load(:active_record) do
|
25
|
+
ActiveRecord::Relation.include(ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::QueryMethods)
|
26
|
+
end
|
@@ -2,7 +2,6 @@ module ActiveRecord
|
|
2
2
|
module ConnectionAdapters
|
3
3
|
module SQLServer
|
4
4
|
module DatabaseLimits
|
5
|
-
|
6
5
|
def table_alias_length
|
7
6
|
128
|
8
7
|
end
|
@@ -32,7 +31,7 @@ module ActiveRecord
|
|
32
31
|
end
|
33
32
|
|
34
33
|
def in_clause_length
|
35
|
-
|
34
|
+
10_000
|
36
35
|
end
|
37
36
|
|
38
37
|
def sql_query_length
|
@@ -43,6 +42,18 @@ module ActiveRecord
|
|
43
42
|
256
|
44
43
|
end
|
45
44
|
|
45
|
+
private
|
46
|
+
|
47
|
+
# The max number of binds is 2100, but because sp_executesql takes
|
48
|
+
# the first 2 params as the query string and the list of types,
|
49
|
+
# we have only 2098 spaces left
|
50
|
+
def bind_params_length
|
51
|
+
2_098
|
52
|
+
end
|
53
|
+
|
54
|
+
def insert_rows_length
|
55
|
+
1_000
|
56
|
+
end
|
46
57
|
end
|
47
58
|
end
|
48
59
|
end
|
@@ -3,10 +3,6 @@ module ActiveRecord
|
|
3
3
|
module SQLServer
|
4
4
|
module DatabaseStatements
|
5
5
|
|
6
|
-
def select_rows(sql, name = nil, binds = [])
|
7
|
-
sp_executesql sql, name, binds, fetch: :rows
|
8
|
-
end
|
9
|
-
|
10
6
|
def execute(sql, name = nil)
|
11
7
|
if id_insert_table_name = query_requires_identity_insert?(sql)
|
12
8
|
with_identity_insert_enabled(id_insert_table_name) { do_execute(sql, name) }
|
@@ -19,26 +15,22 @@ module ActiveRecord
|
|
19
15
|
sp_executesql(sql, name, binds, prepare: prepare)
|
20
16
|
end
|
21
17
|
|
22
|
-
def exec_insert(sql, name, binds, pk = nil, _sequence_name = nil)
|
18
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, _sequence_name = nil)
|
23
19
|
if id_insert_table_name = exec_insert_requires_identity?(sql, pk, binds)
|
24
|
-
with_identity_insert_enabled(id_insert_table_name) {
|
20
|
+
with_identity_insert_enabled(id_insert_table_name) { super(sql, name, binds, pk) }
|
25
21
|
else
|
26
|
-
|
22
|
+
super(sql, name, binds, pk)
|
27
23
|
end
|
28
24
|
end
|
29
25
|
|
30
26
|
def exec_delete(sql, name, binds)
|
31
|
-
sql << '; SELECT @@ROWCOUNT AS AffectedRows'
|
32
|
-
super.rows.first.first
|
27
|
+
sql = sql.dup << '; SELECT @@ROWCOUNT AS AffectedRows'
|
28
|
+
super(sql, name, binds).rows.first.first
|
33
29
|
end
|
34
30
|
|
35
31
|
def exec_update(sql, name, binds)
|
36
|
-
sql << '; SELECT @@ROWCOUNT AS AffectedRows'
|
37
|
-
super.rows.first.first
|
38
|
-
end
|
39
|
-
|
40
|
-
def supports_statement_cache?
|
41
|
-
true
|
32
|
+
sql = sql.dup << '; SELECT @@ROWCOUNT AS AffectedRows'
|
33
|
+
super(sql, name, binds).rows.first.first
|
42
34
|
end
|
43
35
|
|
44
36
|
def begin_db_transaction
|
@@ -80,18 +72,56 @@ module ActiveRecord
|
|
80
72
|
end
|
81
73
|
|
82
74
|
def case_sensitive_comparison(table, attribute, column, value)
|
83
|
-
if
|
84
|
-
table[attribute].eq(Arel::Nodes::Bin.new(
|
75
|
+
if column.collation && !column.case_sensitive?
|
76
|
+
table[attribute].eq(Arel::Nodes::Bin.new(value))
|
85
77
|
else
|
86
78
|
super
|
87
79
|
end
|
88
80
|
end
|
89
81
|
|
82
|
+
# We should propose this change to Rails team
|
83
|
+
def insert_fixtures_set(fixture_set, tables_to_delete = [])
|
84
|
+
fixture_inserts = []
|
85
|
+
|
86
|
+
fixture_set.each do |table_name, fixtures|
|
87
|
+
fixtures.each_slice(insert_rows_length) do |batch|
|
88
|
+
fixture_inserts << build_fixture_sql(batch, table_name)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name table}".dup }
|
93
|
+
total_sql = Array.wrap(combine_multi_statements(table_deletes + fixture_inserts))
|
94
|
+
|
95
|
+
disable_referential_integrity do
|
96
|
+
transaction(requires_new: true) do
|
97
|
+
total_sql.each do |sql|
|
98
|
+
execute sql, "Fixtures Load"
|
99
|
+
yield if block_given?
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
90
105
|
def can_perform_case_insensitive_comparison_for?(column)
|
91
|
-
column.type == :string
|
106
|
+
column.type == :string && (!column.collation || column.case_sensitive?)
|
92
107
|
end
|
93
108
|
private :can_perform_case_insensitive_comparison_for?
|
94
109
|
|
110
|
+
def combine_multi_statements(total_sql)
|
111
|
+
total_sql
|
112
|
+
end
|
113
|
+
private :combine_multi_statements
|
114
|
+
|
115
|
+
def default_insert_value(column)
|
116
|
+
if column.is_identity?
|
117
|
+
table_name = quote(quote_table_name(column.table_name))
|
118
|
+
Arel.sql("IDENT_CURRENT(#{table_name}) + IDENT_INCR(#{table_name})")
|
119
|
+
else
|
120
|
+
super
|
121
|
+
end
|
122
|
+
end
|
123
|
+
private :default_insert_value
|
124
|
+
|
95
125
|
# === SQLServer Specific ======================================== #
|
96
126
|
|
97
127
|
def execute_procedure(proc_name, *variables)
|
@@ -196,9 +226,20 @@ module ActiveRecord
|
|
196
226
|
table_name = query_requires_identity_insert?(sql)
|
197
227
|
pk = primary_key(table_name)
|
198
228
|
end
|
199
|
-
sql = if pk &&
|
229
|
+
sql = if pk && use_output_inserted? && !database_prefix_remote_server?
|
200
230
|
quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
|
201
|
-
|
231
|
+
table_name ||= get_table_name(sql)
|
232
|
+
exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)
|
233
|
+
if exclude_output_inserted
|
234
|
+
id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? 'bigint' : exclude_output_inserted
|
235
|
+
<<-SQL.strip_heredoc
|
236
|
+
DECLARE @ssaIdInsertTable table (#{quoted_pk} #{id_sql_type});
|
237
|
+
#{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk} INTO @ssaIdInsertTable"}
|
238
|
+
SELECT CAST(#{quoted_pk} AS #{id_sql_type}) FROM @ssaIdInsertTable
|
239
|
+
SQL
|
240
|
+
else
|
241
|
+
sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk}"
|
242
|
+
end
|
202
243
|
else
|
203
244
|
"#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
|
204
245
|
end
|
@@ -231,6 +272,8 @@ module ActiveRecord
|
|
231
272
|
def sp_executesql_types_and_parameters(binds)
|
232
273
|
types, params = [], []
|
233
274
|
binds.each_with_index do |attr, index|
|
275
|
+
attr = attr.value if attr.is_a?(Arel::Nodes::BindParam)
|
276
|
+
|
234
277
|
types << "@#{index} #{sp_executesql_sql_type(attr)}"
|
235
278
|
params << sp_executesql_sql_param(attr)
|
236
279
|
end
|
@@ -241,19 +284,19 @@ module ActiveRecord
|
|
241
284
|
return attr.type.sqlserver_type if attr.type.respond_to?(:sqlserver_type)
|
242
285
|
case value = attr.value_for_database
|
243
286
|
when Numeric
|
244
|
-
'int'.freeze
|
287
|
+
value > 2_147_483_647 ? 'bigint'.freeze : 'int'.freeze
|
245
288
|
else
|
246
289
|
'nvarchar(max)'.freeze
|
247
290
|
end
|
248
291
|
end
|
249
292
|
|
250
293
|
def sp_executesql_sql_param(attr)
|
251
|
-
case attr.value_for_database
|
294
|
+
case value = attr.value_for_database
|
252
295
|
when Type::Binary::Data,
|
253
296
|
ActiveRecord::Type::SQLServer::Data
|
254
|
-
quote(
|
297
|
+
quote(value)
|
255
298
|
else
|
256
|
-
quote(type_cast(
|
299
|
+
quote(type_cast(value))
|
257
300
|
end
|
258
301
|
end
|
259
302
|
|
@@ -261,7 +304,7 @@ module ActiveRecord
|
|
261
304
|
if name == 'EXPLAIN'
|
262
305
|
params.each.with_index do |param, index|
|
263
306
|
substitute_at_finder = /(@#{index})(?=(?:[^']|'[^']*')*$)/ # Finds unquoted @n values.
|
264
|
-
sql.sub
|
307
|
+
sql = sql.sub substitute_at_finder, param.to_s
|
265
308
|
end
|
266
309
|
else
|
267
310
|
types = quote(types.join(', '))
|
@@ -275,7 +318,14 @@ module ActiveRecord
|
|
275
318
|
def raw_connection_do(sql)
|
276
319
|
case @connection_options[:mode]
|
277
320
|
when :dblib
|
278
|
-
@connection.execute(sql)
|
321
|
+
result = @connection.execute(sql)
|
322
|
+
|
323
|
+
# TinyTDS returns false instead of raising an exception if connection fails.
|
324
|
+
# Getting around this by raising an exception ourselves while this PR
|
325
|
+
# https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
|
326
|
+
raise TinyTds::Error, "failed to execute statement" if result.is_a?(FalseClass)
|
327
|
+
|
328
|
+
result.do
|
279
329
|
end
|
280
330
|
ensure
|
281
331
|
@update_sql = false
|
@@ -283,15 +333,31 @@ module ActiveRecord
|
|
283
333
|
|
284
334
|
# === SQLServer Specific (Identity Inserts) ===================== #
|
285
335
|
|
336
|
+
def use_output_inserted?
|
337
|
+
self.class.use_output_inserted
|
338
|
+
end
|
339
|
+
|
340
|
+
def exclude_output_inserted_table_names?
|
341
|
+
!self.class.exclude_output_inserted_table_names.empty?
|
342
|
+
end
|
343
|
+
|
344
|
+
def exclude_output_inserted_table_name?(table_name, sql)
|
345
|
+
return false unless exclude_output_inserted_table_names?
|
346
|
+
table_name ||= get_table_name(sql)
|
347
|
+
return false unless table_name
|
348
|
+
self.class.exclude_output_inserted_table_names[table_name]
|
349
|
+
end
|
350
|
+
|
286
351
|
def exec_insert_requires_identity?(sql, pk, binds)
|
287
|
-
query_requires_identity_insert?(sql)
|
352
|
+
query_requires_identity_insert?(sql)
|
288
353
|
end
|
289
354
|
|
290
355
|
def query_requires_identity_insert?(sql)
|
291
356
|
if insert_sql?(sql)
|
292
357
|
table_name = get_table_name(sql)
|
293
358
|
id_column = identity_columns(table_name).first
|
294
|
-
id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
|
359
|
+
# id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
|
360
|
+
id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? table_name : false
|
295
361
|
else
|
296
362
|
false
|
297
363
|
end
|
@@ -90,18 +90,6 @@ module ActiveRecord
|
|
90
90
|
@connection.configure_connection
|
91
91
|
end
|
92
92
|
|
93
|
-
# @Overwrite
|
94
|
-
# Had some special logic and skipped using gem's internal query methods
|
95
|
-
def select_rows(sql, name = nil, binds = [])
|
96
|
-
|
97
|
-
# In some cases the limit is converted to a `TOP(1)` but the bind parameter is still in the array
|
98
|
-
if !binds.empty? && sql.include?('TOP(1)')
|
99
|
-
binds = binds.delete_if {|b| b.name == 'LIMIT' }
|
100
|
-
end
|
101
|
-
|
102
|
-
exec_query(sql, name, binds).rows
|
103
|
-
end
|
104
|
-
|
105
93
|
# Have to reset this because the default arjdbc functionality is to return false unless a level is passed in
|
106
94
|
def supports_transaction_isolation?
|
107
95
|
true
|
@@ -152,25 +140,17 @@ module ActiveRecord
|
|
152
140
|
[sql, binds, pk, sequence_name]
|
153
141
|
end
|
154
142
|
|
143
|
+
# @Override original version uses driver specific query command
|
144
|
+
def sqlserver_version
|
145
|
+
@sqlserver_version ||= select_value('SELECT @@version').to_s
|
146
|
+
end
|
147
|
+
|
155
148
|
# @Override
|
156
149
|
def translate_exception(exception, message)
|
157
150
|
return ActiveRecord::ValueTooLong.new(message) if exception.message.include?('java.sql.DataTruncation')
|
158
151
|
super
|
159
152
|
end
|
160
153
|
|
161
|
-
# @Overwrite
|
162
|
-
# Made it so we don't use the internal calls from the gem
|
163
|
-
def version_year
|
164
|
-
return @version_year if defined?(@version_year)
|
165
|
-
@version_year = begin
|
166
|
-
vstring = select_value('SELECT @@version').to_s
|
167
|
-
return 2016 if vstring =~ /vNext/
|
168
|
-
/SQL Server (\d+)/.match(vstring).to_a.last.to_s.to_i
|
169
|
-
rescue Exception => e
|
170
|
-
2016
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
154
|
private
|
175
155
|
|
176
156
|
def _quote(value)
|
@@ -8,7 +8,8 @@ module ActiveRecord
|
|
8
8
|
def visit_TableDefinition(o)
|
9
9
|
if o.as
|
10
10
|
table_name = quote_table_name(o.temporary ? "##{o.name}" : o.name)
|
11
|
-
|
11
|
+
query = o.as.respond_to?(:to_sql) ? o.as.to_sql : o.as
|
12
|
+
projections, source = query.match(%r{SELECT\s+(.*)?\s+FROM\s+(.*)?}).captures
|
12
13
|
select_into = "SELECT #{projections} INTO #{table_name} FROM #{source}"
|
13
14
|
else
|
14
15
|
o.instance_variable_set :@as, nil
|
@@ -16,6 +17,20 @@ module ActiveRecord
|
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
20
|
+
def add_column_options!(sql, options)
|
21
|
+
sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
|
22
|
+
if options[:null] == false
|
23
|
+
sql << " NOT NULL"
|
24
|
+
end
|
25
|
+
if options[:is_identity] == true
|
26
|
+
sql << " IDENTITY(1,1)"
|
27
|
+
end
|
28
|
+
if options[:primary_key] == true
|
29
|
+
sql << " PRIMARY KEY"
|
30
|
+
end
|
31
|
+
sql
|
32
|
+
end
|
33
|
+
|
19
34
|
def action_sql(action, dependency)
|
20
35
|
case dependency
|
21
36
|
when :restrict
|
@@ -28,6 +43,14 @@ module ActiveRecord
|
|
28
43
|
end
|
29
44
|
end
|
30
45
|
|
46
|
+
def options_include_default?(options)
|
47
|
+
super || options_primary_key_with_nil_default?(options)
|
48
|
+
end
|
49
|
+
|
50
|
+
def options_primary_key_with_nil_default?(options)
|
51
|
+
options[:primary_key] && options.include?(:default) && options[:default].nil?
|
52
|
+
end
|
53
|
+
|
31
54
|
end
|
32
55
|
end
|
33
56
|
end
|
@@ -1,13 +1,34 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module ConnectionAdapters
|
3
3
|
module SQLServer
|
4
|
-
|
4
|
+
class SchemaDumper < ConnectionAdapters::SchemaDumper
|
5
|
+
|
6
|
+
SQLSEVER_NO_LIMIT_TYPES = [
|
7
|
+
'text',
|
8
|
+
'ntext',
|
9
|
+
'varchar(max)',
|
10
|
+
'nvarchar(max)',
|
11
|
+
'varbinary(max)'
|
12
|
+
].freeze
|
5
13
|
|
6
14
|
private
|
7
15
|
|
16
|
+
def explicit_primary_key_default?(column)
|
17
|
+
column.is_primary? && !column.is_identity?
|
18
|
+
end
|
19
|
+
|
20
|
+
def schema_limit(column)
|
21
|
+
return if SQLSEVER_NO_LIMIT_TYPES.include?(column.sql_type)
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
8
25
|
def schema_collation(column)
|
9
26
|
return unless column.collation
|
10
|
-
column.collation if column.collation != collation
|
27
|
+
column.collation if column.collation != @connection.collation
|
28
|
+
end
|
29
|
+
|
30
|
+
def default_primary_key?(column)
|
31
|
+
super && column.is_primary? && column.is_identity?
|
11
32
|
end
|
12
33
|
|
13
34
|
end
|
@@ -7,35 +7,6 @@ module ActiveRecord
|
|
7
7
|
@native_database_types ||= initialize_native_database_types.freeze
|
8
8
|
end
|
9
9
|
|
10
|
-
def tables(name = nil)
|
11
|
-
ActiveSupport::Deprecation.warn 'Passing arguments to #tables is deprecated without replacement.' if name
|
12
|
-
tables_sql('BASE TABLE')
|
13
|
-
end
|
14
|
-
|
15
|
-
def table_exists?(table_name)
|
16
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
17
|
-
#table_exists? currently checks both tables and views.
|
18
|
-
This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
|
19
|
-
Use #data_source_exists? instead.
|
20
|
-
MSG
|
21
|
-
data_source_exists?(table_name)
|
22
|
-
end
|
23
|
-
|
24
|
-
def data_source_exists?(table_name)
|
25
|
-
return false if table_name.blank?
|
26
|
-
unquoted_table_name = SQLServer::Utils.extract_identifiers(table_name).object
|
27
|
-
super(unquoted_table_name)
|
28
|
-
end
|
29
|
-
|
30
|
-
def views
|
31
|
-
tables_sql('VIEW')
|
32
|
-
end
|
33
|
-
|
34
|
-
def view_exists?(table_name)
|
35
|
-
identifier = SQLServer::Utils.extract_identifiers(table_name)
|
36
|
-
super(identifier.object)
|
37
|
-
end
|
38
|
-
|
39
10
|
def create_table(table_name, comment: nil, **options)
|
40
11
|
res = super
|
41
12
|
clear_cache!
|
@@ -43,29 +14,50 @@ module ActiveRecord
|
|
43
14
|
end
|
44
15
|
|
45
16
|
def drop_table(table_name, options = {})
|
46
|
-
|
17
|
+
# Mimic CASCADE option as best we can.
|
18
|
+
if options[:force] == :cascade
|
19
|
+
execute_procedure(:sp_fkeys, pktable_name: table_name).each do |fkdata|
|
20
|
+
fktable = fkdata['FKTABLE_NAME']
|
21
|
+
fkcolmn = fkdata['FKCOLUMN_NAME']
|
22
|
+
pktable = fkdata['PKTABLE_NAME']
|
23
|
+
pkcolmn = fkdata['PKCOLUMN_NAME']
|
24
|
+
remove_foreign_key fktable, name: fkdata['FK_NAME']
|
25
|
+
do_execute "DELETE FROM #{quote_table_name(fktable)} WHERE #{quote_column_name(fkcolmn)} IN ( SELECT #{quote_column_name(pkcolmn)} FROM #{quote_table_name(pktable)} )"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
if options[:if_exists] && @version_year < 2016
|
47
29
|
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}"
|
48
30
|
else
|
49
31
|
super
|
50
32
|
end
|
51
33
|
end
|
52
34
|
|
53
|
-
def indexes(table_name
|
54
|
-
data = (select("EXEC sp_helpindex #{quote(table_name)}",
|
35
|
+
def indexes(table_name)
|
36
|
+
data = (select("EXEC sp_helpindex #{quote(table_name)}", "SCHEMA") || []) rescue [] # JDBC returns nil instead of an array or erring out for no results
|
55
37
|
data.reduce([]) do |indexes, index|
|
56
38
|
index = index.with_indifferent_access
|
39
|
+
|
57
40
|
if index[:index_description] =~ /primary key/
|
58
41
|
indexes
|
59
42
|
else
|
60
43
|
name = index[:index_name]
|
61
44
|
unique = index[:index_description] =~ /unique/
|
62
45
|
where = select_value("SELECT [filter_definition] FROM sys.indexes WHERE name = #{quote(name)}")
|
63
|
-
|
46
|
+
orders = {}
|
47
|
+
columns = []
|
48
|
+
|
49
|
+
index[:index_keys].split(',').each do |column|
|
64
50
|
column.strip!
|
65
|
-
|
66
|
-
column
|
51
|
+
|
52
|
+
if column.ends_with?('(-)')
|
53
|
+
column.gsub! '(-)', ''
|
54
|
+
orders[column] = :desc
|
55
|
+
end
|
56
|
+
|
57
|
+
columns << column
|
67
58
|
end
|
68
|
-
|
59
|
+
|
60
|
+
indexes << IndexDefinition.new(table_name, name, unique, columns, where: where, orders: orders)
|
69
61
|
end
|
70
62
|
end
|
71
63
|
end
|
@@ -103,10 +95,36 @@ module ActiveRecord
|
|
103
95
|
end
|
104
96
|
|
105
97
|
def primary_keys(table_name)
|
106
|
-
primaries =
|
98
|
+
primaries = primary_keys_select(table_name)
|
107
99
|
primaries.present? ? primaries : identity_columns(table_name).map(&:name)
|
108
100
|
end
|
109
101
|
|
102
|
+
def primary_keys_select(table_name)
|
103
|
+
identifier = database_prefix_identifier(table_name)
|
104
|
+
database = identifier.fully_qualified_database_quoted
|
105
|
+
sql = %{
|
106
|
+
SELECT KCU.COLUMN_NAME AS [name]
|
107
|
+
FROM #{database}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
|
108
|
+
LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
|
109
|
+
ON KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
|
110
|
+
AND KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
|
111
|
+
AND KCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG
|
112
|
+
AND KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
|
113
|
+
AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
|
114
|
+
WHERE KCU.TABLE_NAME = #{prepared_statements ? COLUMN_DEFINITION_BIND_STRING_0 : quote(identifier.object)}
|
115
|
+
AND KCU.TABLE_SCHEMA = #{identifier.schema.blank? ? 'schema_name()' : (prepared_statements ? COLUMN_DEFINITION_BIND_STRING_1 : quote(identifier.schema))}
|
116
|
+
AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
|
117
|
+
ORDER BY KCU.ORDINAL_POSITION ASC
|
118
|
+
}.gsub(/[[:space:]]/, ' ')
|
119
|
+
binds = []
|
120
|
+
if prepared_statements
|
121
|
+
nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
|
122
|
+
binds << Relation::QueryAttribute.new('TABLE_NAME', identifier.object, nv128)
|
123
|
+
binds << Relation::QueryAttribute.new('TABLE_SCHEMA', identifier.schema, nv128) unless identifier.schema.blank?
|
124
|
+
end
|
125
|
+
sp_executesql(sql, 'SCHEMA', binds).map { |r| r['name'] }
|
126
|
+
end
|
127
|
+
|
110
128
|
def rename_table(table_name, new_name)
|
111
129
|
do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
|
112
130
|
rename_table_indexes(table_name, new_name)
|
@@ -124,22 +142,30 @@ module ActiveRecord
|
|
124
142
|
sql_commands = []
|
125
143
|
indexes = []
|
126
144
|
column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s }
|
127
|
-
|
145
|
+
without_constraints = options.key?(:default) || options.key?(:limit)
|
146
|
+
default = if !options.key?(:default) && column_object
|
147
|
+
column_object.default
|
148
|
+
else
|
149
|
+
options[:default]
|
150
|
+
end
|
151
|
+
if without_constraints || (column_object && column_object.type != type.to_sym)
|
128
152
|
remove_default_constraint(table_name, column_name)
|
129
153
|
indexes = indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }
|
130
154
|
remove_indexes(table_name, column_name)
|
131
155
|
end
|
132
156
|
sql_commands << "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(options[:default], column_object)} WHERE #{quote_column_name(column_name)} IS NULL" if !options[:null].nil? && options[:null] == false && !options[:default].nil?
|
133
|
-
sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
134
|
-
sql_commands
|
135
|
-
if
|
136
|
-
|
157
|
+
sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, limit: options[:limit], precision: options[:precision], scale: options[:scale])}"
|
158
|
+
sql_commands.last << ' NOT NULL' if !options[:null].nil? && options[:null] == false
|
159
|
+
if without_constraints
|
160
|
+
default = quote_default_expression(default, column_object || column_for(table_name, column_name))
|
161
|
+
sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{default} FOR #{quote_column_name(column_name)}"
|
137
162
|
end
|
138
163
|
# Add any removed indexes back
|
139
164
|
indexes.each do |index|
|
140
165
|
sql_commands << "CREATE INDEX #{quote_table_name(index.name)} ON #{quote_table_name(table_name)} (#{index.columns.map { |c| quote_column_name(c) }.join(', ')})"
|
141
166
|
end
|
142
167
|
sql_commands.each { |c| do_execute(c) }
|
168
|
+
clear_cache!
|
143
169
|
end
|
144
170
|
|
145
171
|
def change_column_default(table_name, column_name, default_or_changes)
|
@@ -194,13 +220,14 @@ module ActiveRecord
|
|
194
220
|
end
|
195
221
|
end
|
196
222
|
|
197
|
-
def type_to_sql(type, limit
|
223
|
+
def type_to_sql(type, limit: nil, precision: nil, scale: nil, **)
|
198
224
|
type_limitable = %w(string integer float char nchar varchar nvarchar).include?(type.to_s)
|
199
225
|
limit = nil unless type_limitable
|
200
226
|
case type.to_s
|
201
227
|
when 'integer'
|
202
228
|
case limit
|
203
|
-
when 1
|
229
|
+
when 1 then 'tinyint'
|
230
|
+
when 2 then 'smallint'
|
204
231
|
when 3..4, nil then 'integer'
|
205
232
|
when 5..8 then 'bigint'
|
206
233
|
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
@@ -226,7 +253,8 @@ module ActiveRecord
|
|
226
253
|
s.gsub(/\s+(?:ASC|DESC)\b/i, '')
|
227
254
|
.gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
|
228
255
|
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
229
|
-
|
256
|
+
|
257
|
+
(order_columns << super).join(", ")
|
230
258
|
end
|
231
259
|
|
232
260
|
def update_table_definition(table_name, base)
|
@@ -236,23 +264,48 @@ module ActiveRecord
|
|
236
264
|
def change_column_null(table_name, column_name, allow_null, default = nil)
|
237
265
|
table_id = SQLServer::Utils.extract_identifiers(table_name)
|
238
266
|
column_id = SQLServer::Utils.extract_identifiers(column_name)
|
239
|
-
column =
|
267
|
+
column = column_for(table_name, column_name)
|
240
268
|
if !allow_null.nil? && allow_null == false && !default.nil?
|
241
269
|
do_execute("UPDATE #{table_id} SET #{column_id}=#{quote(default)} WHERE #{column_id} IS NULL")
|
242
270
|
end
|
243
|
-
sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
|
271
|
+
sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, limit: column.limit, precision: column.precision, scale: column.scale}"
|
244
272
|
sql << ' NOT NULL' if !allow_null.nil? && allow_null == false
|
245
273
|
do_execute sql
|
246
274
|
end
|
247
275
|
|
276
|
+
def create_schema_dumper(options)
|
277
|
+
SQLServer::SchemaDumper.create(self, options)
|
278
|
+
end
|
279
|
+
|
280
|
+
private
|
248
281
|
|
249
|
-
|
282
|
+
def data_source_sql(name = nil, type: nil)
|
283
|
+
scope = quoted_scope name, type: type
|
284
|
+
table_name = lowercase_schema_reflection_sql 'TABLE_NAME'
|
285
|
+
sql = "SELECT #{table_name}"
|
286
|
+
sql << ' FROM INFORMATION_SCHEMA.TABLES WITH (NOLOCK)'
|
287
|
+
sql << ' WHERE TABLE_CATALOG = DB_NAME()'
|
288
|
+
sql << " AND TABLE_SCHEMA = #{quote(scope[:schema])}"
|
289
|
+
sql << " AND TABLE_NAME = #{quote(scope[:name])}" if scope[:name]
|
290
|
+
sql << " AND TABLE_TYPE = #{quote(scope[:type])}" if scope[:type]
|
291
|
+
sql << " ORDER BY #{table_name}"
|
292
|
+
sql
|
293
|
+
end
|
294
|
+
|
295
|
+
def quoted_scope(name = nil, type: nil)
|
296
|
+
identifier = SQLServer::Utils.extract_identifiers(name)
|
297
|
+
{}.tap do |scope|
|
298
|
+
scope[:schema] = identifier.schema || 'dbo'
|
299
|
+
scope[:name] = identifier.object if identifier.object
|
300
|
+
scope[:type] = type if type
|
301
|
+
end
|
302
|
+
end
|
250
303
|
|
251
304
|
# === SQLServer Specific ======================================== #
|
252
305
|
|
253
306
|
def initialize_native_database_types
|
254
307
|
{
|
255
|
-
primary_key: '
|
308
|
+
primary_key: 'bigint NOT NULL IDENTITY(1,1) PRIMARY KEY',
|
256
309
|
primary_key_nonclustered: 'int NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED',
|
257
310
|
integer: { name: 'int', limit: 4 },
|
258
311
|
bigint: { name: 'bigint' },
|
@@ -286,21 +339,11 @@ module ActiveRecord
|
|
286
339
|
}
|
287
340
|
end
|
288
341
|
|
289
|
-
def tables_sql(type)
|
290
|
-
tn = lowercase_schema_reflection_sql 'TABLE_NAME'
|
291
|
-
sql = "SELECT #{tn} FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = '#{type}' ORDER BY TABLE_NAME"
|
292
|
-
select_values sql, 'SCHEMA'
|
293
|
-
end
|
294
|
-
|
295
342
|
COLUMN_DEFINITION_BIND_STRING_0 = defined?(JRUBY_VERSION) ? '?' : '@0'
|
296
343
|
COLUMN_DEFINITION_BIND_STRING_1 = defined?(JRUBY_VERSION) ? '?' : '@1'
|
297
344
|
|
298
345
|
def column_definitions(table_name)
|
299
|
-
identifier
|
300
|
-
SQLServer::Utils.extract_identifiers("#{database_prefix}#{table_name}")
|
301
|
-
else
|
302
|
-
SQLServer::Utils.extract_identifiers(table_name)
|
303
|
-
end
|
346
|
+
identifier = database_prefix_identifier(table_name)
|
304
347
|
database = identifier.fully_qualified_database_quoted
|
305
348
|
view_exists = view_exists?(table_name)
|
306
349
|
view_tblnm = view_table_name(table_name) if view_exists
|
@@ -386,11 +429,13 @@ module ActiveRecord
|
|
386
429
|
ci[:default_function] = begin
|
387
430
|
default = ci[:default_value]
|
388
431
|
if default.nil? && view_exists
|
389
|
-
default = select_value
|
432
|
+
default = select_value %{
|
390
433
|
SELECT c.COLUMN_DEFAULT
|
391
434
|
FROM #{database}.INFORMATION_SCHEMA.COLUMNS c
|
392
|
-
WHERE
|
393
|
-
|
435
|
+
WHERE
|
436
|
+
c.TABLE_NAME = '#{view_tblnm}'
|
437
|
+
AND c.COLUMN_NAME = '#{views_real_column_name(table_name, ci[:name])}'
|
438
|
+
}.squish, 'SCHEMA'
|
394
439
|
end
|
395
440
|
case default
|
396
441
|
when nil
|
@@ -409,7 +454,7 @@ module ActiveRecord
|
|
409
454
|
else ci[:type]
|
410
455
|
end
|
411
456
|
value = default.match(/\A\((.*)\)\Z/m)[1]
|
412
|
-
value = select_value
|
457
|
+
value = select_value("SELECT CAST(#{value} AS #{type}) AS value", 'SCHEMA')
|
413
458
|
[value, nil]
|
414
459
|
end
|
415
460
|
end
|
@@ -460,13 +505,6 @@ module ActiveRecord
|
|
460
505
|
"DF_#{table_name}_#{column_name}"
|
461
506
|
end
|
462
507
|
|
463
|
-
def detect_column_for!(table_name, column_name)
|
464
|
-
unless column = schema_cache.columns(table_name).find { |c| c.name == column_name.to_s }
|
465
|
-
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
466
|
-
end
|
467
|
-
column
|
468
|
-
end
|
469
|
-
|
470
508
|
def lowercase_schema_reflection_sql(node)
|
471
509
|
lowercase_schema_reflection ? "LOWER(#{node})" : node
|
472
510
|
end
|
@@ -482,7 +520,7 @@ module ActiveRecord
|
|
482
520
|
@view_information ||= {}
|
483
521
|
@view_information[table_name] ||= begin
|
484
522
|
identifier = SQLServer::Utils.extract_identifiers(table_name)
|
485
|
-
view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME =
|
523
|
+
view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", 'SCHEMA'
|
486
524
|
if view_info
|
487
525
|
view_info = view_info.with_indifferent_access
|
488
526
|
if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
|
@@ -505,8 +543,6 @@ module ActiveRecord
|
|
505
543
|
match_data ? match_data[1] : column_name
|
506
544
|
end
|
507
545
|
|
508
|
-
private
|
509
|
-
|
510
546
|
def create_table_definition(*args)
|
511
547
|
SQLServer::TableDefinition.new(*args)
|
512
548
|
end
|