activerecord-sqlserver-adapter 7.0.5.1 → 7.1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +3 -3
- data/.gitignore +3 -1
- data/CHANGELOG.md +38 -83
- data/Gemfile +3 -0
- data/README.md +16 -11
- data/RUNNING_UNIT_TESTS.md +24 -10
- data/Rakefile +2 -6
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +1 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/abstract_adapter.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +29 -6
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +10 -2
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +15 -3
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -31
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +86 -133
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +5 -5
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +3 -2
- data/lib/active_record/connection_adapters/sqlserver/savepoints.rb +24 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +68 -29
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +6 -0
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +4 -6
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +10 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +81 -114
- data/lib/active_record/connection_adapters/sqlserver_column.rb +1 -0
- data/lib/active_record/sqlserver_base.rb +1 -10
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +5 -2
- data/lib/arel/visitors/sqlserver.rb +0 -33
- data/test/cases/adapter_test_sqlserver.rb +8 -7
- data/test/cases/coerced_tests.rb +526 -235
- data/test/cases/column_test_sqlserver.rb +6 -6
- data/test/cases/connection_test_sqlserver.rb +3 -6
- data/test/cases/disconnected_test_sqlserver.rb +5 -8
- data/test/cases/execute_procedure_test_sqlserver.rb +1 -1
- data/test/cases/rake_test_sqlserver.rb +1 -1
- data/test/cases/schema_dumper_test_sqlserver.rb +2 -2
- data/test/cases/transaction_test_sqlserver.rb +13 -8
- data/test/config.yml +1 -2
- data/test/support/connection_reflection.rb +2 -8
- data/test/support/core_ext/query_cache.rb +7 -1
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
- data/test/support/sql_counter_sqlserver.rb +0 -15
- metadata +14 -8
@@ -23,10 +23,10 @@ module ActiveRecord
|
|
23
23
|
pktable = fkdata["PKTABLE_NAME"]
|
24
24
|
pkcolmn = fkdata["PKCOLUMN_NAME"]
|
25
25
|
remove_foreign_key fktable, name: fkdata["FK_NAME"]
|
26
|
-
|
26
|
+
execute "DELETE FROM #{quote_table_name(fktable)} WHERE #{quote_column_name(fkcolmn)} IN ( SELECT #{quote_column_name(pkcolmn)} FROM #{quote_table_name(pktable)} )"
|
27
27
|
end
|
28
28
|
end
|
29
|
-
if options[:if_exists] &&
|
29
|
+
if options[:if_exists] && version_year < 2016
|
30
30
|
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}", "SCHEMA"
|
31
31
|
else
|
32
32
|
super
|
@@ -39,12 +39,12 @@ module ActiveRecord
|
|
39
39
|
data.reduce([]) do |indexes, index|
|
40
40
|
index = index.with_indifferent_access
|
41
41
|
|
42
|
-
if index[:index_description]
|
42
|
+
if index[:index_description].match?(/primary key/)
|
43
43
|
indexes
|
44
44
|
else
|
45
45
|
name = index[:index_name]
|
46
|
-
unique = index[:index_description]
|
47
|
-
where = select_value("SELECT [filter_definition] FROM sys.indexes WHERE name = #{quote(name)}")
|
46
|
+
unique = index[:index_description].match?(/unique/)
|
47
|
+
where = select_value("SELECT [filter_definition] FROM sys.indexes WHERE name = #{quote(name)}", "SCHEMA")
|
48
48
|
orders = {}
|
49
49
|
columns = []
|
50
50
|
|
@@ -118,15 +118,20 @@ module ActiveRecord
|
|
118
118
|
AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
|
119
119
|
ORDER BY KCU.ORDINAL_POSITION ASC
|
120
120
|
}.gsub(/[[:space:]]/, " ")
|
121
|
+
|
121
122
|
binds = []
|
122
123
|
nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
|
123
124
|
binds << Relation::QueryAttribute.new("TABLE_NAME", identifier.object, nv128)
|
124
125
|
binds << Relation::QueryAttribute.new("TABLE_SCHEMA", identifier.schema, nv128) unless identifier.schema.blank?
|
125
|
-
|
126
|
+
|
127
|
+
internal_exec_query(sql, "SCHEMA", binds).map { |row| row["name"] }
|
126
128
|
end
|
127
129
|
|
128
|
-
def rename_table(table_name, new_name)
|
129
|
-
|
130
|
+
def rename_table(table_name, new_name, **options)
|
131
|
+
validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
|
132
|
+
schema_cache.clear_data_source_cache!(table_name.to_s)
|
133
|
+
schema_cache.clear_data_source_cache!(new_name.to_s)
|
134
|
+
execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
|
130
135
|
rename_table_indexes(table_name, new_name)
|
131
136
|
end
|
132
137
|
|
@@ -137,7 +142,7 @@ module ActiveRecord
|
|
137
142
|
remove_check_constraints(table_name, column_name)
|
138
143
|
remove_default_constraint(table_name, column_name)
|
139
144
|
remove_indexes(table_name, column_name)
|
140
|
-
|
145
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
141
146
|
end
|
142
147
|
|
143
148
|
def change_column(table_name, column_name, type, options = {})
|
@@ -182,7 +187,7 @@ module ActiveRecord
|
|
182
187
|
sql_commands << "CREATE INDEX #{quote_table_name(index.name)} ON #{quote_table_name(table_name)} (#{index.columns.map { |c| quote_column_name(c) }.join(', ')})"
|
183
188
|
end
|
184
189
|
|
185
|
-
sql_commands.each { |c|
|
190
|
+
sql_commands.each { |c| execute(c) }
|
186
191
|
clear_cache!
|
187
192
|
end
|
188
193
|
|
@@ -193,7 +198,7 @@ module ActiveRecord
|
|
193
198
|
|
194
199
|
remove_default_constraint(table_name, column_name)
|
195
200
|
default = extract_new_default_value(default_or_changes)
|
196
|
-
|
201
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote_default_expression(default, column)} FOR #{quote_column_name(column_name)}"
|
197
202
|
clear_cache!
|
198
203
|
end
|
199
204
|
|
@@ -213,23 +218,45 @@ module ActiveRecord
|
|
213
218
|
end
|
214
219
|
|
215
220
|
def remove_index!(table_name, index_name)
|
216
|
-
|
221
|
+
execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
|
222
|
+
end
|
223
|
+
|
224
|
+
def build_change_column_definition(table_name, column_name, type, **options) # :nodoc:
|
225
|
+
td = create_table_definition(table_name)
|
226
|
+
cd = td.new_column_definition(column_name, type, **options)
|
227
|
+
ChangeColumnDefinition.new(cd, column_name)
|
228
|
+
end
|
229
|
+
|
230
|
+
def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
|
231
|
+
column = column_for(table_name, column_name)
|
232
|
+
return unless column
|
233
|
+
|
234
|
+
default = extract_new_default_value(default_or_changes)
|
235
|
+
ChangeColumnDefaultDefinition.new(column, default)
|
217
236
|
end
|
218
237
|
|
219
238
|
def foreign_keys(table_name)
|
220
239
|
identifier = SQLServer::Utils.extract_identifiers(table_name)
|
221
240
|
fk_info = execute_procedure :sp_fkeys, nil, identifier.schema, nil, identifier.object, identifier.schema
|
222
|
-
|
223
|
-
|
224
|
-
|
241
|
+
|
242
|
+
grouped_fk = fk_info.group_by { |row| row["FK_NAME"] }.values.each { |group| group.sort_by! { |row| row["KEY_SEQ"] } }
|
243
|
+
grouped_fk.map do |group|
|
244
|
+
row = group.first
|
225
245
|
options = {
|
226
246
|
name: row["FK_NAME"],
|
227
|
-
column: row["FKCOLUMN_NAME"],
|
228
|
-
primary_key: row["PKCOLUMN_NAME"],
|
229
247
|
on_update: extract_foreign_key_action("update", row["FK_NAME"]),
|
230
248
|
on_delete: extract_foreign_key_action("delete", row["FK_NAME"])
|
231
249
|
}
|
232
|
-
|
250
|
+
|
251
|
+
if group.one?
|
252
|
+
options[:column] = row["FKCOLUMN_NAME"]
|
253
|
+
options[:primary_key] = row["PKCOLUMN_NAME"]
|
254
|
+
else
|
255
|
+
options[:column] = group.map { |row| row["FKCOLUMN_NAME"] }
|
256
|
+
options[:primary_key] = group.map { |row| row["PKCOLUMN_NAME"] }
|
257
|
+
end
|
258
|
+
|
259
|
+
ForeignKeyDefinition.new(identifier.object, row["PKTABLE_NAME"], options)
|
233
260
|
end
|
234
261
|
end
|
235
262
|
|
@@ -268,6 +295,13 @@ module ActiveRecord
|
|
268
295
|
end
|
269
296
|
end
|
270
297
|
|
298
|
+
# In SQL Server only the first column added should have the `ADD` keyword.
|
299
|
+
def add_timestamps(table_name, **options)
|
300
|
+
fragments = add_timestamps_for_alter(table_name, **options)
|
301
|
+
fragments[1..].each { |fragment| fragment.sub!('ADD ', '') }
|
302
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(', ')}"
|
303
|
+
end
|
304
|
+
|
271
305
|
def columns_for_distinct(columns, orders)
|
272
306
|
order_columns = orders.reject(&:blank?).map { |s|
|
273
307
|
s = s.to_sql unless s.is_a?(String)
|
@@ -282,16 +316,19 @@ module ActiveRecord
|
|
282
316
|
SQLServer::Table.new(table_name, base)
|
283
317
|
end
|
284
318
|
|
285
|
-
def change_column_null(table_name, column_name,
|
319
|
+
def change_column_null(table_name, column_name, null, default = nil)
|
320
|
+
validate_change_column_null_argument!(null)
|
321
|
+
|
286
322
|
table_id = SQLServer::Utils.extract_identifiers(table_name)
|
287
323
|
column_id = SQLServer::Utils.extract_identifiers(column_name)
|
288
324
|
column = column_for(table_name, column_name)
|
289
|
-
if !
|
290
|
-
|
325
|
+
if !null.nil? && null == false && !default.nil?
|
326
|
+
execute("UPDATE #{table_id} SET #{column_id}=#{quote(default)} WHERE #{column_id} IS NULL")
|
291
327
|
end
|
292
328
|
sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, limit: column.limit, precision: column.precision, scale: column.scale}"
|
293
|
-
sql += " NOT NULL" if !
|
294
|
-
|
329
|
+
sql += " NOT NULL" if !null.nil? && null == false
|
330
|
+
|
331
|
+
execute sql
|
295
332
|
end
|
296
333
|
|
297
334
|
def create_schema_dumper(options)
|
@@ -387,12 +424,13 @@ module ActiveRecord
|
|
387
424
|
view_tblnm = view_table_name(table_name) if view_exists
|
388
425
|
|
389
426
|
if view_exists
|
390
|
-
|
427
|
+
sql = <<~SQL
|
391
428
|
SELECT LOWER(c.COLUMN_NAME) AS [name], c.COLUMN_DEFAULT AS [default]
|
392
429
|
FROM #{database}.INFORMATION_SCHEMA.COLUMNS c
|
393
430
|
WHERE c.TABLE_NAME = #{quote(view_tblnm)}
|
394
|
-
|
395
|
-
|
431
|
+
SQL
|
432
|
+
results = internal_exec_query(sql, "SCHEMA")
|
433
|
+
default_functions = results.each.with_object({}) { |row, out| out[row["name"]] = row["default"] }.compact
|
396
434
|
end
|
397
435
|
|
398
436
|
sql = column_definitions_sql(database, identifier)
|
@@ -401,7 +439,8 @@ module ActiveRecord
|
|
401
439
|
nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
|
402
440
|
binds << Relation::QueryAttribute.new("TABLE_NAME", identifier.object, nv128)
|
403
441
|
binds << Relation::QueryAttribute.new("TABLE_SCHEMA", identifier.schema, nv128) unless identifier.schema.blank?
|
404
|
-
results =
|
442
|
+
results = internal_exec_query(sql, "SCHEMA", binds)
|
443
|
+
|
405
444
|
columns = results.map do |ci|
|
406
445
|
ci = ci.symbolize_keys
|
407
446
|
ci[:_type] = ci[:type]
|
@@ -543,7 +582,7 @@ module ActiveRecord
|
|
543
582
|
def remove_check_constraints(table_name, column_name)
|
544
583
|
constraints = select_values "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{quote_string(table_name)}' and COLUMN_NAME = '#{quote_string(column_name)}'", "SCHEMA"
|
545
584
|
constraints.each do |constraint|
|
546
|
-
|
585
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
|
547
586
|
end
|
548
587
|
end
|
549
588
|
|
@@ -552,7 +591,7 @@ module ActiveRecord
|
|
552
591
|
execute_procedure(:sp_helpconstraint, table_name, "nomsg").flatten.select do |row|
|
553
592
|
row["constraint_type"] == "DEFAULT on column #{column_name}"
|
554
593
|
end.each do |row|
|
555
|
-
|
594
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
|
556
595
|
end
|
557
596
|
end
|
558
597
|
|
@@ -12,9 +12,9 @@ module ActiveRecord
|
|
12
12
|
OPTION_XML = "SHOWPLAN_XML"
|
13
13
|
OPTIONS = [OPTION_ALL, OPTION_TEXT, OPTION_XML]
|
14
14
|
|
15
|
-
def explain(arel, binds = [])
|
15
|
+
def explain(arel, binds = [], options = [])
|
16
16
|
sql = to_sql(arel)
|
17
|
-
result = with_showplan_on {
|
17
|
+
result = with_showplan_on { internal_exec_query(sql, "EXPLAIN", binds) }
|
18
18
|
printer = showplan_printer.new(result)
|
19
19
|
printer.pp
|
20
20
|
end
|
@@ -30,7 +30,7 @@ module ActiveRecord
|
|
30
30
|
|
31
31
|
def set_showplan_option(enable = true)
|
32
32
|
sql = "SET #{showplan_option} #{enable ? 'ON' : 'OFF'}"
|
33
|
-
|
33
|
+
raw_execute(sql, "SCHEMA")
|
34
34
|
rescue Exception
|
35
35
|
raise ActiveRecordError, "#{showplan_option} could not be turned #{enable ? 'ON' : 'OFF'}, perhaps you do not have SHOWPLAN permissions?"
|
36
36
|
end
|
@@ -5,14 +5,12 @@ require "active_record/connection_adapters/abstract/transaction"
|
|
5
5
|
module ActiveRecord
|
6
6
|
module ConnectionAdapters
|
7
7
|
module SQLServerTransaction
|
8
|
-
|
8
|
+
delegate :sqlserver?, to: :connection, prefix: true
|
9
9
|
|
10
|
-
|
11
|
-
connection.respond_to?(:sqlserver?) && connection.sqlserver?
|
12
|
-
end
|
10
|
+
private
|
13
11
|
|
14
12
|
def current_isolation_level
|
15
|
-
return unless
|
13
|
+
return unless connection_sqlserver?
|
16
14
|
|
17
15
|
level = connection.user_options_isolation_level
|
18
16
|
# When READ_COMMITTED_SNAPSHOT is set to ON,
|
@@ -50,7 +48,7 @@ module ActiveRecord
|
|
50
48
|
private
|
51
49
|
|
52
50
|
def reset_starting_isolation_level
|
53
|
-
if
|
51
|
+
if connection_sqlserver? && starting_isolation_level
|
54
52
|
connection.set_transaction_isolation_level(starting_isolation_level)
|
55
53
|
end
|
56
54
|
end
|
@@ -36,6 +36,16 @@ module ActiveRecord
|
|
36
36
|
self.class == other.class && value == other.value
|
37
37
|
end
|
38
38
|
alias :== :eql?
|
39
|
+
|
40
|
+
def self.from_msgpack_ext(string)
|
41
|
+
type, value = string.chomp!("msgpack_ext").split(',')
|
42
|
+
|
43
|
+
Data.new(value, type.constantize)
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_msgpack_ext
|
47
|
+
[type.class.to_s, value].join(',') + "msgpack_ext"
|
48
|
+
end
|
39
49
|
end
|
40
50
|
end
|
41
51
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "tiny_tds"
|
3
4
|
require "base64"
|
4
5
|
require "active_record"
|
5
6
|
require "arel_sqlserver"
|
@@ -11,11 +12,13 @@ require "active_record/connection_adapters/sqlserver/core_ext/explain_subscriber
|
|
11
12
|
require "active_record/connection_adapters/sqlserver/core_ext/attribute_methods"
|
12
13
|
require "active_record/connection_adapters/sqlserver/core_ext/finder_methods"
|
13
14
|
require "active_record/connection_adapters/sqlserver/core_ext/preloader"
|
15
|
+
require "active_record/connection_adapters/sqlserver/core_ext/abstract_adapter"
|
14
16
|
require "active_record/connection_adapters/sqlserver/version"
|
15
17
|
require "active_record/connection_adapters/sqlserver/type"
|
16
18
|
require "active_record/connection_adapters/sqlserver/database_limits"
|
17
19
|
require "active_record/connection_adapters/sqlserver/database_statements"
|
18
20
|
require "active_record/connection_adapters/sqlserver/database_tasks"
|
21
|
+
require "active_record/connection_adapters/sqlserver/savepoints"
|
19
22
|
require "active_record/connection_adapters/sqlserver/transaction"
|
20
23
|
require "active_record/connection_adapters/sqlserver/errors"
|
21
24
|
require "active_record/connection_adapters/sqlserver/schema_creation"
|
@@ -39,7 +42,8 @@ module ActiveRecord
|
|
39
42
|
SQLServer::Showplan,
|
40
43
|
SQLServer::SchemaStatements,
|
41
44
|
SQLServer::DatabaseLimits,
|
42
|
-
SQLServer::DatabaseTasks
|
45
|
+
SQLServer::DatabaseTasks,
|
46
|
+
SQLServer::Savepoints
|
43
47
|
|
44
48
|
ADAPTER_NAME = "SQLServer".freeze
|
45
49
|
|
@@ -77,93 +81,38 @@ module ActiveRecord
|
|
77
81
|
end
|
78
82
|
|
79
83
|
def new_client(config)
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
+
TinyTds::Client.new(config)
|
85
|
+
rescue TinyTds::Error => error
|
86
|
+
if error.message.match(/database .* does not exist/i)
|
87
|
+
raise ActiveRecord::NoDatabaseError
|
84
88
|
else
|
85
|
-
raise
|
89
|
+
raise
|
86
90
|
end
|
87
91
|
end
|
88
92
|
|
89
|
-
def dblib_connect(config)
|
90
|
-
TinyTds::Client.new(
|
91
|
-
dataserver: config[:dataserver],
|
92
|
-
host: config[:host],
|
93
|
-
port: config[:port],
|
94
|
-
username: config[:username],
|
95
|
-
password: config[:password],
|
96
|
-
database: config[:database],
|
97
|
-
tds_version: config[:tds_version] || "7.3",
|
98
|
-
appname: config_appname(config),
|
99
|
-
login_timeout: config_login_timeout(config),
|
100
|
-
timeout: config_timeout(config),
|
101
|
-
encoding: config_encoding(config),
|
102
|
-
azure: config[:azure],
|
103
|
-
contained: config[:contained]
|
104
|
-
).tap do |client|
|
105
|
-
if config[:azure]
|
106
|
-
client.execute("SET ANSI_NULLS ON").do
|
107
|
-
client.execute("SET ANSI_NULL_DFLT_ON ON").do
|
108
|
-
client.execute("SET ANSI_PADDING ON").do
|
109
|
-
client.execute("SET ANSI_WARNINGS ON").do
|
110
|
-
else
|
111
|
-
client.execute("SET ANSI_DEFAULTS ON").do
|
112
|
-
end
|
113
|
-
client.execute("SET QUOTED_IDENTIFIER ON").do
|
114
|
-
client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
|
115
|
-
client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
|
116
|
-
client.execute("SET TEXTSIZE 2147483647").do
|
117
|
-
client.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
|
118
|
-
end
|
119
|
-
rescue TinyTds::Error => e
|
120
|
-
raise ActiveRecord::NoDatabaseError if e.message.match(/database .* does not exist/i)
|
121
|
-
raise e
|
122
|
-
end
|
123
|
-
|
124
|
-
def config_appname(config)
|
125
|
-
if instance_methods.include?(:configure_application_name)
|
126
|
-
ActiveSupport::Deprecation.warn <<~MSG.squish
|
127
|
-
Configuring the application name used by TinyTDS by overriding the
|
128
|
-
`ActiveRecord::ConnectionAdapters::SQLServerAdapter#configure_application_name`
|
129
|
-
instance method is no longer supported. The application name should configured
|
130
|
-
using the `appname` setting in the `database.yml` file instead. Consult the
|
131
|
-
README for further information."
|
132
|
-
MSG
|
133
|
-
end
|
134
|
-
|
135
|
-
config[:appname] || rails_application_name
|
136
|
-
end
|
137
|
-
|
138
93
|
def rails_application_name
|
139
94
|
Rails.application.class.name.split("::").first
|
140
95
|
rescue
|
141
96
|
nil # Might not be in a Rails context so we fallback to `nil`.
|
142
97
|
end
|
98
|
+
end
|
143
99
|
|
144
|
-
|
145
|
-
|
146
|
-
end
|
147
|
-
|
148
|
-
def config_timeout(config)
|
149
|
-
config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
|
150
|
-
end
|
100
|
+
def initialize(...)
|
101
|
+
super
|
151
102
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
103
|
+
@config[:tds_version] = "7.3" unless @config[:tds_version]
|
104
|
+
@config[:appname] = self.class.rails_application_name unless @config[:appname]
|
105
|
+
@config[:login_timeout] = @config[:login_timeout].present? ? @config[:login_timeout].to_i : nil
|
106
|
+
@config[:timeout] = @config[:timeout].present? ? @config[:timeout].to_i / 1000 : nil
|
107
|
+
@config[:encoding] = @config[:encoding].present? ? @config[:encoding] : nil
|
156
108
|
|
157
|
-
|
158
|
-
super(connection, logger, config)
|
159
|
-
@connection_options = config
|
160
|
-
perform_connection_configuration
|
109
|
+
@connection_parameters ||= @config
|
161
110
|
end
|
162
111
|
|
163
112
|
# === Abstract Adapter ========================================== #
|
164
113
|
|
165
114
|
def arel_visitor
|
166
|
-
Arel::Visitors::SQLServer.new
|
115
|
+
Arel::Visitors::SQLServer.new(self)
|
167
116
|
end
|
168
117
|
|
169
118
|
def valid_type?(type)
|
@@ -171,13 +120,7 @@ module ActiveRecord
|
|
171
120
|
end
|
172
121
|
|
173
122
|
def schema_creation
|
174
|
-
SQLServer::SchemaCreation.new
|
175
|
-
end
|
176
|
-
|
177
|
-
def self.database_exists?(config)
|
178
|
-
!!ActiveRecord::Base.sqlserver_connection(config)
|
179
|
-
rescue ActiveRecord::NoDatabaseError
|
180
|
-
false
|
123
|
+
SQLServer::SchemaCreation.new(self)
|
181
124
|
end
|
182
125
|
|
183
126
|
def supports_ddl_transactions?
|
@@ -229,7 +172,7 @@ module ActiveRecord
|
|
229
172
|
end
|
230
173
|
|
231
174
|
def supports_json?
|
232
|
-
|
175
|
+
version_year >= 2016
|
233
176
|
end
|
234
177
|
|
235
178
|
def supports_comments?
|
@@ -248,12 +191,16 @@ module ActiveRecord
|
|
248
191
|
true
|
249
192
|
end
|
250
193
|
|
194
|
+
def supports_common_table_expressions?
|
195
|
+
true
|
196
|
+
end
|
197
|
+
|
251
198
|
def supports_lazy_transactions?
|
252
199
|
true
|
253
200
|
end
|
254
201
|
|
255
202
|
def supports_in_memory_oltp?
|
256
|
-
|
203
|
+
version_year >= 2014
|
257
204
|
end
|
258
205
|
|
259
206
|
def supports_insert_returning?
|
@@ -272,50 +219,52 @@ module ActiveRecord
|
|
272
219
|
false
|
273
220
|
end
|
274
221
|
|
222
|
+
def return_value_after_insert?(column) # :nodoc:
|
223
|
+
column.is_primary? || column.is_identity?
|
224
|
+
end
|
225
|
+
|
275
226
|
def disable_referential_integrity
|
276
227
|
tables = tables_with_referential_integrity
|
277
|
-
tables.each { |t|
|
228
|
+
tables.each { |t| execute "ALTER TABLE #{quote_table_name(t)} NOCHECK CONSTRAINT ALL" }
|
278
229
|
yield
|
279
230
|
ensure
|
280
|
-
tables.each { |t|
|
231
|
+
tables.each { |t| execute "ALTER TABLE #{quote_table_name(t)} CHECK CONSTRAINT ALL" }
|
281
232
|
end
|
282
233
|
|
283
234
|
# === Abstract Adapter (Connection Management) ================== #
|
284
235
|
|
285
236
|
def active?
|
286
|
-
|
287
|
-
|
288
|
-
raw_connection_do "SELECT 1"
|
289
|
-
true
|
237
|
+
@raw_connection&.active?
|
290
238
|
rescue *connection_errors
|
291
239
|
false
|
292
240
|
end
|
293
241
|
|
294
|
-
def reconnect
|
295
|
-
|
296
|
-
|
242
|
+
def reconnect
|
243
|
+
@raw_connection&.close rescue nil
|
244
|
+
@raw_connection = nil
|
245
|
+
@spid = nil
|
246
|
+
@collation = nil
|
247
|
+
|
297
248
|
connect
|
298
249
|
end
|
299
250
|
|
300
251
|
def disconnect!
|
301
252
|
super
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
end
|
306
|
-
@connection = nil
|
253
|
+
|
254
|
+
@raw_connection&.close rescue nil
|
255
|
+
@raw_connection = nil
|
307
256
|
@spid = nil
|
308
257
|
@collation = nil
|
309
258
|
end
|
310
259
|
|
311
|
-
def clear_cache!
|
260
|
+
def clear_cache!(...)
|
312
261
|
@view_information = nil
|
313
262
|
super
|
314
263
|
end
|
315
264
|
|
316
265
|
def reset!
|
317
266
|
reset_transaction
|
318
|
-
|
267
|
+
execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
|
319
268
|
end
|
320
269
|
|
321
270
|
# === Abstract Adapter (Misc Support) =========================== #
|
@@ -356,7 +305,7 @@ module ActiveRecord
|
|
356
305
|
end
|
357
306
|
|
358
307
|
def database_prefix
|
359
|
-
@
|
308
|
+
@connection_parameters[:database_prefix]
|
360
309
|
end
|
361
310
|
|
362
311
|
def database_prefix_identifier(name)
|
@@ -372,7 +321,7 @@ module ActiveRecord
|
|
372
321
|
end
|
373
322
|
|
374
323
|
def inspect
|
375
|
-
"#<#{self.class} version: #{version},
|
324
|
+
"#<#{self.class} version: #{version}, azure: #{sqlserver_azure?.inspect}>"
|
376
325
|
end
|
377
326
|
|
378
327
|
def combine_bind_parameters(from_clause: [], join_clause: [], where_clause: [], having_clause: [], limit: nil, offset: nil)
|
@@ -386,6 +335,12 @@ module ActiveRecord
|
|
386
335
|
version_year
|
387
336
|
end
|
388
337
|
|
338
|
+
def check_version # :nodoc:
|
339
|
+
if schema_cache.database_version < 2012
|
340
|
+
raise "Your version of SQL Server (#{database_version}) is too old. SQL Server Active Record supports 2012 or higher."
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
389
344
|
class << self
|
390
345
|
protected
|
391
346
|
|
@@ -513,7 +468,7 @@ module ActiveRecord
|
|
513
468
|
# === SQLServer Specific (Connection Management) ================ #
|
514
469
|
|
515
470
|
def connection_errors
|
516
|
-
@
|
471
|
+
@raw_connection_errors ||= [].tap do |errors|
|
517
472
|
errors << TinyTds::Error if defined?(TinyTds::Error)
|
518
473
|
end
|
519
474
|
end
|
@@ -534,32 +489,44 @@ module ActiveRecord
|
|
534
489
|
end
|
535
490
|
|
536
491
|
def version_year
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
492
|
+
@version_year ||= begin
|
493
|
+
if sqlserver_version =~ /vNext/
|
494
|
+
2016
|
495
|
+
else
|
496
|
+
/SQL Server (\d+)/.match(sqlserver_version).to_a.last.to_s.to_i
|
497
|
+
end
|
498
|
+
rescue StandardError
|
499
|
+
2016
|
500
|
+
end
|
542
501
|
end
|
543
502
|
|
544
503
|
def sqlserver_version
|
545
|
-
@sqlserver_version ||= _raw_select("SELECT @@version",
|
504
|
+
@sqlserver_version ||= _raw_select("SELECT @@version", @raw_connection).first.first.to_s
|
546
505
|
end
|
547
506
|
|
548
507
|
private
|
549
508
|
|
550
509
|
def connect
|
551
|
-
@
|
552
|
-
perform_connection_configuration
|
510
|
+
@raw_connection = self.class.new_client(@connection_parameters)
|
553
511
|
end
|
554
512
|
|
555
|
-
def
|
556
|
-
|
557
|
-
|
558
|
-
|
513
|
+
def configure_connection
|
514
|
+
if @config[:azure]
|
515
|
+
@raw_connection.execute("SET ANSI_NULLS ON").do
|
516
|
+
@raw_connection.execute("SET ANSI_NULL_DFLT_ON ON").do
|
517
|
+
@raw_connection.execute("SET ANSI_PADDING ON").do
|
518
|
+
@raw_connection.execute("SET ANSI_WARNINGS ON").do
|
519
|
+
else
|
520
|
+
@raw_connection.execute("SET ANSI_DEFAULTS ON").do
|
521
|
+
end
|
522
|
+
|
523
|
+
@raw_connection.execute("SET QUOTED_IDENTIFIER ON").do
|
524
|
+
@raw_connection.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
|
525
|
+
@raw_connection.execute("SET IMPLICIT_TRANSACTIONS OFF").do
|
526
|
+
@raw_connection.execute("SET TEXTSIZE 2147483647").do
|
527
|
+
@raw_connection.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
|
559
528
|
|
560
|
-
|
561
|
-
@spid = _raw_select("SELECT @@SPID", fetch: :rows).first.first
|
562
|
-
@version_year = version_year
|
529
|
+
@spid = _raw_select("SELECT @@SPID", @raw_connection).first.first
|
563
530
|
|
564
531
|
initialize_dateformatter
|
565
532
|
use_database
|
@@ -7,16 +7,7 @@ module ActiveRecord
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def sqlserver_connection(config) #:nodoc:
|
10
|
-
config
|
11
|
-
config.reverse_merge!(mode: :dblib)
|
12
|
-
config[:mode] = config[:mode].to_s.downcase.underscore.to_sym
|
13
|
-
|
14
|
-
sqlserver_adapter_class.new(
|
15
|
-
sqlserver_adapter_class.new_client(config),
|
16
|
-
logger,
|
17
|
-
nil,
|
18
|
-
config
|
19
|
-
)
|
10
|
+
sqlserver_adapter_class.new(config)
|
20
11
|
end
|
21
12
|
end
|
22
13
|
end
|