activerecord-sqlserver-adapter 7.0.4.0 → 7.1.0.beta1
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/.github/workflows/ci.yml +3 -3
- data/.gitignore +3 -1
- data/CHANGELOG.md +2 -69
- data/Gemfile +3 -0
- data/README.md +16 -11
- 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 +87 -131
- 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 +71 -32
- 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 +573 -208
- 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/view_test_sqlserver.rb +46 -0
- 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
- metadata +20 -12
@@ -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
|
-
|
391
|
-
SELECT c.COLUMN_NAME AS [name], c.COLUMN_DEFAULT AS [default]
|
427
|
+
sql = <<~SQL
|
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]
|
@@ -426,7 +465,7 @@ module ActiveRecord
|
|
426
465
|
ci[:default_function] = begin
|
427
466
|
default = ci[:default_value]
|
428
467
|
if default.nil? && view_exists
|
429
|
-
view_column = views_real_column_name(table_name, ci[:name])
|
468
|
+
view_column = views_real_column_name(table_name, ci[:name]).downcase
|
430
469
|
default = default_functions[view_column] if view_column.present?
|
431
470
|
end
|
432
471
|
case default
|
@@ -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
|
|
@@ -620,7 +659,7 @@ module ActiveRecord
|
|
620
659
|
view_definition = view_information(table_name)[:VIEW_DEFINITION]
|
621
660
|
return column_name unless view_definition
|
622
661
|
|
623
|
-
match_data = view_definition.match(/([\w-]*)\s+
|
662
|
+
match_data = view_definition.match(/CREATE\s+VIEW.*AS\s+SELECT.*\W([\w-]*)\s+AS\s+#{column_name}/im)
|
624
663
|
match_data ? match_data[1] : column_name
|
625
664
|
end
|
626
665
|
|
@@ -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", fetch: :rows).first.first.to_s
|
504
|
+
@sqlserver_version ||= _raw_select("SELECT @@version", @raw_connection, fetch: :rows).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, fetch: :rows).first.first
|
563
530
|
|
564
531
|
initialize_dateformatter
|
565
532
|
use_database
|