activerecord-sqlserver-adapter 5.2.1 → 7.0.0.0
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/.editorconfig +9 -0
- data/.github/issue_template.md +23 -0
- data/.github/workflows/ci.yml +29 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +29 -0
- data/CHANGELOG.md +17 -27
- data/{Dockerfile → Dockerfile.ci} +1 -1
- data/Gemfile +49 -41
- data/Guardfile +9 -8
- data/MIT-LICENSE +1 -1
- data/README.md +65 -42
- 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 +22 -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 +5 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +10 -14
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +12 -5
- 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 +10 -7
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +30 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -4
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +117 -52
- 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 +51 -14
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +40 -6
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +18 -10
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +235 -167
- 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/showplan.rb +8 -8
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +36 -7
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +43 -45
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +8 -10
- 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 +5 -3
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +7 -5
- 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/type.rb +38 -35
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +26 -12
- data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +271 -180
- data/lib/active_record/connection_adapters/sqlserver_column.rb +76 -16
- data/lib/active_record/sqlserver_base.rb +11 -9
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +38 -39
- data/lib/activerecord-sqlserver-adapter.rb +3 -1
- data/lib/arel/visitors/sqlserver.rb +177 -56
- data/lib/arel_sqlserver.rb +4 -2
- data/test/appveyor/dbsetup.ps1 +4 -4
- data/test/cases/active_schema_test_sqlserver.rb +55 -0
- data/test/cases/adapter_test_sqlserver.rb +258 -173
- data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
- data/test/cases/change_column_null_test_sqlserver.rb +14 -12
- data/test/cases/coerced_tests.rb +1421 -397
- data/test/cases/column_test_sqlserver.rb +321 -315
- data/test/cases/connection_test_sqlserver.rb +17 -20
- data/test/cases/disconnected_test_sqlserver.rb +39 -0
- data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +28 -19
- data/test/cases/fetch_test_sqlserver.rb +33 -21
- 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 +63 -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 +74 -27
- data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
- data/test/cases/order_test_sqlserver.rb +59 -53
- data/test/cases/pessimistic_locking_test_sqlserver.rb +27 -33
- data/test/cases/primary_keys_test_sqlserver.rb +103 -0
- data/test/cases/rake_test_sqlserver.rb +70 -45
- data/test/cases/schema_dumper_test_sqlserver.rb +124 -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_collation.rb +19 -0
- 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/composite_pk.rb +9 -0
- 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/sst_string_collation.rb +3 -0
- 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 +56 -21
- data/test/support/coerceable_test_sqlserver.rb +19 -13
- 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/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
- data/test/support/minitest_sqlserver.rb +3 -1
- data/test/support/paths_sqlserver.rb +11 -11
- data/test/support/rake_helpers.rb +15 -10
- data/test/support/sql_counter_sqlserver.rb +16 -15
- data/test/support/test_in_memory_oltp.rb +9 -7
- metadata +47 -13
- data/.travis.yml +0 -25
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +0 -26
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionHandling
|
|
3
5
|
def sqlserver_connection(config) #:nodoc:
|
|
4
6
|
config = config.symbolize_keys
|
|
5
|
-
config.reverse_merge!
|
|
6
|
-
mode = config[:mode].to_s.downcase.underscore.to_sym
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
)
|
|
14
16
|
end
|
|
15
17
|
end
|
|
16
18
|
end
|
|
@@ -1,28 +1,33 @@
|
|
|
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
|
|
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
|
-
rescue ActiveRecord::StatementInvalid =>
|
|
25
|
-
if /database .* already exists/i ===
|
|
29
|
+
rescue ActiveRecord::StatementInvalid => e
|
|
30
|
+
if /database .* already exists/i === e.message
|
|
26
31
|
raise DatabaseAlreadyExists
|
|
27
32
|
else
|
|
28
33
|
raise
|
|
@@ -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,27 +54,28 @@ 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) }
|
|
63
68
|
command.concat(table_args)
|
|
64
69
|
view_args = connection.views.map { |v| Shellwords.escape(v) }
|
|
65
70
|
command.concat(view_args)
|
|
66
|
-
raise
|
|
71
|
+
raise "Error dumping database" unless Kernel.system(command.join(" "))
|
|
72
|
+
|
|
67
73
|
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+\)/,
|
|
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
|
|
73
79
|
File.open(filename, "w") { |file| file.puts dump }
|
|
74
80
|
end
|
|
75
81
|
|
|
@@ -77,33 +83,27 @@ module ActiveRecord
|
|
|
77
83
|
connection.execute File.read(filename)
|
|
78
84
|
end
|
|
79
85
|
|
|
80
|
-
|
|
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
|
-
|
|
95
97
|
end
|
|
96
98
|
|
|
97
99
|
module DatabaseTasksSQLServer
|
|
98
|
-
|
|
99
100
|
extend ActiveSupport::Concern
|
|
100
101
|
|
|
101
102
|
module ClassMethods
|
|
102
|
-
|
|
103
103
|
LOCAL_IPADDR = [
|
|
104
|
-
IPAddr.new(
|
|
105
|
-
IPAddr.new(
|
|
106
|
-
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")
|
|
107
107
|
]
|
|
108
108
|
|
|
109
109
|
private
|
|
@@ -113,21 +113,20 @@ module ActiveRecord
|
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
def configuration_host_ip(configuration)
|
|
116
|
-
return nil unless configuration
|
|
117
|
-
|
|
116
|
+
return nil unless configuration.host
|
|
117
|
+
|
|
118
|
+
Socket::getaddrinfo(configuration.host, "echo", Socket::AF_INET)[0][3]
|
|
118
119
|
end
|
|
119
120
|
|
|
120
121
|
def local_ipaddr?(host_ip)
|
|
121
122
|
return false unless host_ip
|
|
123
|
+
|
|
122
124
|
LOCAL_IPADDR.any? { |ip| ip.include?(host_ip) }
|
|
123
125
|
end
|
|
124
|
-
|
|
125
126
|
end
|
|
126
|
-
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
DatabaseTasks.register_task %r{sqlserver}, SQLServerDatabaseTasks
|
|
130
130
|
DatabaseTasks.send :include, DatabaseTasksSQLServer
|
|
131
|
-
|
|
132
131
|
end
|
|
133
132
|
end
|
|
@@ -1,47 +1,54 @@
|
|
|
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
|
-
# SQLServer ToSql/Visitor (
|
|
14
|
+
# SQLServer ToSql/Visitor (Overrides)
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
end
|
|
16
|
+
BIND_BLOCK = proc { |i| "@#{i - 1}" }
|
|
17
|
+
private_constant :BIND_BLOCK
|
|
19
18
|
|
|
20
|
-
def
|
|
19
|
+
def bind_block; BIND_BLOCK; end
|
|
20
|
+
|
|
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
|
|
24
25
|
|
|
25
|
-
def
|
|
26
|
+
def visit_Arel_Nodes_Concat(o, collector)
|
|
27
|
+
visit o.left, collector
|
|
28
|
+
collector << " + "
|
|
29
|
+
visit o.right, collector
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def visit_Arel_Nodes_UpdateStatement(o, collector)
|
|
26
33
|
if o.orders.any? && o.limit.nil?
|
|
27
34
|
o.limit = Nodes::Limit.new(9_223_372_036_854_775_807)
|
|
28
35
|
end
|
|
29
36
|
super
|
|
30
37
|
end
|
|
31
38
|
|
|
32
|
-
def visit_Arel_Nodes_Lock
|
|
33
|
-
o.expr = Arel.sql(
|
|
34
|
-
collector <<
|
|
39
|
+
def visit_Arel_Nodes_Lock(o, collector)
|
|
40
|
+
o.expr = Arel.sql("WITH(UPDLOCK)") if o.expr.to_s =~ /FOR UPDATE/
|
|
41
|
+
collector << " "
|
|
35
42
|
visit o.expr, collector
|
|
36
43
|
end
|
|
37
44
|
|
|
38
|
-
def visit_Arel_Nodes_Offset
|
|
45
|
+
def visit_Arel_Nodes_Offset(o, collector)
|
|
39
46
|
collector << OFFSET
|
|
40
47
|
visit o.expr, collector
|
|
41
48
|
collector << ROWS
|
|
42
49
|
end
|
|
43
50
|
|
|
44
|
-
def visit_Arel_Nodes_Limit
|
|
51
|
+
def visit_Arel_Nodes_Limit(o, collector)
|
|
45
52
|
if node_value(o) == 0
|
|
46
53
|
collector << FETCH0
|
|
47
54
|
collector << ROWS_ONLY
|
|
@@ -52,14 +59,52 @@ module Arel
|
|
|
52
59
|
end
|
|
53
60
|
end
|
|
54
61
|
|
|
55
|
-
def
|
|
62
|
+
def visit_Arel_Nodes_Grouping(o, collector)
|
|
63
|
+
remove_invalid_ordering_from_select_statement(o.expr)
|
|
64
|
+
super
|
|
65
|
+
end
|
|
66
|
+
|
|
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
|
+
# Use cast_type on encrypted attributes. Don't encrypt them again
|
|
87
|
+
column_type = column_type.cast_type if column_type.is_a?(ActiveRecord::Encryption::EncryptedAttributeType)
|
|
88
|
+
attrs = values.map { |value| ActiveRecord::Relation::QueryAttribute.new(column_name, value, column_type) }
|
|
89
|
+
|
|
90
|
+
collector.add_binds(attrs, &bind_block)
|
|
91
|
+
# Monkey-patch end.
|
|
92
|
+
else
|
|
93
|
+
collector.add_binds(values, &bind_block)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
collector << ")"
|
|
97
|
+
collector
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def visit_Arel_Nodes_SelectStatement(o, collector)
|
|
56
101
|
@select_statement = o
|
|
57
102
|
distinct_One_As_One_Is_So_Not_Fetch o
|
|
58
103
|
if o.with
|
|
59
104
|
collector = visit o.with, collector
|
|
60
|
-
collector <<
|
|
105
|
+
collector << " "
|
|
61
106
|
end
|
|
62
|
-
collector = o.cores.inject(collector) { |c,x|
|
|
107
|
+
collector = o.cores.inject(collector) { |c, x|
|
|
63
108
|
visit_Arel_Nodes_SelectCore(x, c)
|
|
64
109
|
}
|
|
65
110
|
collector = visit_Orders_And_Let_Fetch_Happen o, collector
|
|
@@ -69,19 +114,31 @@ module Arel
|
|
|
69
114
|
@select_statement = nil
|
|
70
115
|
end
|
|
71
116
|
|
|
72
|
-
def
|
|
117
|
+
def visit_Arel_Nodes_SelectCore(o, collector)
|
|
118
|
+
collector = super
|
|
119
|
+
maybe_visit o.optimizer_hints, collector
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def visit_Arel_Nodes_OptimizerHints(o, collector)
|
|
123
|
+
hints = o.expr.map { |v| sanitize_as_option_clause(v) }.join(", ")
|
|
124
|
+
collector << "OPTION (#{hints})"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def visit_Arel_Table(o, collector)
|
|
73
128
|
# Apparently, o.engine.connection can actually be a different adapter
|
|
74
129
|
# than sqlserver. Can be removed if fixed in ActiveRecord. See:
|
|
75
130
|
# github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/450
|
|
76
|
-
table_name =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
131
|
+
table_name =
|
|
132
|
+
begin
|
|
133
|
+
if o.class.engine.connection.respond_to?(:sqlserver?) && o.class.engine.connection.database_prefix_remote_server?
|
|
134
|
+
remote_server_table_name(o)
|
|
135
|
+
else
|
|
136
|
+
quote_table_name(o.name)
|
|
137
|
+
end
|
|
138
|
+
rescue Exception
|
|
80
139
|
quote_table_name(o.name)
|
|
81
140
|
end
|
|
82
|
-
|
|
83
|
-
quote_table_name(o.name)
|
|
84
|
-
end
|
|
141
|
+
|
|
85
142
|
if o.table_alias
|
|
86
143
|
collector << "#{table_name} #{quote_table_name o.table_alias}"
|
|
87
144
|
else
|
|
@@ -89,73 +146,109 @@ module Arel
|
|
|
89
146
|
end
|
|
90
147
|
end
|
|
91
148
|
|
|
92
|
-
def visit_Arel_Nodes_JoinSource
|
|
149
|
+
def visit_Arel_Nodes_JoinSource(o, collector)
|
|
93
150
|
if o.left
|
|
94
151
|
collector = visit o.left, collector
|
|
95
152
|
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector
|
|
96
153
|
end
|
|
97
154
|
if o.right.any?
|
|
98
|
-
collector <<
|
|
99
|
-
collector = inject_join o.right, collector,
|
|
155
|
+
collector << " " if o.left
|
|
156
|
+
collector = inject_join o.right, collector, " "
|
|
100
157
|
end
|
|
101
158
|
collector
|
|
102
159
|
end
|
|
103
160
|
|
|
104
|
-
def visit_Arel_Nodes_InnerJoin
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if o.right
|
|
109
|
-
collector << SPACE
|
|
110
|
-
visit(o.right, collector)
|
|
161
|
+
def visit_Arel_Nodes_InnerJoin(o, collector)
|
|
162
|
+
if o.left.is_a?(Arel::Nodes::As) && o.left.left.is_a?(Arel::Nodes::Lateral)
|
|
163
|
+
collector << "CROSS "
|
|
164
|
+
visit o.left, collector
|
|
111
165
|
else
|
|
112
|
-
collector
|
|
166
|
+
collector << "INNER JOIN "
|
|
167
|
+
collector = visit o.left, collector
|
|
168
|
+
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
|
|
169
|
+
if o.right
|
|
170
|
+
collector << " "
|
|
171
|
+
visit(o.right, collector)
|
|
172
|
+
else
|
|
173
|
+
collector
|
|
174
|
+
end
|
|
113
175
|
end
|
|
114
176
|
end
|
|
115
177
|
|
|
116
|
-
def visit_Arel_Nodes_OuterJoin
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
178
|
+
def visit_Arel_Nodes_OuterJoin(o, collector)
|
|
179
|
+
if o.left.is_a?(Arel::Nodes::As) && o.left.left.is_a?(Arel::Nodes::Lateral)
|
|
180
|
+
collector << "OUTER "
|
|
181
|
+
visit o.left, collector
|
|
182
|
+
else
|
|
183
|
+
collector << "LEFT OUTER JOIN "
|
|
184
|
+
collector = visit o.left, collector
|
|
185
|
+
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
|
|
186
|
+
collector << " "
|
|
187
|
+
visit o.right, collector
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def visit_Arel_Nodes_In(o, collector)
|
|
192
|
+
if Array === o.right
|
|
193
|
+
o.right.each { |node| remove_invalid_ordering_from_select_statement(node) }
|
|
194
|
+
else
|
|
195
|
+
remove_invalid_ordering_from_select_statement(o.right)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
super
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def collect_optimizer_hints(o, collector)
|
|
202
|
+
collector
|
|
122
203
|
end
|
|
123
204
|
|
|
124
205
|
# SQLServer ToSql/Visitor (Additions)
|
|
125
206
|
|
|
126
|
-
def visit_Arel_Nodes_SelectStatement_SQLServer_Lock
|
|
207
|
+
def visit_Arel_Nodes_SelectStatement_SQLServer_Lock(collector, options = {})
|
|
127
208
|
if select_statement_lock?
|
|
128
209
|
collector = visit @select_statement.lock, collector
|
|
129
|
-
collector <<
|
|
210
|
+
collector << " " if options[:space]
|
|
130
211
|
end
|
|
131
212
|
collector
|
|
132
213
|
end
|
|
133
214
|
|
|
134
|
-
def visit_Orders_And_Let_Fetch_Happen
|
|
215
|
+
def visit_Orders_And_Let_Fetch_Happen(o, collector)
|
|
135
216
|
make_Fetch_Possible_And_Deterministic o
|
|
136
217
|
unless o.orders.empty?
|
|
137
|
-
collector <<
|
|
138
|
-
collector << ORDER_BY
|
|
218
|
+
collector << " ORDER BY "
|
|
139
219
|
len = o.orders.length - 1
|
|
140
220
|
o.orders.each_with_index { |x, i|
|
|
141
221
|
collector = visit(x, collector)
|
|
142
|
-
collector <<
|
|
222
|
+
collector << ", " unless len == i
|
|
143
223
|
}
|
|
144
224
|
end
|
|
145
225
|
collector
|
|
146
226
|
end
|
|
147
227
|
|
|
148
|
-
def visit_Make_Fetch_Happen
|
|
228
|
+
def visit_Make_Fetch_Happen(o, collector)
|
|
149
229
|
o.offset = Nodes::Offset.new(0) if o.limit && !o.offset
|
|
150
230
|
collector = visit o.offset, collector if o.offset
|
|
151
231
|
collector = visit o.limit, collector if o.limit
|
|
152
232
|
collector
|
|
153
233
|
end
|
|
154
234
|
|
|
235
|
+
def visit_Arel_Nodes_Lateral(o, collector)
|
|
236
|
+
collector << "APPLY"
|
|
237
|
+
collector << " "
|
|
238
|
+
if o.expr.is_a?(Arel::Nodes::SelectStatement)
|
|
239
|
+
collector << "("
|
|
240
|
+
visit(o.expr, collector)
|
|
241
|
+
collector << ")"
|
|
242
|
+
else
|
|
243
|
+
visit(o.expr, collector)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
155
247
|
# SQLServer Helpers
|
|
156
248
|
|
|
157
249
|
def node_value(node)
|
|
158
250
|
return nil unless node
|
|
251
|
+
|
|
159
252
|
case node.expr
|
|
160
253
|
when NilClass then nil
|
|
161
254
|
when Numeric then node.expr
|
|
@@ -167,18 +260,20 @@ module Arel
|
|
|
167
260
|
@select_statement && @select_statement.lock
|
|
168
261
|
end
|
|
169
262
|
|
|
170
|
-
def make_Fetch_Possible_And_Deterministic
|
|
263
|
+
def make_Fetch_Possible_And_Deterministic(o)
|
|
171
264
|
return if o.limit.nil? && o.offset.nil?
|
|
265
|
+
|
|
172
266
|
t = table_From_Statement o
|
|
173
267
|
pk = primary_Key_From_Table t
|
|
174
268
|
return unless pk
|
|
269
|
+
|
|
175
270
|
if o.orders.empty?
|
|
176
271
|
# Prefer deterministic vs a simple `(SELECT NULL)` expr.
|
|
177
272
|
o.orders = [pk.asc]
|
|
178
273
|
end
|
|
179
274
|
end
|
|
180
275
|
|
|
181
|
-
def distinct_One_As_One_Is_So_Not_Fetch
|
|
276
|
+
def distinct_One_As_One_Is_So_Not_Fetch(o)
|
|
182
277
|
core = o.cores.first
|
|
183
278
|
distinct = Nodes::Distinct === core.set_quantifier
|
|
184
279
|
oneasone = core.projections.all? { |x| x == ActiveRecord::FinderMethods::ONE_AS_ONE }
|
|
@@ -189,30 +284,56 @@ module Arel
|
|
|
189
284
|
end
|
|
190
285
|
end
|
|
191
286
|
|
|
192
|
-
def table_From_Statement
|
|
287
|
+
def table_From_Statement(o)
|
|
193
288
|
core = o.cores.first
|
|
194
289
|
if Arel::Table === core.from
|
|
195
290
|
core.from
|
|
196
291
|
elsif Arel::Nodes::SqlLiteral === core.from
|
|
197
292
|
Arel::Table.new(core.from)
|
|
198
293
|
elsif Arel::Nodes::JoinSource === core.source
|
|
199
|
-
Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left
|
|
294
|
+
Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left.left
|
|
200
295
|
end
|
|
201
296
|
end
|
|
202
297
|
|
|
203
|
-
def primary_Key_From_Table
|
|
298
|
+
def primary_Key_From_Table(t)
|
|
204
299
|
return unless t
|
|
205
|
-
|
|
206
|
-
|
|
300
|
+
|
|
301
|
+
primary_keys = @connection.schema_cache.primary_keys(t.name)
|
|
302
|
+
column_name = nil
|
|
303
|
+
|
|
304
|
+
case primary_keys
|
|
305
|
+
when NilClass
|
|
306
|
+
column_name = @connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
|
|
307
|
+
when String
|
|
308
|
+
column_name = primary_keys
|
|
309
|
+
when Array
|
|
310
|
+
candidate_columns = @connection.schema_cache.columns_hash(t.name).slice(*primary_keys).values
|
|
311
|
+
candidate_column = candidate_columns.find(&:is_identity?)
|
|
312
|
+
candidate_column ||= candidate_columns.first
|
|
313
|
+
column_name = candidate_column.try(:name)
|
|
314
|
+
end
|
|
315
|
+
|
|
207
316
|
column_name ? t[column_name] : nil
|
|
208
317
|
end
|
|
209
318
|
|
|
210
|
-
def remote_server_table_name
|
|
319
|
+
def remote_server_table_name(o)
|
|
211
320
|
ActiveRecord::ConnectionAdapters::SQLServer::Utils.extract_identifiers(
|
|
212
321
|
"#{o.class.engine.connection.database_prefix}#{o.name}"
|
|
213
322
|
).quoted
|
|
214
323
|
end
|
|
215
324
|
|
|
325
|
+
# Need to remove ordering from subqueries unless TOP/OFFSET also used. Otherwise, SQLServer
|
|
326
|
+
# returns error "The ORDER BY clause is invalid in views, inline functions, derived tables,
|
|
327
|
+
# subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified."
|
|
328
|
+
def remove_invalid_ordering_from_select_statement(node)
|
|
329
|
+
return unless Arel::Nodes::SelectStatement === node
|
|
330
|
+
|
|
331
|
+
node.orders = [] unless node.offset || node.limit
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def sanitize_as_option_clause(value)
|
|
335
|
+
value.gsub(%r{OPTION \s* \( (.+) \)}xi, "\\1")
|
|
336
|
+
end
|
|
216
337
|
end
|
|
217
338
|
end
|
|
218
339
|
end
|
data/lib/arel_sqlserver.rb
CHANGED
data/test/appveyor/dbsetup.ps1
CHANGED
|
@@ -5,15 +5,15 @@ Write-Output "Setting up..."
|
|
|
5
5
|
|
|
6
6
|
Write-Output "Setting variables..."
|
|
7
7
|
$serverName = $env:COMPUTERNAME
|
|
8
|
-
$
|
|
8
|
+
$instanceNames = @('SQL2014')
|
|
9
9
|
$smo = 'Microsoft.SqlServer.Management.Smo.'
|
|
10
10
|
$wmi = new-object ($smo + 'Wmi.ManagedComputer')
|
|
11
11
|
|
|
12
12
|
Write-Output "Configure Instances..."
|
|
13
|
-
foreach ($
|
|
14
|
-
Write-Output "Instance $
|
|
13
|
+
foreach ($instanceName in $instanceNames) {
|
|
14
|
+
Write-Output "Instance $instanceName ..."
|
|
15
15
|
Write-Output "Enable TCP/IP and port 1433..."
|
|
16
|
-
$uri = "ManagedComputer[@Name='$serverName']/ServerInstance[@Name='$
|
|
16
|
+
$uri = "ManagedComputer[@Name='$serverName']/ServerInstance[@Name='$instanceName']/ServerProtocol[@Name='Tcp']"
|
|
17
17
|
$tcp = $wmi.GetSmoObject($uri)
|
|
18
18
|
$tcp.IsEnabled = $true
|
|
19
19
|
foreach ($ipAddress in $Tcp.IPAddresses) {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_sqlserver"
|
|
4
|
+
|
|
5
|
+
class ActiveSchemaTestSQLServer < ActiveRecord::TestCase
|
|
6
|
+
before do
|
|
7
|
+
connection.create_table :schema_test_table, force: true, id: false do |t|
|
|
8
|
+
t.column :foo, :string, limit: 100
|
|
9
|
+
t.column :state, :string
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
after do
|
|
14
|
+
connection.drop_table :schema_test_table rescue nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'default index' do
|
|
18
|
+
assert_sql('CREATE INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo])') do
|
|
19
|
+
connection.add_index :schema_test_table, "foo"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'unique index' do
|
|
24
|
+
assert_sql('CREATE UNIQUE INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo])') do
|
|
25
|
+
connection.add_index :schema_test_table, "foo", unique: true
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'where condition on index' do
|
|
30
|
+
assert_sql("CREATE INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo]) WHERE state = 'active'") do
|
|
31
|
+
connection.add_index :schema_test_table, "foo", where: "state = 'active'"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'if index does not exist' do
|
|
36
|
+
assert_sql("IF NOT EXISTS (SELECT name FROM sysindexes WHERE name = 'index_schema_test_table_on_foo') " \
|
|
37
|
+
"CREATE INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo])") do
|
|
38
|
+
connection.add_index :schema_test_table, "foo", if_not_exists: true
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "index types" do
|
|
43
|
+
it 'clustered index' do
|
|
44
|
+
assert_sql('CREATE CLUSTERED INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo])') do
|
|
45
|
+
connection.add_index :schema_test_table, "foo", type: :clustered
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'nonclustered index' do
|
|
50
|
+
assert_sql('CREATE NONCLUSTERED INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo])') do
|
|
51
|
+
connection.add_index :schema_test_table, "foo", type: :nonclustered
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|