activerecord-jdbcsqlserver-adapter 50.0.0 → 52.0.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/.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
|