activerecord-sqlserver-adapter 5.2.1 → 6.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.editorconfig +9 -0
- data/.github/issue_template.md +23 -0
- data/.github/workflows/ci.yml +26 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +29 -0
- data/CHANGELOG.md +58 -20
- data/{Dockerfile → Dockerfile.ci} +1 -1
- data/Gemfile +48 -41
- data/Guardfile +9 -8
- data/README.md +28 -31
- data/RUNNING_UNIT_TESTS.md +3 -0
- data/Rakefile +14 -16
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +25 -14
- data/appveyor.yml +24 -17
- data/docker-compose.ci.yml +7 -5
- data/guides/RELEASING.md +11 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +3 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +8 -7
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +36 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +6 -4
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +88 -44
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +9 -12
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +46 -8
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +16 -5
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -7
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +210 -163
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +8 -8
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +4 -2
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +43 -44
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +7 -9
- data/lib/active_record/connection_adapters/sqlserver/type.rb +38 -35
- data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/char.rb +7 -4
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +4 -3
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +8 -8
- data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
- data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
- data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
- data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
- data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -11
- data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +145 -94
- data/lib/active_record/connection_adapters/sqlserver_column.rb +9 -5
- data/lib/active_record/sqlserver_base.rb +9 -1
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +28 -32
- data/lib/activerecord-sqlserver-adapter.rb +3 -1
- data/lib/arel/visitors/sqlserver.rb +108 -34
- data/lib/arel_sqlserver.rb +4 -2
- data/test/appveyor/dbsetup.ps1 +4 -4
- data/test/cases/adapter_test_sqlserver.rb +246 -171
- data/test/cases/change_column_null_test_sqlserver.rb +14 -12
- data/test/cases/coerced_tests.rb +722 -381
- data/test/cases/column_test_sqlserver.rb +287 -285
- data/test/cases/connection_test_sqlserver.rb +17 -20
- data/test/cases/execute_procedure_test_sqlserver.rb +20 -20
- data/test/cases/fetch_test_sqlserver.rb +16 -22
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
- data/test/cases/helper_sqlserver.rb +15 -15
- data/test/cases/in_clause_test_sqlserver.rb +36 -0
- data/test/cases/index_test_sqlserver.rb +15 -15
- data/test/cases/json_test_sqlserver.rb +25 -25
- data/test/cases/lateral_test_sqlserver.rb +35 -0
- data/test/cases/migration_test_sqlserver.rb +67 -27
- data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
- data/test/cases/order_test_sqlserver.rb +53 -54
- data/test/cases/pessimistic_locking_test_sqlserver.rb +27 -33
- data/test/cases/rake_test_sqlserver.rb +33 -45
- data/test/cases/schema_dumper_test_sqlserver.rb +115 -109
- data/test/cases/schema_test_sqlserver.rb +20 -26
- data/test/cases/scratchpad_test_sqlserver.rb +4 -4
- data/test/cases/showplan_test_sqlserver.rb +28 -35
- data/test/cases/specific_schema_test_sqlserver.rb +68 -65
- data/test/cases/transaction_test_sqlserver.rb +18 -20
- data/test/cases/trigger_test_sqlserver.rb +14 -13
- data/test/cases/utils_test_sqlserver.rb +70 -70
- data/test/cases/uuid_test_sqlserver.rb +13 -14
- data/test/debug.rb +8 -6
- data/test/migrations/create_clients_and_change_column_null.rb +3 -1
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +4 -4
- data/test/models/sqlserver/booking.rb +3 -1
- data/test/models/sqlserver/customers_view.rb +3 -1
- data/test/models/sqlserver/datatype.rb +2 -0
- data/test/models/sqlserver/datatype_migration.rb +2 -0
- data/test/models/sqlserver/dollar_table_name.rb +3 -1
- data/test/models/sqlserver/edge_schema.rb +3 -3
- data/test/models/sqlserver/fk_has_fk.rb +3 -1
- data/test/models/sqlserver/fk_has_pk.rb +3 -1
- data/test/models/sqlserver/natural_pk_data.rb +4 -2
- data/test/models/sqlserver/natural_pk_int_data.rb +3 -1
- data/test/models/sqlserver/no_pk_data.rb +3 -1
- data/test/models/sqlserver/object_default.rb +3 -1
- data/test/models/sqlserver/quoted_table.rb +4 -2
- data/test/models/sqlserver/quoted_view_1.rb +3 -1
- data/test/models/sqlserver/quoted_view_2.rb +3 -1
- data/test/models/sqlserver/sst_memory.rb +3 -1
- data/test/models/sqlserver/string_default.rb +3 -1
- data/test/models/sqlserver/string_defaults_big_view.rb +3 -1
- data/test/models/sqlserver/string_defaults_view.rb +3 -1
- data/test/models/sqlserver/tinyint_pk.rb +3 -1
- data/test/models/sqlserver/trigger.rb +4 -2
- data/test/models/sqlserver/trigger_history.rb +3 -1
- data/test/models/sqlserver/upper.rb +3 -1
- data/test/models/sqlserver/uppered.rb +3 -1
- data/test/models/sqlserver/uuid.rb +3 -1
- data/test/schema/sqlserver_specific_schema.rb +31 -21
- data/test/support/coerceable_test_sqlserver.rb +15 -9
- data/test/support/connection_reflection.rb +3 -2
- data/test/support/core_ext/query_cache.rb +4 -1
- data/test/support/load_schema_sqlserver.rb +5 -5
- data/test/support/minitest_sqlserver.rb +3 -1
- data/test/support/paths_sqlserver.rb +11 -11
- data/test/support/rake_helpers.rb +13 -10
- data/test/support/sql_counter_sqlserver.rb +3 -4
- data/test/support/test_in_memory_oltp.rb +9 -7
- metadata +27 -12
- data/.travis.yml +0 -25
@@ -1,10 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module ConnectionAdapters
|
3
5
|
class SQLServerColumn < Column
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
super(name, default, sql_type_metadata, null, table_name, default_function, collation, comment: comment)
|
6
|
+
def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **sqlserver_options)
|
7
|
+
@sqlserver_options = sqlserver_options
|
8
|
+
super
|
8
9
|
end
|
9
10
|
|
10
11
|
def is_identity?
|
@@ -15,6 +16,10 @@ module ActiveRecord
|
|
15
16
|
@sqlserver_options[:is_primary]
|
16
17
|
end
|
17
18
|
|
19
|
+
def table_name
|
20
|
+
@sqlserver_options[:table_name]
|
21
|
+
end
|
22
|
+
|
18
23
|
def is_utf8?
|
19
24
|
sql_type =~ /nvarchar|ntext|nchar/i
|
20
25
|
end
|
@@ -22,7 +27,6 @@ module ActiveRecord
|
|
22
27
|
def case_sensitive?
|
23
28
|
collation && collation.match(/_CS/)
|
24
29
|
end
|
25
|
-
|
26
30
|
end
|
27
31
|
end
|
28
32
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module ConnectionHandling
|
3
5
|
def sqlserver_connection(config) #:nodoc:
|
@@ -6,11 +8,17 @@ module ActiveRecord
|
|
6
8
|
mode = config[:mode].to_s.downcase.underscore.to_sym
|
7
9
|
case mode
|
8
10
|
when :dblib
|
9
|
-
require
|
11
|
+
require "tiny_tds"
|
10
12
|
else
|
11
13
|
raise ArgumentError, "Unknown connection mode in #{config.inspect}."
|
12
14
|
end
|
13
15
|
ConnectionAdapters::SQLServerAdapter.new(nil, nil, config.merge(mode: mode))
|
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
|
14
22
|
end
|
15
23
|
end
|
16
24
|
end
|
@@ -1,14 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record/tasks/database_tasks"
|
4
|
+
require "shellwords"
|
5
|
+
require "ipaddr"
|
6
|
+
require "socket"
|
5
7
|
|
6
8
|
module ActiveRecord
|
7
9
|
module Tasks
|
8
|
-
|
9
10
|
class SQLServerDatabaseTasks
|
10
|
-
|
11
|
-
DEFAULT_COLLATION = 'SQL_Latin1_General_CP1_CI_AS'
|
11
|
+
DEFAULT_COLLATION = "SQL_Latin1_General_CP1_CI_AS"
|
12
12
|
|
13
13
|
delegate :connection, :establish_connection, :clear_active_connections!,
|
14
14
|
to: ActiveRecord::Base
|
@@ -19,10 +19,10 @@ module ActiveRecord
|
|
19
19
|
|
20
20
|
def create(master_established = false)
|
21
21
|
establish_master_connection unless master_established
|
22
|
-
connection.create_database configuration[
|
22
|
+
connection.create_database configuration["database"], configuration.merge("collation" => default_collation)
|
23
23
|
establish_connection configuration
|
24
|
-
rescue ActiveRecord::StatementInvalid =>
|
25
|
-
if /database .* already exists/i ===
|
24
|
+
rescue ActiveRecord::StatementInvalid => e
|
25
|
+
if /database .* already exists/i === e.message
|
26
26
|
raise DatabaseAlreadyExists
|
27
27
|
else
|
28
28
|
raise
|
@@ -31,7 +31,7 @@ module ActiveRecord
|
|
31
31
|
|
32
32
|
def drop
|
33
33
|
establish_master_connection
|
34
|
-
connection.drop_database configuration[
|
34
|
+
connection.drop_database configuration["database"]
|
35
35
|
end
|
36
36
|
|
37
37
|
def charset
|
@@ -50,7 +50,7 @@ module ActiveRecord
|
|
50
50
|
|
51
51
|
def structure_dump(filename, extra_flags)
|
52
52
|
server_arg = "-S #{Shellwords.escape(configuration['host'])}"
|
53
|
-
server_arg += ":#{Shellwords.escape(configuration['port'])}" if configuration[
|
53
|
+
server_arg += ":#{Shellwords.escape(configuration['port'])}" if configuration["port"]
|
54
54
|
command = [
|
55
55
|
"defncopy-ttds",
|
56
56
|
server_arg,
|
@@ -63,13 +63,14 @@ module ActiveRecord
|
|
63
63
|
command.concat(table_args)
|
64
64
|
view_args = connection.views.map { |v| Shellwords.escape(v) }
|
65
65
|
command.concat(view_args)
|
66
|
-
raise
|
66
|
+
raise "Error dumping database" unless Kernel.system(command.join(" "))
|
67
|
+
|
67
68
|
dump = File.read(filename)
|
68
|
-
dump.gsub!(/^USE .*$\nGO\n/,
|
69
|
-
dump.gsub!(/^GO\n/,
|
70
|
-
dump.gsub!(/nvarchar\(8000\)/,
|
71
|
-
dump.gsub!(/nvarchar\(-1\)/,
|
72
|
-
dump.gsub!(/text\(\d+\)/,
|
69
|
+
dump.gsub!(/^USE .*$\nGO\n/, "") # Strip db USE statements
|
70
|
+
dump.gsub!(/^GO\n/, "") # Strip db GO statements
|
71
|
+
dump.gsub!(/nvarchar\(8000\)/, "nvarchar(4000)") # Fix nvarchar(8000) column defs
|
72
|
+
dump.gsub!(/nvarchar\(-1\)/, "nvarchar(max)") # Fix nvarchar(-1) column defs
|
73
|
+
dump.gsub!(/text\(\d+\)/, "text") # Fix text(16) column defs
|
73
74
|
File.open(filename, "w") { |file| file.puts dump }
|
74
75
|
end
|
75
76
|
|
@@ -77,7 +78,6 @@ module ActiveRecord
|
|
77
78
|
connection.execute File.read(filename)
|
78
79
|
end
|
79
80
|
|
80
|
-
|
81
81
|
private
|
82
82
|
|
83
83
|
def configuration
|
@@ -85,25 +85,22 @@ module ActiveRecord
|
|
85
85
|
end
|
86
86
|
|
87
87
|
def default_collation
|
88
|
-
configuration[
|
88
|
+
configuration["collation"] || DEFAULT_COLLATION
|
89
89
|
end
|
90
90
|
|
91
91
|
def establish_master_connection
|
92
|
-
establish_connection configuration.merge(
|
92
|
+
establish_connection configuration.merge("database" => "master")
|
93
93
|
end
|
94
|
-
|
95
94
|
end
|
96
95
|
|
97
96
|
module DatabaseTasksSQLServer
|
98
|
-
|
99
97
|
extend ActiveSupport::Concern
|
100
98
|
|
101
99
|
module ClassMethods
|
102
|
-
|
103
100
|
LOCAL_IPADDR = [
|
104
|
-
IPAddr.new(
|
105
|
-
IPAddr.new(
|
106
|
-
IPAddr.new(
|
101
|
+
IPAddr.new("192.168.0.0/16"),
|
102
|
+
IPAddr.new("10.0.0.0/8"),
|
103
|
+
IPAddr.new("172.16.0.0/12")
|
107
104
|
]
|
108
105
|
|
109
106
|
private
|
@@ -113,21 +110,20 @@ module ActiveRecord
|
|
113
110
|
end
|
114
111
|
|
115
112
|
def configuration_host_ip(configuration)
|
116
|
-
return nil unless configuration[
|
117
|
-
|
113
|
+
return nil unless configuration["host"]
|
114
|
+
|
115
|
+
Socket::getaddrinfo(configuration["host"], "echo", Socket::AF_INET)[0][3]
|
118
116
|
end
|
119
117
|
|
120
118
|
def local_ipaddr?(host_ip)
|
121
119
|
return false unless host_ip
|
120
|
+
|
122
121
|
LOCAL_IPADDR.any? { |ip| ip.include?(host_ip) }
|
123
122
|
end
|
124
|
-
|
125
123
|
end
|
126
|
-
|
127
124
|
end
|
128
125
|
|
129
126
|
DatabaseTasks.register_task %r{sqlserver}, SQLServerDatabaseTasks
|
130
127
|
DatabaseTasks.send :include, DatabaseTasksSQLServer
|
131
|
-
|
132
128
|
end
|
133
129
|
end
|
@@ -1,20 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Arel
|
2
4
|
module Visitors
|
3
5
|
class SQLServer < Arel::Visitors::ToSql
|
4
|
-
|
5
6
|
OFFSET = " OFFSET "
|
6
7
|
ROWS = " ROWS"
|
7
8
|
FETCH = " FETCH NEXT "
|
8
9
|
FETCH0 = " FETCH FIRST (SELECT 0) "
|
9
10
|
ROWS_ONLY = " ROWS ONLY"
|
10
11
|
|
11
|
-
|
12
12
|
private
|
13
13
|
|
14
14
|
# SQLServer ToSql/Visitor (Overides)
|
15
15
|
|
16
16
|
def visit_Arel_Nodes_BindParam o, collector
|
17
|
-
collector.add_bind(o.value) { |i| "@#{i-1}" }
|
17
|
+
collector.add_bind(o.value) { |i| "@#{i - 1}" }
|
18
18
|
end
|
19
19
|
|
20
20
|
def visit_Arel_Nodes_Bin o, collector
|
@@ -22,6 +22,12 @@ module Arel
|
|
22
22
|
collector << " #{ActiveRecord::ConnectionAdapters::SQLServerAdapter.cs_equality_operator} "
|
23
23
|
end
|
24
24
|
|
25
|
+
def visit_Arel_Nodes_Concat(o, collector)
|
26
|
+
visit o.left, collector
|
27
|
+
collector << " + "
|
28
|
+
visit o.right, collector
|
29
|
+
end
|
30
|
+
|
25
31
|
def visit_Arel_Nodes_UpdateStatement(o, a)
|
26
32
|
if o.orders.any? && o.limit.nil?
|
27
33
|
o.limit = Nodes::Limit.new(9_223_372_036_854_775_807)
|
@@ -30,8 +36,8 @@ module Arel
|
|
30
36
|
end
|
31
37
|
|
32
38
|
def visit_Arel_Nodes_Lock o, collector
|
33
|
-
o.expr = Arel.sql(
|
34
|
-
collector <<
|
39
|
+
o.expr = Arel.sql("WITH(UPDLOCK)") if o.expr.to_s =~ /FOR UPDATE/
|
40
|
+
collector << " "
|
35
41
|
visit o.expr, collector
|
36
42
|
end
|
37
43
|
|
@@ -52,14 +58,19 @@ module Arel
|
|
52
58
|
end
|
53
59
|
end
|
54
60
|
|
61
|
+
def visit_Arel_Nodes_Grouping(o, collector)
|
62
|
+
remove_invalid_ordering_from_select_statement(o.expr)
|
63
|
+
super
|
64
|
+
end
|
65
|
+
|
55
66
|
def visit_Arel_Nodes_SelectStatement o, collector
|
56
67
|
@select_statement = o
|
57
68
|
distinct_One_As_One_Is_So_Not_Fetch o
|
58
69
|
if o.with
|
59
70
|
collector = visit o.with, collector
|
60
|
-
collector <<
|
71
|
+
collector << " "
|
61
72
|
end
|
62
|
-
collector = o.cores.inject(collector) { |c,x|
|
73
|
+
collector = o.cores.inject(collector) { |c, x|
|
63
74
|
visit_Arel_Nodes_SelectCore(x, c)
|
64
75
|
}
|
65
76
|
collector = visit_Orders_And_Let_Fetch_Happen o, collector
|
@@ -69,19 +80,31 @@ module Arel
|
|
69
80
|
@select_statement = nil
|
70
81
|
end
|
71
82
|
|
83
|
+
def visit_Arel_Nodes_SelectCore(o, collector)
|
84
|
+
collector = super
|
85
|
+
maybe_visit o.optimizer_hints, collector
|
86
|
+
end
|
87
|
+
|
88
|
+
def visit_Arel_Nodes_OptimizerHints(o, collector)
|
89
|
+
hints = o.expr.map { |v| sanitize_as_option_clause(v) }.join(", ")
|
90
|
+
collector << "OPTION (#{hints})"
|
91
|
+
end
|
92
|
+
|
72
93
|
def visit_Arel_Table o, collector
|
73
94
|
# Apparently, o.engine.connection can actually be a different adapter
|
74
95
|
# than sqlserver. Can be removed if fixed in ActiveRecord. See:
|
75
96
|
# github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/450
|
76
|
-
table_name =
|
77
|
-
|
78
|
-
|
79
|
-
|
97
|
+
table_name =
|
98
|
+
begin
|
99
|
+
if o.class.engine.connection.respond_to?(:sqlserver?) && o.class.engine.connection.database_prefix_remote_server?
|
100
|
+
remote_server_table_name(o)
|
101
|
+
else
|
102
|
+
quote_table_name(o.name)
|
103
|
+
end
|
104
|
+
rescue Exception
|
80
105
|
quote_table_name(o.name)
|
81
106
|
end
|
82
|
-
|
83
|
-
quote_table_name(o.name)
|
84
|
-
end
|
107
|
+
|
85
108
|
if o.table_alias
|
86
109
|
collector << "#{table_name} #{quote_table_name o.table_alias}"
|
87
110
|
else
|
@@ -95,30 +118,54 @@ module Arel
|
|
95
118
|
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector
|
96
119
|
end
|
97
120
|
if o.right.any?
|
98
|
-
collector <<
|
99
|
-
collector = inject_join o.right, collector,
|
121
|
+
collector << " " if o.left
|
122
|
+
collector = inject_join o.right, collector, " "
|
100
123
|
end
|
101
124
|
collector
|
102
125
|
end
|
103
126
|
|
104
127
|
def visit_Arel_Nodes_InnerJoin o, collector
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
if o.right
|
109
|
-
collector << SPACE
|
110
|
-
visit(o.right, collector)
|
128
|
+
if o.left.is_a?(Arel::Nodes::As) && o.left.left.is_a?(Arel::Nodes::Lateral)
|
129
|
+
collector << "CROSS "
|
130
|
+
visit o.left, collector
|
111
131
|
else
|
112
|
-
collector
|
132
|
+
collector << "INNER JOIN "
|
133
|
+
collector = visit o.left, collector
|
134
|
+
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
|
135
|
+
if o.right
|
136
|
+
collector << " "
|
137
|
+
visit(o.right, collector)
|
138
|
+
else
|
139
|
+
collector
|
140
|
+
end
|
113
141
|
end
|
114
142
|
end
|
115
143
|
|
116
144
|
def visit_Arel_Nodes_OuterJoin o, collector
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
145
|
+
if o.left.is_a?(Arel::Nodes::As) && o.left.left.is_a?(Arel::Nodes::Lateral)
|
146
|
+
collector << "OUTER "
|
147
|
+
visit o.left, collector
|
148
|
+
else
|
149
|
+
collector << "LEFT OUTER JOIN "
|
150
|
+
collector = visit o.left, collector
|
151
|
+
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
|
152
|
+
collector << " "
|
153
|
+
visit o.right, collector
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def collect_in_clause(left, right, collector)
|
158
|
+
if Array === right
|
159
|
+
right.each { |node| remove_invalid_ordering_from_select_statement(node) }
|
160
|
+
else
|
161
|
+
remove_invalid_ordering_from_select_statement(right)
|
162
|
+
end
|
163
|
+
|
164
|
+
super
|
165
|
+
end
|
166
|
+
|
167
|
+
def collect_optimizer_hints(o, collector)
|
168
|
+
collector
|
122
169
|
end
|
123
170
|
|
124
171
|
# SQLServer ToSql/Visitor (Additions)
|
@@ -126,7 +173,7 @@ module Arel
|
|
126
173
|
def visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, options = {}
|
127
174
|
if select_statement_lock?
|
128
175
|
collector = visit @select_statement.lock, collector
|
129
|
-
collector <<
|
176
|
+
collector << " " if options[:space]
|
130
177
|
end
|
131
178
|
collector
|
132
179
|
end
|
@@ -134,12 +181,11 @@ module Arel
|
|
134
181
|
def visit_Orders_And_Let_Fetch_Happen o, collector
|
135
182
|
make_Fetch_Possible_And_Deterministic o
|
136
183
|
unless o.orders.empty?
|
137
|
-
collector <<
|
138
|
-
collector << ORDER_BY
|
184
|
+
collector << " ORDER BY "
|
139
185
|
len = o.orders.length - 1
|
140
186
|
o.orders.each_with_index { |x, i|
|
141
187
|
collector = visit(x, collector)
|
142
|
-
collector <<
|
188
|
+
collector << ", " unless len == i
|
143
189
|
}
|
144
190
|
end
|
145
191
|
collector
|
@@ -152,10 +198,23 @@ module Arel
|
|
152
198
|
collector
|
153
199
|
end
|
154
200
|
|
201
|
+
def visit_Arel_Nodes_Lateral o, collector
|
202
|
+
collector << "APPLY"
|
203
|
+
collector << " "
|
204
|
+
if o.expr.is_a?(Arel::Nodes::SelectStatement)
|
205
|
+
collector << "("
|
206
|
+
visit(o.expr, collector)
|
207
|
+
collector << ")"
|
208
|
+
else
|
209
|
+
visit(o.expr, collector)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
155
213
|
# SQLServer Helpers
|
156
214
|
|
157
215
|
def node_value(node)
|
158
216
|
return nil unless node
|
217
|
+
|
159
218
|
case node.expr
|
160
219
|
when NilClass then nil
|
161
220
|
when Numeric then node.expr
|
@@ -169,9 +228,11 @@ module Arel
|
|
169
228
|
|
170
229
|
def make_Fetch_Possible_And_Deterministic o
|
171
230
|
return if o.limit.nil? && o.offset.nil?
|
231
|
+
|
172
232
|
t = table_From_Statement o
|
173
233
|
pk = primary_Key_From_Table t
|
174
234
|
return unless pk
|
235
|
+
|
175
236
|
if o.orders.empty?
|
176
237
|
# Prefer deterministic vs a simple `(SELECT NULL)` expr.
|
177
238
|
o.orders = [pk.asc]
|
@@ -196,14 +257,15 @@ module Arel
|
|
196
257
|
elsif Arel::Nodes::SqlLiteral === core.from
|
197
258
|
Arel::Table.new(core.from)
|
198
259
|
elsif Arel::Nodes::JoinSource === core.source
|
199
|
-
Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left
|
260
|
+
Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left.left
|
200
261
|
end
|
201
262
|
end
|
202
263
|
|
203
264
|
def primary_Key_From_Table t
|
204
265
|
return unless t
|
266
|
+
|
205
267
|
column_name = @connection.schema_cache.primary_keys(t.name) ||
|
206
|
-
|
268
|
+
@connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
|
207
269
|
column_name ? t[column_name] : nil
|
208
270
|
end
|
209
271
|
|
@@ -213,6 +275,18 @@ module Arel
|
|
213
275
|
).quoted
|
214
276
|
end
|
215
277
|
|
278
|
+
# Need to remove ordering from subqueries unless TOP/OFFSET also used. Otherwise, SQLServer
|
279
|
+
# returns error "The ORDER BY clause is invalid in views, inline functions, derived tables,
|
280
|
+
# subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified."
|
281
|
+
def remove_invalid_ordering_from_select_statement(node)
|
282
|
+
return unless Arel::Nodes::SelectStatement === node
|
283
|
+
|
284
|
+
node.orders = [] unless node.offset || node.limit
|
285
|
+
end
|
286
|
+
|
287
|
+
def sanitize_as_option_clause(value)
|
288
|
+
value.gsub(%r{OPTION \s* \( (.+) \)}xi, "\\1")
|
289
|
+
end
|
216
290
|
end
|
217
291
|
end
|
218
292
|
end
|