activerecord-sqlserver-adapter 6.0.3 → 6.1.0.0.rc1
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 +1 -3
- data/CHANGELOG.md +18 -77
- data/Gemfile +1 -2
- data/README.md +13 -2
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +1 -1
- data/appveyor.yml +7 -5
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +0 -13
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +7 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +27 -15
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -1
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -3
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +7 -5
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +27 -7
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +0 -1
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +83 -66
- data/lib/active_record/connection_adapters/sqlserver_column.rb +17 -0
- data/lib/active_record/sqlserver_base.rb +9 -15
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +17 -14
- data/lib/arel/visitors/sqlserver.rb +60 -28
- data/test/cases/adapter_test_sqlserver.rb +17 -15
- data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
- data/test/cases/coerced_tests.rb +470 -95
- data/test/cases/disconnected_test_sqlserver.rb +39 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +9 -0
- data/test/cases/in_clause_test_sqlserver.rb +27 -0
- data/test/cases/migration_test_sqlserver.rb +7 -0
- data/test/cases/order_test_sqlserver.rb +7 -0
- data/test/cases/primary_keys_test_sqlserver.rb +103 -0
- data/test/cases/rake_test_sqlserver.rb +3 -2
- data/test/cases/schema_dumper_test_sqlserver.rb +9 -0
- data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
- data/test/models/sqlserver/sst_string_collation.rb +3 -0
- data/test/schema/sqlserver_specific_schema.rb +7 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
- data/test/support/sql_counter_sqlserver.rb +14 -12
- metadata +23 -9
@@ -59,13 +59,73 @@ module ActiveRecord
|
|
59
59
|
self.use_output_inserted = true
|
60
60
|
self.exclude_output_inserted_table_names = Concurrent::Map.new { false }
|
61
61
|
|
62
|
-
|
62
|
+
class << self
|
63
|
+
def new_client(config)
|
64
|
+
case config[:mode]
|
65
|
+
when :dblib
|
66
|
+
require "tiny_tds"
|
67
|
+
dblib_connect(config)
|
68
|
+
else
|
69
|
+
raise ArgumentError, "Unknown connection mode in #{config.inspect}."
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def dblib_connect(config)
|
74
|
+
TinyTds::Client.new(
|
75
|
+
dataserver: config[:dataserver],
|
76
|
+
host: config[:host],
|
77
|
+
port: config[:port],
|
78
|
+
username: config[:username],
|
79
|
+
password: config[:password],
|
80
|
+
database: config[:database],
|
81
|
+
tds_version: config[:tds_version] || "7.3",
|
82
|
+
appname: config_appname(config),
|
83
|
+
login_timeout: config_login_timeout(config),
|
84
|
+
timeout: config_timeout(config),
|
85
|
+
encoding: config_encoding(config),
|
86
|
+
azure: config[:azure],
|
87
|
+
contained: config[:contained]
|
88
|
+
).tap do |client|
|
89
|
+
if config[:azure]
|
90
|
+
client.execute("SET ANSI_NULLS ON").do
|
91
|
+
client.execute("SET ANSI_NULL_DFLT_ON ON").do
|
92
|
+
client.execute("SET ANSI_PADDING ON").do
|
93
|
+
client.execute("SET ANSI_WARNINGS ON").do
|
94
|
+
else
|
95
|
+
client.execute("SET ANSI_DEFAULTS ON").do
|
96
|
+
end
|
97
|
+
client.execute("SET QUOTED_IDENTIFIER ON").do
|
98
|
+
client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
|
99
|
+
client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
|
100
|
+
client.execute("SET TEXTSIZE 2147483647").do
|
101
|
+
client.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
|
102
|
+
end
|
103
|
+
rescue TinyTds::Error => e
|
104
|
+
raise ActiveRecord::NoDatabaseError if e.message.match(/database .* does not exist/i)
|
105
|
+
raise e
|
106
|
+
end
|
107
|
+
|
108
|
+
def config_appname(config)
|
109
|
+
config[:appname] || configure_application_name || Rails.application.class.name.split("::").first rescue nil
|
110
|
+
end
|
111
|
+
|
112
|
+
def config_login_timeout(config)
|
113
|
+
config[:login_timeout].present? ? config[:login_timeout].to_i : nil
|
114
|
+
end
|
115
|
+
|
116
|
+
def config_timeout(config)
|
117
|
+
config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
|
118
|
+
end
|
119
|
+
|
120
|
+
def config_encoding(config)
|
121
|
+
config[:encoding].present? ? config[:encoding] : nil
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def initialize(connection, logger, _connection_options, config)
|
63
126
|
super(connection, logger, config)
|
64
|
-
# Our Responsibility
|
65
127
|
@connection_options = config
|
66
|
-
|
67
|
-
initialize_dateformatter
|
68
|
-
use_database
|
128
|
+
configure_connection
|
69
129
|
end
|
70
130
|
|
71
131
|
# === Abstract Adapter ========================================== #
|
@@ -226,6 +286,14 @@ module ActiveRecord
|
|
226
286
|
do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
|
227
287
|
end
|
228
288
|
|
289
|
+
def configure_connection
|
290
|
+
@spid = _raw_select("SELECT @@SPID", fetch: :rows).first.first
|
291
|
+
@version_year = version_year
|
292
|
+
|
293
|
+
initialize_dateformatter
|
294
|
+
use_database
|
295
|
+
end
|
296
|
+
|
229
297
|
# === Abstract Adapter (Misc Support) =========================== #
|
230
298
|
|
231
299
|
def tables_with_referential_integrity
|
@@ -375,6 +443,8 @@ module ActiveRecord
|
|
375
443
|
|
376
444
|
def translate_exception(e, message:, sql:, binds:)
|
377
445
|
case message
|
446
|
+
when /(SQL Server client is not connected)|(failed to execute statement)/i
|
447
|
+
ConnectionNotEstablished.new(message)
|
378
448
|
when /(cannot insert duplicate key .* with unique index) | (violation of unique key constraint)/i
|
379
449
|
RecordNotUnique.new(message, sql: sql, binds: binds)
|
380
450
|
when /(conflicted with the foreign key constraint) | (The DELETE statement conflicted with the REFERENCE constraint)/i
|
@@ -408,78 +478,18 @@ module ActiveRecord
|
|
408
478
|
|
409
479
|
# === SQLServer Specific (Connection Management) ================ #
|
410
480
|
|
411
|
-
def connect
|
412
|
-
config = @connection_options
|
413
|
-
@connection = case config[:mode]
|
414
|
-
when :dblib
|
415
|
-
dblib_connect(config)
|
416
|
-
end
|
417
|
-
@spid = _raw_select("SELECT @@SPID", fetch: :rows).first.first
|
418
|
-
@version_year = version_year
|
419
|
-
configure_connection
|
420
|
-
end
|
421
|
-
|
422
481
|
def connection_errors
|
423
482
|
@connection_errors ||= [].tap do |errors|
|
424
483
|
errors << TinyTds::Error if defined?(TinyTds::Error)
|
425
484
|
end
|
426
485
|
end
|
427
486
|
|
428
|
-
def dblib_connect(config)
|
429
|
-
TinyTds::Client.new(
|
430
|
-
dataserver: config[:dataserver],
|
431
|
-
host: config[:host],
|
432
|
-
port: config[:port],
|
433
|
-
username: config[:username],
|
434
|
-
password: config[:password],
|
435
|
-
database: config[:database],
|
436
|
-
tds_version: config[:tds_version] || "7.3",
|
437
|
-
appname: config_appname(config),
|
438
|
-
login_timeout: config_login_timeout(config),
|
439
|
-
timeout: config_timeout(config),
|
440
|
-
encoding: config_encoding(config),
|
441
|
-
azure: config[:azure],
|
442
|
-
contained: config[:contained]
|
443
|
-
).tap do |client|
|
444
|
-
if config[:azure]
|
445
|
-
client.execute("SET ANSI_NULLS ON").do
|
446
|
-
client.execute("SET ANSI_NULL_DFLT_ON ON").do
|
447
|
-
client.execute("SET ANSI_PADDING ON").do
|
448
|
-
client.execute("SET ANSI_WARNINGS ON").do
|
449
|
-
else
|
450
|
-
client.execute("SET ANSI_DEFAULTS ON").do
|
451
|
-
end
|
452
|
-
client.execute("SET QUOTED_IDENTIFIER ON").do
|
453
|
-
client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
|
454
|
-
client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
|
455
|
-
client.execute("SET TEXTSIZE 2147483647").do
|
456
|
-
client.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
|
457
|
-
end
|
458
|
-
end
|
459
|
-
|
460
|
-
def config_appname(config)
|
461
|
-
config[:appname] || configure_application_name || Rails.application.class.name.split("::").first rescue nil
|
462
|
-
end
|
463
|
-
|
464
|
-
def config_login_timeout(config)
|
465
|
-
config[:login_timeout].present? ? config[:login_timeout].to_i : nil
|
466
|
-
end
|
467
|
-
|
468
|
-
def config_timeout(config)
|
469
|
-
config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
|
470
|
-
end
|
471
|
-
|
472
|
-
def config_encoding(config)
|
473
|
-
config[:encoding].present? ? config[:encoding] : nil
|
474
|
-
end
|
475
|
-
|
476
|
-
def configure_connection; end
|
477
|
-
|
478
487
|
def configure_application_name; end
|
479
488
|
|
480
489
|
def initialize_dateformatter
|
481
490
|
@database_dateformat = user_options_dateformat
|
482
491
|
a, b, c = @database_dateformat.each_char.to_a
|
492
|
+
|
483
493
|
[a, b, c].each { |f| f.upcase! if f == "y" }
|
484
494
|
dateformat = "%#{a}-%#{b}-%#{c}"
|
485
495
|
::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
|
@@ -502,6 +512,13 @@ module ActiveRecord
|
|
502
512
|
def sqlserver_version
|
503
513
|
@sqlserver_version ||= _raw_select("SELECT @@version", fetch: :rows).first.first.to_s
|
504
514
|
end
|
515
|
+
|
516
|
+
private
|
517
|
+
|
518
|
+
def connect
|
519
|
+
@connection = self.class.new_client(@connection_options)
|
520
|
+
configure_connection
|
521
|
+
end
|
505
522
|
end
|
506
523
|
end
|
507
524
|
end
|
@@ -27,6 +27,23 @@ module ActiveRecord
|
|
27
27
|
def case_sensitive?
|
28
28
|
collation && collation.match(/_CS/)
|
29
29
|
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# In the Rails version of this method there is an assumption that the `default` value will always be a
|
34
|
+
# `String` class, which must be true for the MySQL/PostgreSQL/SQLite adapters. However, in the SQL Server
|
35
|
+
# adapter the `default` value can also be Boolean/Date/Time/etc. Changed the implementation of this method
|
36
|
+
# to handle non-String `default` objects.
|
37
|
+
def deduplicated
|
38
|
+
@name = -name
|
39
|
+
@sql_type_metadata = sql_type_metadata.deduplicate if sql_type_metadata
|
40
|
+
@default = (default.is_a?(String) ? -default : default.dup.freeze) if default
|
41
|
+
@default_function = -default_function if default_function
|
42
|
+
@collation = -collation if collation
|
43
|
+
@comment = -comment if comment
|
44
|
+
|
45
|
+
freeze
|
46
|
+
end
|
30
47
|
end
|
31
48
|
end
|
32
49
|
end
|
@@ -4,21 +4,15 @@ module ActiveRecord
|
|
4
4
|
module ConnectionHandling
|
5
5
|
def sqlserver_connection(config) #:nodoc:
|
6
6
|
config = config.symbolize_keys
|
7
|
-
config.reverse_merge!
|
8
|
-
mode = config[:mode].to_s.downcase.underscore.to_sym
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
rescue TinyTds::Error => e
|
17
|
-
if e.message.match(/database .* does not exist/i)
|
18
|
-
raise ActiveRecord::NoDatabaseError
|
19
|
-
else
|
20
|
-
raise
|
21
|
-
end
|
7
|
+
config.reverse_merge!(mode: :dblib)
|
8
|
+
config[:mode] = config[:mode].to_s.downcase.underscore.to_sym
|
9
|
+
|
10
|
+
ConnectionAdapters::SQLServerAdapter.new(
|
11
|
+
ConnectionAdapters::SQLServerAdapter.new_client(config),
|
12
|
+
logger,
|
13
|
+
nil,
|
14
|
+
config
|
15
|
+
)
|
22
16
|
end
|
23
17
|
end
|
24
18
|
end
|
@@ -13,13 +13,18 @@ module ActiveRecord
|
|
13
13
|
delegate :connection, :establish_connection, :clear_active_connections!,
|
14
14
|
to: ActiveRecord::Base
|
15
15
|
|
16
|
+
def self.using_database_configurations?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
16
20
|
def initialize(configuration)
|
17
21
|
@configuration = configuration
|
22
|
+
@configuration_hash = @configuration.configuration_hash
|
18
23
|
end
|
19
24
|
|
20
25
|
def create(master_established = false)
|
21
26
|
establish_master_connection unless master_established
|
22
|
-
connection.create_database configuration
|
27
|
+
connection.create_database configuration.database, configuration_hash.merge(collation: default_collation)
|
23
28
|
establish_connection configuration
|
24
29
|
rescue ActiveRecord::StatementInvalid => e
|
25
30
|
if /database .* already exists/i === e.message
|
@@ -31,7 +36,7 @@ module ActiveRecord
|
|
31
36
|
|
32
37
|
def drop
|
33
38
|
establish_master_connection
|
34
|
-
connection.drop_database configuration
|
39
|
+
connection.drop_database configuration.database
|
35
40
|
end
|
36
41
|
|
37
42
|
def charset
|
@@ -49,14 +54,14 @@ module ActiveRecord
|
|
49
54
|
end
|
50
55
|
|
51
56
|
def structure_dump(filename, extra_flags)
|
52
|
-
server_arg = "-S #{Shellwords.escape(
|
53
|
-
server_arg += ":#{Shellwords.escape(
|
57
|
+
server_arg = "-S #{Shellwords.escape(configuration_hash[:host])}"
|
58
|
+
server_arg += ":#{Shellwords.escape(configuration_hash[:port])}" if configuration_hash[:port]
|
54
59
|
command = [
|
55
60
|
"defncopy-ttds",
|
56
61
|
server_arg,
|
57
|
-
"-D #{Shellwords.escape(
|
58
|
-
"-U #{Shellwords.escape(
|
59
|
-
"-P #{Shellwords.escape(
|
62
|
+
"-D #{Shellwords.escape(configuration_hash[:database])}",
|
63
|
+
"-U #{Shellwords.escape(configuration_hash[:username])}",
|
64
|
+
"-P #{Shellwords.escape(configuration_hash[:password])}",
|
60
65
|
"-o #{Shellwords.escape(filename)}",
|
61
66
|
]
|
62
67
|
table_args = connection.tables.map { |t| Shellwords.escape(t) }
|
@@ -80,16 +85,14 @@ module ActiveRecord
|
|
80
85
|
|
81
86
|
private
|
82
87
|
|
83
|
-
|
84
|
-
@configuration
|
85
|
-
end
|
88
|
+
attr_reader :configuration, :configuration_hash
|
86
89
|
|
87
90
|
def default_collation
|
88
|
-
|
91
|
+
configuration_hash[:collation] || DEFAULT_COLLATION
|
89
92
|
end
|
90
93
|
|
91
94
|
def establish_master_connection
|
92
|
-
establish_connection
|
95
|
+
establish_connection configuration_hash.merge(database: "master")
|
93
96
|
end
|
94
97
|
end
|
95
98
|
|
@@ -110,9 +113,9 @@ module ActiveRecord
|
|
110
113
|
end
|
111
114
|
|
112
115
|
def configuration_host_ip(configuration)
|
113
|
-
return nil unless configuration
|
116
|
+
return nil unless configuration.host
|
114
117
|
|
115
|
-
Socket::getaddrinfo(configuration
|
118
|
+
Socket::getaddrinfo(configuration.host, "echo", Socket::AF_INET)[0][3]
|
116
119
|
end
|
117
120
|
|
118
121
|
def local_ipaddr?(host_ip)
|
@@ -11,13 +11,14 @@ module Arel
|
|
11
11
|
|
12
12
|
private
|
13
13
|
|
14
|
-
# SQLServer ToSql/Visitor (
|
14
|
+
# SQLServer ToSql/Visitor (Overrides)
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
BIND_BLOCK = proc { |i| "@#{i - 1}" }
|
17
|
+
private_constant :BIND_BLOCK
|
18
|
+
|
19
|
+
def bind_block; BIND_BLOCK; end
|
19
20
|
|
20
|
-
def visit_Arel_Nodes_Bin
|
21
|
+
def visit_Arel_Nodes_Bin(o, collector)
|
21
22
|
visit o.expr, collector
|
22
23
|
collector << " #{ActiveRecord::ConnectionAdapters::SQLServerAdapter.cs_equality_operator} "
|
23
24
|
end
|
@@ -28,26 +29,26 @@ module Arel
|
|
28
29
|
visit o.right, collector
|
29
30
|
end
|
30
31
|
|
31
|
-
def visit_Arel_Nodes_UpdateStatement(o,
|
32
|
+
def visit_Arel_Nodes_UpdateStatement(o, collector)
|
32
33
|
if o.orders.any? && o.limit.nil?
|
33
34
|
o.limit = Nodes::Limit.new(9_223_372_036_854_775_807)
|
34
35
|
end
|
35
36
|
super
|
36
37
|
end
|
37
38
|
|
38
|
-
def visit_Arel_Nodes_Lock
|
39
|
+
def visit_Arel_Nodes_Lock(o, collector)
|
39
40
|
o.expr = Arel.sql("WITH(UPDLOCK)") if o.expr.to_s =~ /FOR UPDATE/
|
40
41
|
collector << " "
|
41
42
|
visit o.expr, collector
|
42
43
|
end
|
43
44
|
|
44
|
-
def visit_Arel_Nodes_Offset
|
45
|
+
def visit_Arel_Nodes_Offset(o, collector)
|
45
46
|
collector << OFFSET
|
46
47
|
visit o.expr, collector
|
47
48
|
collector << ROWS
|
48
49
|
end
|
49
50
|
|
50
|
-
def visit_Arel_Nodes_Limit
|
51
|
+
def visit_Arel_Nodes_Limit(o, collector)
|
51
52
|
if node_value(o) == 0
|
52
53
|
collector << FETCH0
|
53
54
|
collector << ROWS_ONLY
|
@@ -63,7 +64,38 @@ module Arel
|
|
63
64
|
super
|
64
65
|
end
|
65
66
|
|
66
|
-
def
|
67
|
+
def visit_Arel_Nodes_HomogeneousIn(o, collector)
|
68
|
+
collector.preparable = false
|
69
|
+
|
70
|
+
collector << quote_table_name(o.table_name) << "." << quote_column_name(o.column_name)
|
71
|
+
|
72
|
+
if o.type == :in
|
73
|
+
collector << " IN ("
|
74
|
+
else
|
75
|
+
collector << " NOT IN ("
|
76
|
+
end
|
77
|
+
|
78
|
+
values = o.casted_values
|
79
|
+
|
80
|
+
if values.empty?
|
81
|
+
collector << @connection.quote(nil)
|
82
|
+
elsif @connection.prepared_statements
|
83
|
+
# Monkey-patch start. Add query attribute bindings rather than just values.
|
84
|
+
column_name = o.column_name
|
85
|
+
column_type = o.attribute.relation.type_for_attribute(o.column_name)
|
86
|
+
attrs = values.map { |value| ActiveRecord::Relation::QueryAttribute.new(column_name, value, column_type) }
|
87
|
+
|
88
|
+
collector.add_binds(attrs, &bind_block)
|
89
|
+
# Monkey-patch end.
|
90
|
+
else
|
91
|
+
collector.add_binds(values, &bind_block)
|
92
|
+
end
|
93
|
+
|
94
|
+
collector << ")"
|
95
|
+
collector
|
96
|
+
end
|
97
|
+
|
98
|
+
def visit_Arel_Nodes_SelectStatement(o, collector)
|
67
99
|
@select_statement = o
|
68
100
|
distinct_One_As_One_Is_So_Not_Fetch o
|
69
101
|
if o.with
|
@@ -90,7 +122,7 @@ module Arel
|
|
90
122
|
collector << "OPTION (#{hints})"
|
91
123
|
end
|
92
124
|
|
93
|
-
def visit_Arel_Table
|
125
|
+
def visit_Arel_Table(o, collector)
|
94
126
|
# Apparently, o.engine.connection can actually be a different adapter
|
95
127
|
# than sqlserver. Can be removed if fixed in ActiveRecord. See:
|
96
128
|
# github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/450
|
@@ -112,7 +144,7 @@ module Arel
|
|
112
144
|
end
|
113
145
|
end
|
114
146
|
|
115
|
-
def visit_Arel_Nodes_JoinSource
|
147
|
+
def visit_Arel_Nodes_JoinSource(o, collector)
|
116
148
|
if o.left
|
117
149
|
collector = visit o.left, collector
|
118
150
|
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector
|
@@ -124,7 +156,7 @@ module Arel
|
|
124
156
|
collector
|
125
157
|
end
|
126
158
|
|
127
|
-
def visit_Arel_Nodes_InnerJoin
|
159
|
+
def visit_Arel_Nodes_InnerJoin(o, collector)
|
128
160
|
if o.left.is_a?(Arel::Nodes::As) && o.left.left.is_a?(Arel::Nodes::Lateral)
|
129
161
|
collector << "CROSS "
|
130
162
|
visit o.left, collector
|
@@ -141,7 +173,7 @@ module Arel
|
|
141
173
|
end
|
142
174
|
end
|
143
175
|
|
144
|
-
def visit_Arel_Nodes_OuterJoin
|
176
|
+
def visit_Arel_Nodes_OuterJoin(o, collector)
|
145
177
|
if o.left.is_a?(Arel::Nodes::As) && o.left.left.is_a?(Arel::Nodes::Lateral)
|
146
178
|
collector << "OUTER "
|
147
179
|
visit o.left, collector
|
@@ -154,11 +186,11 @@ module Arel
|
|
154
186
|
end
|
155
187
|
end
|
156
188
|
|
157
|
-
def
|
158
|
-
if Array === right
|
159
|
-
right.each { |node| remove_invalid_ordering_from_select_statement(node) }
|
189
|
+
def visit_Arel_Nodes_In(o, collector)
|
190
|
+
if Array === o.right
|
191
|
+
o.right.each { |node| remove_invalid_ordering_from_select_statement(node) }
|
160
192
|
else
|
161
|
-
remove_invalid_ordering_from_select_statement(right)
|
193
|
+
remove_invalid_ordering_from_select_statement(o.right)
|
162
194
|
end
|
163
195
|
|
164
196
|
super
|
@@ -170,7 +202,7 @@ module Arel
|
|
170
202
|
|
171
203
|
# SQLServer ToSql/Visitor (Additions)
|
172
204
|
|
173
|
-
def visit_Arel_Nodes_SelectStatement_SQLServer_Lock
|
205
|
+
def visit_Arel_Nodes_SelectStatement_SQLServer_Lock(collector, options = {})
|
174
206
|
if select_statement_lock?
|
175
207
|
collector = visit @select_statement.lock, collector
|
176
208
|
collector << " " if options[:space]
|
@@ -178,7 +210,7 @@ module Arel
|
|
178
210
|
collector
|
179
211
|
end
|
180
212
|
|
181
|
-
def visit_Orders_And_Let_Fetch_Happen
|
213
|
+
def visit_Orders_And_Let_Fetch_Happen(o, collector)
|
182
214
|
make_Fetch_Possible_And_Deterministic o
|
183
215
|
unless o.orders.empty?
|
184
216
|
collector << " ORDER BY "
|
@@ -191,14 +223,14 @@ module Arel
|
|
191
223
|
collector
|
192
224
|
end
|
193
225
|
|
194
|
-
def visit_Make_Fetch_Happen
|
226
|
+
def visit_Make_Fetch_Happen(o, collector)
|
195
227
|
o.offset = Nodes::Offset.new(0) if o.limit && !o.offset
|
196
228
|
collector = visit o.offset, collector if o.offset
|
197
229
|
collector = visit o.limit, collector if o.limit
|
198
230
|
collector
|
199
231
|
end
|
200
232
|
|
201
|
-
def visit_Arel_Nodes_Lateral
|
233
|
+
def visit_Arel_Nodes_Lateral(o, collector)
|
202
234
|
collector << "APPLY"
|
203
235
|
collector << " "
|
204
236
|
if o.expr.is_a?(Arel::Nodes::SelectStatement)
|
@@ -226,7 +258,7 @@ module Arel
|
|
226
258
|
@select_statement && @select_statement.lock
|
227
259
|
end
|
228
260
|
|
229
|
-
def make_Fetch_Possible_And_Deterministic
|
261
|
+
def make_Fetch_Possible_And_Deterministic(o)
|
230
262
|
return if o.limit.nil? && o.offset.nil?
|
231
263
|
|
232
264
|
t = table_From_Statement o
|
@@ -239,7 +271,7 @@ module Arel
|
|
239
271
|
end
|
240
272
|
end
|
241
273
|
|
242
|
-
def distinct_One_As_One_Is_So_Not_Fetch
|
274
|
+
def distinct_One_As_One_Is_So_Not_Fetch(o)
|
243
275
|
core = o.cores.first
|
244
276
|
distinct = Nodes::Distinct === core.set_quantifier
|
245
277
|
oneasone = core.projections.all? { |x| x == ActiveRecord::FinderMethods::ONE_AS_ONE }
|
@@ -250,7 +282,7 @@ module Arel
|
|
250
282
|
end
|
251
283
|
end
|
252
284
|
|
253
|
-
def table_From_Statement
|
285
|
+
def table_From_Statement(o)
|
254
286
|
core = o.cores.first
|
255
287
|
if Arel::Table === core.from
|
256
288
|
core.from
|
@@ -261,15 +293,15 @@ module Arel
|
|
261
293
|
end
|
262
294
|
end
|
263
295
|
|
264
|
-
def primary_Key_From_Table
|
296
|
+
def primary_Key_From_Table(t)
|
265
297
|
return unless t
|
266
298
|
|
267
299
|
column_name = @connection.schema_cache.primary_keys(t.name) ||
|
268
|
-
|
300
|
+
@connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
|
269
301
|
column_name ? t[column_name] : nil
|
270
302
|
end
|
271
303
|
|
272
|
-
def remote_server_table_name
|
304
|
+
def remote_server_table_name(o)
|
273
305
|
ActiveRecord::ConnectionAdapters::SQLServer::Utils.extract_identifiers(
|
274
306
|
"#{o.class.engine.connection.database_prefix}#{o.name}"
|
275
307
|
).quoted
|
@@ -53,7 +53,8 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
53
53
|
assert Topic.table_exists?, "Topics table name of 'dbo.topics' should return true for exists."
|
54
54
|
|
55
55
|
# Test when database and owner included in table name.
|
56
|
-
|
56
|
+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
57
|
+
Topic.table_name = "#{db_config.database}.dbo.topics"
|
57
58
|
assert Topic.table_exists?, "Topics table name of '[DATABASE].dbo.topics' should return true for exists."
|
58
59
|
ensure
|
59
60
|
Topic.table_name = "topics"
|
@@ -64,8 +65,8 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
64
65
|
arunit_connection = Topic.connection
|
65
66
|
arunit2_connection = College.connection
|
66
67
|
|
67
|
-
arunit_database = arunit_connection.pool.
|
68
|
-
arunit2_database = arunit2_connection.pool.
|
68
|
+
arunit_database = arunit_connection.pool.db_config.database
|
69
|
+
arunit2_database = arunit2_connection.pool.db_config.database
|
69
70
|
|
70
71
|
# Assert that connections use different default databases schemas.
|
71
72
|
assert_not_equal arunit_database, arunit2_database
|
@@ -100,21 +101,23 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
100
101
|
|
101
102
|
it "test bad connection" do
|
102
103
|
assert_raise ActiveRecord::NoDatabaseError do
|
103
|
-
|
104
|
-
|
104
|
+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
105
|
+
configuration = db_config.configuration_hash.merge(database: "inexistent_activerecord_unittest")
|
106
|
+
ActiveRecord::Base.sqlserver_connection configuration
|
105
107
|
end
|
106
108
|
end
|
107
109
|
|
108
110
|
it "test database exists returns false if database does not exist" do
|
109
|
-
|
110
|
-
|
111
|
+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
112
|
+
configuration = db_config.configuration_hash.merge(database: "inexistent_activerecord_unittest")
|
113
|
+
assert_not ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(configuration),
|
111
114
|
"expected database to not exist"
|
112
115
|
end
|
113
116
|
|
114
117
|
it "test database exists returns true when the database exists" do
|
115
|
-
|
116
|
-
assert ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(
|
117
|
-
"expected database #{
|
118
|
+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
119
|
+
assert ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(db_config.configuration_hash),
|
120
|
+
"expected database #{db_config.database} to exist"
|
118
121
|
end
|
119
122
|
|
120
123
|
describe "with different language" do
|
@@ -466,12 +469,11 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
466
469
|
describe "block writes to a database" do
|
467
470
|
def setup
|
468
471
|
@conn = ActiveRecord::Base.connection
|
469
|
-
@connection_handler = ActiveRecord::Base.connection_handler
|
470
472
|
end
|
471
473
|
|
472
474
|
def test_errors_when_an_insert_query_is_called_while_preventing_writes
|
473
475
|
assert_raises(ActiveRecord::ReadOnlyError) do
|
474
|
-
|
476
|
+
ActiveRecord::Base.while_preventing_writes do
|
475
477
|
@conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
|
476
478
|
end
|
477
479
|
end
|
@@ -481,7 +483,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
481
483
|
@conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
|
482
484
|
|
483
485
|
assert_raises(ActiveRecord::ReadOnlyError) do
|
484
|
-
|
486
|
+
ActiveRecord::Base.while_preventing_writes do
|
485
487
|
@conn.update("UPDATE [subscribers] SET [subscribers].[name] = 'Aidan' WHERE [subscribers].[nick] = 'aido'")
|
486
488
|
end
|
487
489
|
end
|
@@ -491,7 +493,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
491
493
|
@conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
|
492
494
|
|
493
495
|
assert_raises(ActiveRecord::ReadOnlyError) do
|
494
|
-
|
496
|
+
ActiveRecord::Base.while_preventing_writes do
|
495
497
|
@conn.execute("DELETE FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
|
496
498
|
end
|
497
499
|
end
|
@@ -500,7 +502,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
500
502
|
def test_doesnt_error_when_a_select_query_is_called_while_preventing_writes
|
501
503
|
@conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
|
502
504
|
|
503
|
-
|
505
|
+
ActiveRecord::Base.while_preventing_writes do
|
504
506
|
assert_equal 1, @conn.execute("SELECT * FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
|
505
507
|
end
|
506
508
|
end
|