activerecord-sqlserver-adapter 6.0.0.rc1 → 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 +26 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +29 -0
- data/CHANGELOG.md +18 -23
- data/Gemfile +11 -5
- data/Guardfile +9 -8
- data/README.md +32 -3
- data/RUNNING_UNIT_TESTS.md +1 -1
- data/Rakefile +12 -16
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +4 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +0 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +1 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +3 -13
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +8 -5
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +58 -43
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +7 -12
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +0 -3
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +15 -15
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -3
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +16 -10
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +131 -105
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +6 -8
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +25 -7
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +1 -5
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +6 -10
- data/lib/active_record/connection_adapters/sqlserver/type.rb +36 -35
- data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/char.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
- data/lib/active_record/connection_adapters/sqlserver/type/float.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/json.rb +0 -1
- data/lib/active_record/connection_adapters/sqlserver/type/money.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/real.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/text.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +6 -9
- data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +1 -3
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +1 -2
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +1 -3
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +1 -3
- data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +8 -11
- data/lib/active_record/connection_adapters/sqlserver/version.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +170 -136
- data/lib/active_record/connection_adapters/sqlserver_column.rb +16 -1
- data/lib/active_record/sqlserver_base.rb +9 -15
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +36 -39
- data/lib/activerecord-sqlserver-adapter.rb +1 -1
- data/lib/arel/visitors/sqlserver.rb +126 -50
- data/lib/arel_sqlserver.rb +2 -2
- data/test/cases/adapter_test_sqlserver.rb +203 -190
- data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
- data/test/cases/change_column_null_test_sqlserver.rb +12 -12
- data/test/cases/coerced_tests.rb +656 -318
- data/test/cases/column_test_sqlserver.rb +285 -284
- data/test/cases/connection_test_sqlserver.rb +15 -20
- data/test/cases/disconnected_test_sqlserver.rb +39 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +26 -19
- data/test/cases/fetch_test_sqlserver.rb +14 -22
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +12 -18
- data/test/cases/helper_sqlserver.rb +13 -15
- data/test/cases/in_clause_test_sqlserver.rb +36 -9
- data/test/cases/index_test_sqlserver.rb +13 -15
- data/test/cases/json_test_sqlserver.rb +23 -25
- data/test/cases/lateral_test_sqlserver.rb +35 -0
- data/test/cases/migration_test_sqlserver.rb +71 -26
- data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
- data/test/cases/order_test_sqlserver.rb +57 -53
- data/test/cases/pessimistic_locking_test_sqlserver.rb +25 -33
- data/test/cases/primary_keys_test_sqlserver.rb +103 -0
- data/test/cases/rake_test_sqlserver.rb +33 -46
- data/test/cases/schema_dumper_test_sqlserver.rb +121 -108
- data/test/cases/schema_test_sqlserver.rb +18 -26
- data/test/cases/scratchpad_test_sqlserver.rb +2 -4
- data/test/cases/showplan_test_sqlserver.rb +24 -33
- data/test/cases/specific_schema_test_sqlserver.rb +66 -65
- data/test/cases/transaction_test_sqlserver.rb +16 -19
- data/test/cases/trigger_test_sqlserver.rb +12 -12
- data/test/cases/utils_test_sqlserver.rb +68 -70
- data/test/cases/uuid_test_sqlserver.rb +11 -13
- data/test/debug.rb +6 -6
- data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
- data/test/migrations/create_clients_and_change_column_null.rb +1 -1
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +2 -4
- data/test/models/sqlserver/booking.rb +1 -1
- data/test/models/sqlserver/customers_view.rb +1 -1
- data/test/models/sqlserver/dollar_table_name.rb +1 -1
- data/test/models/sqlserver/edge_schema.rb +1 -3
- data/test/models/sqlserver/fk_has_fk.rb +1 -1
- data/test/models/sqlserver/fk_has_pk.rb +1 -1
- data/test/models/sqlserver/natural_pk_data.rb +2 -2
- data/test/models/sqlserver/natural_pk_int_data.rb +1 -1
- data/test/models/sqlserver/no_pk_data.rb +1 -1
- data/test/models/sqlserver/object_default.rb +1 -1
- data/test/models/sqlserver/quoted_table.rb +2 -2
- data/test/models/sqlserver/quoted_view_1.rb +1 -1
- data/test/models/sqlserver/quoted_view_2.rb +1 -1
- data/test/models/sqlserver/sst_memory.rb +1 -1
- data/test/models/sqlserver/sst_string_collation.rb +3 -0
- data/test/models/sqlserver/string_default.rb +1 -1
- data/test/models/sqlserver/string_defaults_big_view.rb +1 -1
- data/test/models/sqlserver/string_defaults_view.rb +1 -1
- data/test/models/sqlserver/tinyint_pk.rb +1 -1
- data/test/models/sqlserver/trigger.rb +2 -2
- data/test/models/sqlserver/trigger_history.rb +1 -1
- data/test/models/sqlserver/upper.rb +1 -1
- data/test/models/sqlserver/uppered.rb +1 -1
- data/test/models/sqlserver/uuid.rb +1 -1
- data/test/schema/sqlserver_specific_schema.rb +36 -21
- data/test/support/coerceable_test_sqlserver.rb +1 -4
- data/test/support/connection_reflection.rb +1 -2
- data/test/support/core_ext/query_cache.rb +1 -1
- data/test/support/load_schema_sqlserver.rb +3 -5
- 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/minitest_sqlserver.rb +1 -1
- data/test/support/paths_sqlserver.rb +9 -11
- data/test/support/rake_helpers.rb +12 -10
- data/test/support/sql_counter_sqlserver.rb +14 -16
- data/test/support/test_in_memory_oltp.rb +7 -7
- metadata +31 -11
- data/.travis.yml +0 -23
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module ConnectionAdapters
|
|
5
5
|
class SQLServerColumn < Column
|
|
6
|
-
|
|
7
6
|
def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **sqlserver_options)
|
|
8
7
|
@sqlserver_options = sqlserver_options
|
|
9
8
|
super
|
|
@@ -29,6 +28,22 @@ module ActiveRecord
|
|
|
29
28
|
collation && collation.match(/_CS/)
|
|
30
29
|
end
|
|
31
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
|
|
32
47
|
end
|
|
33
48
|
end
|
|
34
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
|
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
3
|
+
require "active_record/tasks/database_tasks"
|
|
4
|
+
require "shellwords"
|
|
5
|
+
require "ipaddr"
|
|
6
|
+
require "socket"
|
|
7
7
|
|
|
8
8
|
module ActiveRecord
|
|
9
9
|
module Tasks
|
|
10
|
-
|
|
11
10
|
class SQLServerDatabaseTasks
|
|
12
|
-
|
|
13
|
-
DEFAULT_COLLATION = 'SQL_Latin1_General_CP1_CI_AS'
|
|
11
|
+
DEFAULT_COLLATION = "SQL_Latin1_General_CP1_CI_AS"
|
|
14
12
|
|
|
15
13
|
delegate :connection, :establish_connection, :clear_active_connections!,
|
|
16
14
|
to: ActiveRecord::Base
|
|
17
15
|
|
|
16
|
+
def self.using_database_configurations?
|
|
17
|
+
true
|
|
18
|
+
end
|
|
19
|
+
|
|
18
20
|
def initialize(configuration)
|
|
19
21
|
@configuration = configuration
|
|
22
|
+
@configuration_hash = @configuration.configuration_hash
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
def create(master_established = false)
|
|
23
26
|
establish_master_connection unless master_established
|
|
24
|
-
connection.create_database configuration
|
|
27
|
+
connection.create_database configuration.database, configuration_hash.merge(collation: default_collation)
|
|
25
28
|
establish_connection configuration
|
|
26
|
-
rescue ActiveRecord::StatementInvalid =>
|
|
27
|
-
if /database .* already exists/i ===
|
|
29
|
+
rescue ActiveRecord::StatementInvalid => e
|
|
30
|
+
if /database .* already exists/i === e.message
|
|
28
31
|
raise DatabaseAlreadyExists
|
|
29
32
|
else
|
|
30
33
|
raise
|
|
@@ -33,7 +36,7 @@ module ActiveRecord
|
|
|
33
36
|
|
|
34
37
|
def drop
|
|
35
38
|
establish_master_connection
|
|
36
|
-
connection.drop_database configuration
|
|
39
|
+
connection.drop_database configuration.database
|
|
37
40
|
end
|
|
38
41
|
|
|
39
42
|
def charset
|
|
@@ -51,27 +54,28 @@ module ActiveRecord
|
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
def structure_dump(filename, extra_flags)
|
|
54
|
-
server_arg = "-S #{Shellwords.escape(
|
|
55
|
-
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]
|
|
56
59
|
command = [
|
|
57
60
|
"defncopy-ttds",
|
|
58
61
|
server_arg,
|
|
59
|
-
"-D #{Shellwords.escape(
|
|
60
|
-
"-U #{Shellwords.escape(
|
|
61
|
-
"-P #{Shellwords.escape(
|
|
62
|
+
"-D #{Shellwords.escape(configuration_hash[:database])}",
|
|
63
|
+
"-U #{Shellwords.escape(configuration_hash[:username])}",
|
|
64
|
+
"-P #{Shellwords.escape(configuration_hash[:password])}",
|
|
62
65
|
"-o #{Shellwords.escape(filename)}",
|
|
63
66
|
]
|
|
64
67
|
table_args = connection.tables.map { |t| Shellwords.escape(t) }
|
|
65
68
|
command.concat(table_args)
|
|
66
69
|
view_args = connection.views.map { |v| Shellwords.escape(v) }
|
|
67
70
|
command.concat(view_args)
|
|
68
|
-
raise
|
|
71
|
+
raise "Error dumping database" unless Kernel.system(command.join(" "))
|
|
72
|
+
|
|
69
73
|
dump = File.read(filename)
|
|
70
|
-
dump.gsub!(/^USE .*$\nGO\n/,
|
|
71
|
-
dump.gsub!(/^GO\n/,
|
|
72
|
-
dump.gsub!(/nvarchar\(8000\)/,
|
|
73
|
-
dump.gsub!(/nvarchar\(-1\)/,
|
|
74
|
-
dump.gsub!(/text\(\d+\)/,
|
|
74
|
+
dump.gsub!(/^USE .*$\nGO\n/, "") # Strip db USE statements
|
|
75
|
+
dump.gsub!(/^GO\n/, "") # Strip db GO statements
|
|
76
|
+
dump.gsub!(/nvarchar\(8000\)/, "nvarchar(4000)") # Fix nvarchar(8000) column defs
|
|
77
|
+
dump.gsub!(/nvarchar\(-1\)/, "nvarchar(max)") # Fix nvarchar(-1) column defs
|
|
78
|
+
dump.gsub!(/text\(\d+\)/, "text") # Fix text(16) column defs
|
|
75
79
|
File.open(filename, "w") { |file| file.puts dump }
|
|
76
80
|
end
|
|
77
81
|
|
|
@@ -79,33 +83,27 @@ module ActiveRecord
|
|
|
79
83
|
connection.execute File.read(filename)
|
|
80
84
|
end
|
|
81
85
|
|
|
82
|
-
|
|
83
86
|
private
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
@configuration
|
|
87
|
-
end
|
|
88
|
+
attr_reader :configuration, :configuration_hash
|
|
88
89
|
|
|
89
90
|
def default_collation
|
|
90
|
-
|
|
91
|
+
configuration_hash[:collation] || DEFAULT_COLLATION
|
|
91
92
|
end
|
|
92
93
|
|
|
93
94
|
def establish_master_connection
|
|
94
|
-
establish_connection
|
|
95
|
+
establish_connection configuration_hash.merge(database: "master")
|
|
95
96
|
end
|
|
96
|
-
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
module DatabaseTasksSQLServer
|
|
100
|
-
|
|
101
100
|
extend ActiveSupport::Concern
|
|
102
101
|
|
|
103
102
|
module ClassMethods
|
|
104
|
-
|
|
105
103
|
LOCAL_IPADDR = [
|
|
106
|
-
IPAddr.new(
|
|
107
|
-
IPAddr.new(
|
|
108
|
-
IPAddr.new(
|
|
104
|
+
IPAddr.new("192.168.0.0/16"),
|
|
105
|
+
IPAddr.new("10.0.0.0/8"),
|
|
106
|
+
IPAddr.new("172.16.0.0/12")
|
|
109
107
|
]
|
|
110
108
|
|
|
111
109
|
private
|
|
@@ -115,21 +113,20 @@ module ActiveRecord
|
|
|
115
113
|
end
|
|
116
114
|
|
|
117
115
|
def configuration_host_ip(configuration)
|
|
118
|
-
return nil unless configuration
|
|
119
|
-
|
|
116
|
+
return nil unless configuration.host
|
|
117
|
+
|
|
118
|
+
Socket::getaddrinfo(configuration.host, "echo", Socket::AF_INET)[0][3]
|
|
120
119
|
end
|
|
121
120
|
|
|
122
121
|
def local_ipaddr?(host_ip)
|
|
123
122
|
return false unless host_ip
|
|
123
|
+
|
|
124
124
|
LOCAL_IPADDR.any? { |ip| ip.include?(host_ip) }
|
|
125
125
|
end
|
|
126
|
-
|
|
127
126
|
end
|
|
128
|
-
|
|
129
127
|
end
|
|
130
128
|
|
|
131
129
|
DatabaseTasks.register_task %r{sqlserver}, SQLServerDatabaseTasks
|
|
132
130
|
DatabaseTasks.send :include, DatabaseTasksSQLServer
|
|
133
|
-
|
|
134
131
|
end
|
|
135
132
|
end
|
|
@@ -3,23 +3,22 @@
|
|
|
3
3
|
module Arel
|
|
4
4
|
module Visitors
|
|
5
5
|
class SQLServer < Arel::Visitors::ToSql
|
|
6
|
-
|
|
7
6
|
OFFSET = " OFFSET "
|
|
8
7
|
ROWS = " ROWS"
|
|
9
8
|
FETCH = " FETCH NEXT "
|
|
10
9
|
FETCH0 = " FETCH FIRST (SELECT 0) "
|
|
11
10
|
ROWS_ONLY = " ROWS ONLY"
|
|
12
11
|
|
|
13
|
-
|
|
14
12
|
private
|
|
15
13
|
|
|
16
|
-
# SQLServer ToSql/Visitor (
|
|
14
|
+
# SQLServer ToSql/Visitor (Overrides)
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
end
|
|
16
|
+
BIND_BLOCK = proc { |i| "@#{i - 1}" }
|
|
17
|
+
private_constant :BIND_BLOCK
|
|
21
18
|
|
|
22
|
-
def
|
|
19
|
+
def bind_block; BIND_BLOCK; end
|
|
20
|
+
|
|
21
|
+
def visit_Arel_Nodes_Bin(o, collector)
|
|
23
22
|
visit o.expr, collector
|
|
24
23
|
collector << " #{ActiveRecord::ConnectionAdapters::SQLServerAdapter.cs_equality_operator} "
|
|
25
24
|
end
|
|
@@ -30,26 +29,26 @@ module Arel
|
|
|
30
29
|
visit o.right, collector
|
|
31
30
|
end
|
|
32
31
|
|
|
33
|
-
def visit_Arel_Nodes_UpdateStatement(o,
|
|
32
|
+
def visit_Arel_Nodes_UpdateStatement(o, collector)
|
|
34
33
|
if o.orders.any? && o.limit.nil?
|
|
35
34
|
o.limit = Nodes::Limit.new(9_223_372_036_854_775_807)
|
|
36
35
|
end
|
|
37
36
|
super
|
|
38
37
|
end
|
|
39
38
|
|
|
40
|
-
def visit_Arel_Nodes_Lock
|
|
41
|
-
o.expr = Arel.sql(
|
|
39
|
+
def visit_Arel_Nodes_Lock(o, collector)
|
|
40
|
+
o.expr = Arel.sql("WITH(UPDLOCK)") if o.expr.to_s =~ /FOR UPDATE/
|
|
42
41
|
collector << " "
|
|
43
42
|
visit o.expr, collector
|
|
44
43
|
end
|
|
45
44
|
|
|
46
|
-
def visit_Arel_Nodes_Offset
|
|
45
|
+
def visit_Arel_Nodes_Offset(o, collector)
|
|
47
46
|
collector << OFFSET
|
|
48
47
|
visit o.expr, collector
|
|
49
48
|
collector << ROWS
|
|
50
49
|
end
|
|
51
50
|
|
|
52
|
-
def visit_Arel_Nodes_Limit
|
|
51
|
+
def visit_Arel_Nodes_Limit(o, collector)
|
|
53
52
|
if node_value(o) == 0
|
|
54
53
|
collector << FETCH0
|
|
55
54
|
collector << ROWS_ONLY
|
|
@@ -65,14 +64,45 @@ module Arel
|
|
|
65
64
|
super
|
|
66
65
|
end
|
|
67
66
|
|
|
68
|
-
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)
|
|
69
99
|
@select_statement = o
|
|
70
100
|
distinct_One_As_One_Is_So_Not_Fetch o
|
|
71
101
|
if o.with
|
|
72
102
|
collector = visit o.with, collector
|
|
73
103
|
collector << " "
|
|
74
104
|
end
|
|
75
|
-
collector = o.cores.inject(collector) { |c,x|
|
|
105
|
+
collector = o.cores.inject(collector) { |c, x|
|
|
76
106
|
visit_Arel_Nodes_SelectCore(x, c)
|
|
77
107
|
}
|
|
78
108
|
collector = visit_Orders_And_Let_Fetch_Happen o, collector
|
|
@@ -82,19 +112,31 @@ module Arel
|
|
|
82
112
|
@select_statement = nil
|
|
83
113
|
end
|
|
84
114
|
|
|
85
|
-
def
|
|
115
|
+
def visit_Arel_Nodes_SelectCore(o, collector)
|
|
116
|
+
collector = super
|
|
117
|
+
maybe_visit o.optimizer_hints, collector
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def visit_Arel_Nodes_OptimizerHints(o, collector)
|
|
121
|
+
hints = o.expr.map { |v| sanitize_as_option_clause(v) }.join(", ")
|
|
122
|
+
collector << "OPTION (#{hints})"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def visit_Arel_Table(o, collector)
|
|
86
126
|
# Apparently, o.engine.connection can actually be a different adapter
|
|
87
127
|
# than sqlserver. Can be removed if fixed in ActiveRecord. See:
|
|
88
128
|
# github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/450
|
|
89
|
-
table_name =
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
129
|
+
table_name =
|
|
130
|
+
begin
|
|
131
|
+
if o.class.engine.connection.respond_to?(:sqlserver?) && o.class.engine.connection.database_prefix_remote_server?
|
|
132
|
+
remote_server_table_name(o)
|
|
133
|
+
else
|
|
134
|
+
quote_table_name(o.name)
|
|
135
|
+
end
|
|
136
|
+
rescue Exception
|
|
93
137
|
quote_table_name(o.name)
|
|
94
138
|
end
|
|
95
|
-
|
|
96
|
-
quote_table_name(o.name)
|
|
97
|
-
end
|
|
139
|
+
|
|
98
140
|
if o.table_alias
|
|
99
141
|
collector << "#{table_name} #{quote_table_name o.table_alias}"
|
|
100
142
|
else
|
|
@@ -102,51 +144,65 @@ module Arel
|
|
|
102
144
|
end
|
|
103
145
|
end
|
|
104
146
|
|
|
105
|
-
def visit_Arel_Nodes_JoinSource
|
|
147
|
+
def visit_Arel_Nodes_JoinSource(o, collector)
|
|
106
148
|
if o.left
|
|
107
149
|
collector = visit o.left, collector
|
|
108
150
|
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector
|
|
109
151
|
end
|
|
110
152
|
if o.right.any?
|
|
111
153
|
collector << " " if o.left
|
|
112
|
-
collector = inject_join o.right, collector,
|
|
154
|
+
collector = inject_join o.right, collector, " "
|
|
113
155
|
end
|
|
114
156
|
collector
|
|
115
157
|
end
|
|
116
158
|
|
|
117
|
-
def visit_Arel_Nodes_InnerJoin
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if o.right
|
|
122
|
-
collector << " "
|
|
123
|
-
visit(o.right, collector)
|
|
159
|
+
def visit_Arel_Nodes_InnerJoin(o, collector)
|
|
160
|
+
if o.left.is_a?(Arel::Nodes::As) && o.left.left.is_a?(Arel::Nodes::Lateral)
|
|
161
|
+
collector << "CROSS "
|
|
162
|
+
visit o.left, collector
|
|
124
163
|
else
|
|
125
|
-
collector
|
|
164
|
+
collector << "INNER JOIN "
|
|
165
|
+
collector = visit o.left, collector
|
|
166
|
+
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
|
|
167
|
+
if o.right
|
|
168
|
+
collector << " "
|
|
169
|
+
visit(o.right, collector)
|
|
170
|
+
else
|
|
171
|
+
collector
|
|
172
|
+
end
|
|
126
173
|
end
|
|
127
174
|
end
|
|
128
175
|
|
|
129
|
-
def visit_Arel_Nodes_OuterJoin
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
176
|
+
def visit_Arel_Nodes_OuterJoin(o, collector)
|
|
177
|
+
if o.left.is_a?(Arel::Nodes::As) && o.left.left.is_a?(Arel::Nodes::Lateral)
|
|
178
|
+
collector << "OUTER "
|
|
179
|
+
visit o.left, collector
|
|
180
|
+
else
|
|
181
|
+
collector << "LEFT OUTER JOIN "
|
|
182
|
+
collector = visit o.left, collector
|
|
183
|
+
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
|
|
184
|
+
collector << " "
|
|
185
|
+
visit o.right, collector
|
|
186
|
+
end
|
|
135
187
|
end
|
|
136
188
|
|
|
137
|
-
def
|
|
138
|
-
if Array === right
|
|
139
|
-
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) }
|
|
140
192
|
else
|
|
141
|
-
remove_invalid_ordering_from_select_statement(right)
|
|
193
|
+
remove_invalid_ordering_from_select_statement(o.right)
|
|
142
194
|
end
|
|
143
195
|
|
|
144
196
|
super
|
|
145
197
|
end
|
|
146
198
|
|
|
199
|
+
def collect_optimizer_hints(o, collector)
|
|
200
|
+
collector
|
|
201
|
+
end
|
|
202
|
+
|
|
147
203
|
# SQLServer ToSql/Visitor (Additions)
|
|
148
204
|
|
|
149
|
-
def visit_Arel_Nodes_SelectStatement_SQLServer_Lock
|
|
205
|
+
def visit_Arel_Nodes_SelectStatement_SQLServer_Lock(collector, options = {})
|
|
150
206
|
if select_statement_lock?
|
|
151
207
|
collector = visit @select_statement.lock, collector
|
|
152
208
|
collector << " " if options[:space]
|
|
@@ -154,7 +210,7 @@ module Arel
|
|
|
154
210
|
collector
|
|
155
211
|
end
|
|
156
212
|
|
|
157
|
-
def visit_Orders_And_Let_Fetch_Happen
|
|
213
|
+
def visit_Orders_And_Let_Fetch_Happen(o, collector)
|
|
158
214
|
make_Fetch_Possible_And_Deterministic o
|
|
159
215
|
unless o.orders.empty?
|
|
160
216
|
collector << " ORDER BY "
|
|
@@ -167,17 +223,30 @@ module Arel
|
|
|
167
223
|
collector
|
|
168
224
|
end
|
|
169
225
|
|
|
170
|
-
def visit_Make_Fetch_Happen
|
|
226
|
+
def visit_Make_Fetch_Happen(o, collector)
|
|
171
227
|
o.offset = Nodes::Offset.new(0) if o.limit && !o.offset
|
|
172
228
|
collector = visit o.offset, collector if o.offset
|
|
173
229
|
collector = visit o.limit, collector if o.limit
|
|
174
230
|
collector
|
|
175
231
|
end
|
|
176
232
|
|
|
233
|
+
def visit_Arel_Nodes_Lateral(o, collector)
|
|
234
|
+
collector << "APPLY"
|
|
235
|
+
collector << " "
|
|
236
|
+
if o.expr.is_a?(Arel::Nodes::SelectStatement)
|
|
237
|
+
collector << "("
|
|
238
|
+
visit(o.expr, collector)
|
|
239
|
+
collector << ")"
|
|
240
|
+
else
|
|
241
|
+
visit(o.expr, collector)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
177
245
|
# SQLServer Helpers
|
|
178
246
|
|
|
179
247
|
def node_value(node)
|
|
180
248
|
return nil unless node
|
|
249
|
+
|
|
181
250
|
case node.expr
|
|
182
251
|
when NilClass then nil
|
|
183
252
|
when Numeric then node.expr
|
|
@@ -189,18 +258,20 @@ module Arel
|
|
|
189
258
|
@select_statement && @select_statement.lock
|
|
190
259
|
end
|
|
191
260
|
|
|
192
|
-
def make_Fetch_Possible_And_Deterministic
|
|
261
|
+
def make_Fetch_Possible_And_Deterministic(o)
|
|
193
262
|
return if o.limit.nil? && o.offset.nil?
|
|
263
|
+
|
|
194
264
|
t = table_From_Statement o
|
|
195
265
|
pk = primary_Key_From_Table t
|
|
196
266
|
return unless pk
|
|
267
|
+
|
|
197
268
|
if o.orders.empty?
|
|
198
269
|
# Prefer deterministic vs a simple `(SELECT NULL)` expr.
|
|
199
270
|
o.orders = [pk.asc]
|
|
200
271
|
end
|
|
201
272
|
end
|
|
202
273
|
|
|
203
|
-
def distinct_One_As_One_Is_So_Not_Fetch
|
|
274
|
+
def distinct_One_As_One_Is_So_Not_Fetch(o)
|
|
204
275
|
core = o.cores.first
|
|
205
276
|
distinct = Nodes::Distinct === core.set_quantifier
|
|
206
277
|
oneasone = core.projections.all? { |x| x == ActiveRecord::FinderMethods::ONE_AS_ONE }
|
|
@@ -211,7 +282,7 @@ module Arel
|
|
|
211
282
|
end
|
|
212
283
|
end
|
|
213
284
|
|
|
214
|
-
def table_From_Statement
|
|
285
|
+
def table_From_Statement(o)
|
|
215
286
|
core = o.cores.first
|
|
216
287
|
if Arel::Table === core.from
|
|
217
288
|
core.from
|
|
@@ -222,14 +293,15 @@ module Arel
|
|
|
222
293
|
end
|
|
223
294
|
end
|
|
224
295
|
|
|
225
|
-
def primary_Key_From_Table
|
|
296
|
+
def primary_Key_From_Table(t)
|
|
226
297
|
return unless t
|
|
298
|
+
|
|
227
299
|
column_name = @connection.schema_cache.primary_keys(t.name) ||
|
|
228
300
|
@connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
|
|
229
301
|
column_name ? t[column_name] : nil
|
|
230
302
|
end
|
|
231
303
|
|
|
232
|
-
def remote_server_table_name
|
|
304
|
+
def remote_server_table_name(o)
|
|
233
305
|
ActiveRecord::ConnectionAdapters::SQLServer::Utils.extract_identifiers(
|
|
234
306
|
"#{o.class.engine.connection.database_prefix}#{o.name}"
|
|
235
307
|
).quoted
|
|
@@ -243,6 +315,10 @@ module Arel
|
|
|
243
315
|
|
|
244
316
|
node.orders = [] unless node.offset || node.limit
|
|
245
317
|
end
|
|
318
|
+
|
|
319
|
+
def sanitize_as_option_clause(value)
|
|
320
|
+
value.gsub(%r{OPTION \s* \( (.+) \)}xi, "\\1")
|
|
321
|
+
end
|
|
246
322
|
end
|
|
247
323
|
end
|
|
248
324
|
end
|