activerecord-sqlserver-adapter 6.0.2 → 6.1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -71
- data/README.md +13 -2
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +1 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +0 -9
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +7 -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 +531 -77
- 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 +22 -8
@@ -3,16 +3,36 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
module ConnectionAdapters
|
5
5
|
module SQLServer
|
6
|
-
class
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
class TypeMetadata < DelegateClass(SqlTypeMetadata)
|
7
|
+
undef to_yaml if method_defined?(:to_yaml)
|
8
|
+
|
9
|
+
include Deduplicable
|
10
|
+
|
11
|
+
attr_reader :sqlserver_options
|
12
|
+
|
13
|
+
def initialize(type_metadata, sqlserver_options: nil)
|
14
|
+
super(type_metadata)
|
15
|
+
@sqlserver_options = sqlserver_options
|
16
|
+
end
|
17
|
+
|
18
|
+
def ==(other)
|
19
|
+
other.is_a?(TypeMetadata) &&
|
20
|
+
__getobj__ == other.__getobj__ &&
|
21
|
+
sqlserver_options == other.sqlserver_options
|
22
|
+
end
|
23
|
+
alias eql? ==
|
24
|
+
|
25
|
+
def hash
|
26
|
+
TypeMetadata.hash ^
|
27
|
+
__getobj__.hash ^
|
28
|
+
sqlserver_options.hash
|
10
29
|
end
|
11
30
|
|
12
|
-
|
31
|
+
private
|
13
32
|
|
14
|
-
def
|
15
|
-
|
33
|
+
def deduplicated
|
34
|
+
__setobj__(__getobj__.deduplicate)
|
35
|
+
super
|
16
36
|
end
|
17
37
|
end
|
18
38
|
end
|
@@ -31,9 +31,9 @@ module ActiveRecord
|
|
31
31
|
module SQLServerRealTransaction
|
32
32
|
attr_reader :starting_isolation_level
|
33
33
|
|
34
|
-
def initialize(connection,
|
34
|
+
def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
|
35
35
|
@connection = connection
|
36
|
-
@starting_isolation_level = current_isolation_level if
|
36
|
+
@starting_isolation_level = current_isolation_level if isolation
|
37
37
|
super
|
38
38
|
end
|
39
39
|
|
@@ -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
|