activerecord-sqlserver-adapter 8.0.12 → 8.1.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/.devcontainer/Dockerfile +1 -1
- data/.github/workflows/ci.yml +34 -3
- data/CHANGELOG.md +13 -84
- data/Dockerfile.ci +1 -1
- data/Gemfile +9 -12
- data/Guardfile +2 -2
- data/README.md +33 -13
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +15 -16
- data/compose.ci.yaml +8 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +1 -2
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +118 -83
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +3 -4
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +7 -7
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +24 -12
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +17 -8
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +170 -172
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +5 -5
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -7
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +3 -4
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +4 -6
- data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -12
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +119 -67
- data/lib/active_record/connection_adapters/sqlserver_column.rb +17 -9
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +5 -5
- data/lib/arel/visitors/sqlserver.rb +55 -26
- data/test/cases/active_schema_test_sqlserver.rb +45 -23
- data/test/cases/adapter_test_sqlserver.rb +72 -61
- data/test/cases/change_column_null_test_sqlserver.rb +16 -46
- data/test/cases/coerced_tests.rb +365 -161
- data/test/cases/column_test_sqlserver.rb +328 -316
- data/test/cases/connection_test_sqlserver.rb +15 -11
- data/test/cases/enum_test_sqlserver.rb +8 -9
- data/test/cases/execute_procedure_test_sqlserver.rb +1 -1
- data/test/cases/fetch_test_sqlserver.rb +1 -1
- data/test/cases/helper_sqlserver.rb +8 -3
- data/test/cases/index_test_sqlserver.rb +8 -6
- data/test/cases/insert_all_test_sqlserver.rb +3 -28
- data/test/cases/json_test_sqlserver.rb +8 -8
- data/test/cases/lateral_test_sqlserver.rb +2 -2
- data/test/cases/migration_test_sqlserver.rb +12 -12
- data/test/cases/pessimistic_locking_test_sqlserver.rb +6 -6
- data/test/cases/primary_keys_test_sqlserver.rb +4 -4
- data/test/cases/rake_test_sqlserver.rb +15 -7
- data/test/cases/schema_dumper_test_sqlserver.rb +109 -113
- data/test/cases/schema_test_sqlserver.rb +7 -39
- data/test/cases/transaction_test_sqlserver.rb +6 -8
- data/test/cases/trigger_test_sqlserver.rb +1 -1
- data/test/cases/utils_test_sqlserver.rb +3 -3
- data/test/cases/view_test_sqlserver.rb +12 -8
- data/test/cases/virtual_column_test_sqlserver.rb +113 -0
- data/test/migrations/create_clients_and_change_column_collation.rb +2 -2
- data/test/models/sqlserver/edge_schema.rb +2 -2
- data/test/schema/sqlserver_specific_schema.rb +49 -37
- data/test/support/coerceable_test_sqlserver.rb +10 -10
- data/test/support/connection_reflection.rb +0 -5
- data/test/support/core_ext/backtrace_cleaner.rb +36 -0
- data/test/support/query_assertions.rb +6 -6
- data/test/support/rake_helpers.rb +6 -10
- metadata +13 -108
|
@@ -16,7 +16,9 @@ module Arel
|
|
|
16
16
|
BIND_BLOCK = proc { |i| "@#{i - 1}" }
|
|
17
17
|
private_constant :BIND_BLOCK
|
|
18
18
|
|
|
19
|
-
def bind_block
|
|
19
|
+
def bind_block
|
|
20
|
+
BIND_BLOCK
|
|
21
|
+
end
|
|
20
22
|
|
|
21
23
|
def visit_Arel_Nodes_Bin(o, collector)
|
|
22
24
|
visit o.expr, collector
|
|
@@ -29,10 +31,49 @@ module Arel
|
|
|
29
31
|
visit o.right, collector
|
|
30
32
|
end
|
|
31
33
|
|
|
34
|
+
# Same as SQLite and PostgreSQL.
|
|
32
35
|
def visit_Arel_Nodes_UpdateStatement(o, collector)
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
collector.retryable = false
|
|
37
|
+
o = prepare_update_statement(o)
|
|
38
|
+
|
|
39
|
+
collector << "UPDATE "
|
|
40
|
+
|
|
41
|
+
# UPDATE with JOIN is in the form of:
|
|
42
|
+
#
|
|
43
|
+
# UPDATE t1
|
|
44
|
+
# SET ..
|
|
45
|
+
# FROM t1 JOIN t2 ON t2.join_id = t1.join_id ..
|
|
46
|
+
# WHERE ..
|
|
47
|
+
if has_join_sources?(o)
|
|
48
|
+
collector = visit o.relation.left, collector
|
|
49
|
+
collect_nodes_for o.values, collector, " SET "
|
|
50
|
+
collector << " FROM "
|
|
51
|
+
collector = inject_join o.relation.right, collector, " "
|
|
52
|
+
else
|
|
53
|
+
collector = visit o.relation, collector
|
|
54
|
+
collect_nodes_for o.values, collector, " SET "
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
collect_nodes_for o.wheres, collector, " WHERE ", " AND "
|
|
58
|
+
collect_nodes_for o.orders, collector, " ORDER BY "
|
|
59
|
+
maybe_visit o.limit, collector
|
|
60
|
+
maybe_visit o.comment, collector
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Similar to PostgreSQL and SQLite.
|
|
64
|
+
def prepare_update_statement(o)
|
|
65
|
+
if o.key && has_join_sources?(o) && !has_group_by_and_having?(o) && !has_limit_or_offset_or_orders?(o)
|
|
66
|
+
# Join clauses cannot reference the target table, so alias the
|
|
67
|
+
# updated table, place the entire relation in the FROM clause, and
|
|
68
|
+
# add a self-join (which requires the primary key)
|
|
69
|
+
stmt = o.clone
|
|
70
|
+
|
|
71
|
+
stmt.relation, stmt.wheres = o.relation.clone, o.wheres.clone
|
|
72
|
+
stmt.relation.right = [stmt.relation.left, *stmt.relation.right]
|
|
73
|
+
# Don't need to use alias
|
|
74
|
+
stmt
|
|
35
75
|
else
|
|
76
|
+
# If using subquery, we need to add limit
|
|
36
77
|
o.limit = Nodes::Limit.new(9_223_372_036_854_775_807) if o.orders.any? && o.limit.nil?
|
|
37
78
|
|
|
38
79
|
super
|
|
@@ -61,19 +102,8 @@ module Arel
|
|
|
61
102
|
collect_nodes_for o.wheres, collector, " WHERE ", " AND "
|
|
62
103
|
end
|
|
63
104
|
|
|
64
|
-
def update_statement_using_join(o, collector)
|
|
65
|
-
collector.retryable = false
|
|
66
|
-
|
|
67
|
-
collector << "UPDATE "
|
|
68
|
-
visit o.relation.left, collector
|
|
69
|
-
collect_nodes_for o.values, collector, " SET "
|
|
70
|
-
collector << " FROM "
|
|
71
|
-
visit o.relation, collector
|
|
72
|
-
collect_nodes_for o.wheres, collector, " WHERE ", " AND "
|
|
73
|
-
end
|
|
74
|
-
|
|
75
105
|
def visit_Arel_Nodes_Lock(o, collector)
|
|
76
|
-
o.expr = Arel.sql("WITH(UPDLOCK)") if o.expr.to_s
|
|
106
|
+
o.expr = Arel.sql("WITH(UPDLOCK)") if /FOR UPDATE/.match?(o.expr.to_s)
|
|
77
107
|
collector << " "
|
|
78
108
|
visit o.expr, collector
|
|
79
109
|
end
|
|
@@ -87,12 +117,11 @@ module Arel
|
|
|
87
117
|
def visit_Arel_Nodes_Limit(o, collector)
|
|
88
118
|
if node_value(o) == 0
|
|
89
119
|
collector << FETCH0
|
|
90
|
-
collector << ROWS_ONLY
|
|
91
120
|
else
|
|
92
121
|
collector << FETCH
|
|
93
122
|
visit o.expr, collector
|
|
94
|
-
collector << ROWS_ONLY
|
|
95
123
|
end
|
|
124
|
+
collector << ROWS_ONLY
|
|
96
125
|
end
|
|
97
126
|
|
|
98
127
|
def visit_Arel_Nodes_Grouping(o, collector)
|
|
@@ -105,10 +134,10 @@ module Arel
|
|
|
105
134
|
|
|
106
135
|
visit o.left, collector
|
|
107
136
|
|
|
108
|
-
if o.type == :in
|
|
109
|
-
|
|
137
|
+
collector << if o.type == :in
|
|
138
|
+
" IN ("
|
|
110
139
|
else
|
|
111
|
-
|
|
140
|
+
" NOT IN ("
|
|
112
141
|
end
|
|
113
142
|
|
|
114
143
|
values = o.casted_values
|
|
@@ -170,14 +199,14 @@ module Arel
|
|
|
170
199
|
quote_table_name(o.name)
|
|
171
200
|
end
|
|
172
201
|
end
|
|
173
|
-
rescue
|
|
202
|
+
rescue
|
|
174
203
|
quote_table_name(o.name)
|
|
175
204
|
end
|
|
176
205
|
|
|
177
|
-
if o.table_alias
|
|
178
|
-
|
|
206
|
+
collector << if o.table_alias
|
|
207
|
+
"#{table_name} #{quote_table_name o.table_alias}"
|
|
179
208
|
else
|
|
180
|
-
|
|
209
|
+
table_name
|
|
181
210
|
end
|
|
182
211
|
end
|
|
183
212
|
|
|
@@ -297,7 +326,7 @@ module Arel
|
|
|
297
326
|
end
|
|
298
327
|
|
|
299
328
|
def select_statement_lock?
|
|
300
|
-
@select_statement
|
|
329
|
+
@select_statement&.lock
|
|
301
330
|
end
|
|
302
331
|
|
|
303
332
|
def make_Fetch_Possible_And_Deterministic(o)
|
|
@@ -330,7 +359,7 @@ module Arel
|
|
|
330
359
|
elsif Arel::Nodes::SqlLiteral === core.from
|
|
331
360
|
Arel::Table.new(core.from)
|
|
332
361
|
elsif Arel::Nodes::JoinSource === core.source
|
|
333
|
-
Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left.left
|
|
362
|
+
(Arel::Nodes::SqlLiteral === core.source.left) ? Arel::Table.new(core.source.left, @engine) : core.source.left.left
|
|
334
363
|
end
|
|
335
364
|
end
|
|
336
365
|
|
|
@@ -12,48 +12,50 @@ class ActiveSchemaTestSQLServer < ActiveRecord::TestCase
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
after do
|
|
15
|
-
connection.drop_table :schema_test_table
|
|
15
|
+
connection.drop_table :schema_test_table
|
|
16
|
+
rescue
|
|
17
|
+
nil
|
|
16
18
|
end
|
|
17
19
|
|
|
18
|
-
it
|
|
19
|
-
assert_queries_match(
|
|
20
|
+
it "default index" do
|
|
21
|
+
assert_queries_match("CREATE INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo])") do
|
|
20
22
|
connection.add_index :schema_test_table, "foo"
|
|
21
23
|
end
|
|
22
24
|
end
|
|
23
25
|
|
|
24
|
-
it
|
|
25
|
-
assert_queries_match(
|
|
26
|
+
it "unique index" do
|
|
27
|
+
assert_queries_match("CREATE UNIQUE INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo])") do
|
|
26
28
|
connection.add_index :schema_test_table, "foo", unique: true
|
|
27
29
|
end
|
|
28
30
|
end
|
|
29
31
|
|
|
30
|
-
it
|
|
32
|
+
it "where condition on index" do
|
|
31
33
|
assert_queries_match("CREATE INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo]) WHERE state = 'active'") do
|
|
32
34
|
connection.add_index :schema_test_table, "foo", where: "state = 'active'"
|
|
33
35
|
end
|
|
34
36
|
end
|
|
35
37
|
|
|
36
|
-
it
|
|
38
|
+
it "if index does not exist" do
|
|
37
39
|
assert_queries_match("IF NOT EXISTS (SELECT name FROM sysindexes WHERE name = 'index_schema_test_table_on_foo') " \
|
|
38
40
|
"CREATE INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo])") do
|
|
39
41
|
connection.add_index :schema_test_table, "foo", if_not_exists: true
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
44
|
|
|
43
|
-
it
|
|
44
|
-
assert_queries_match(
|
|
45
|
+
it "clustered index" do
|
|
46
|
+
assert_queries_match("CREATE CLUSTERED INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo])") do
|
|
45
47
|
connection.add_index :schema_test_table, "foo", type: :clustered
|
|
46
48
|
end
|
|
47
49
|
end
|
|
48
50
|
|
|
49
|
-
it
|
|
50
|
-
assert_queries_match(
|
|
51
|
+
it "nonclustered index" do
|
|
52
|
+
assert_queries_match("CREATE NONCLUSTERED INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo])") do
|
|
51
53
|
connection.add_index :schema_test_table, "foo", type: :nonclustered
|
|
52
54
|
end
|
|
53
55
|
end
|
|
54
56
|
end
|
|
55
57
|
|
|
56
|
-
describe
|
|
58
|
+
describe "collation" do
|
|
57
59
|
it "create column with NOT NULL and COLLATE" do
|
|
58
60
|
assert_nothing_raised do
|
|
59
61
|
connection.create_table :not_null_with_collation_table, force: true, id: false do |t|
|
|
@@ -61,12 +63,16 @@ class ActiveSchemaTestSQLServer < ActiveRecord::TestCase
|
|
|
61
63
|
end
|
|
62
64
|
end
|
|
63
65
|
ensure
|
|
64
|
-
|
|
66
|
+
begin
|
|
67
|
+
connection.drop_table :not_null_with_collation_table
|
|
68
|
+
rescue
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
65
71
|
end
|
|
66
72
|
end
|
|
67
73
|
|
|
68
|
-
describe
|
|
69
|
-
it
|
|
74
|
+
describe "datetimeoffset precision" do
|
|
75
|
+
it "valid precisions are correct" do
|
|
70
76
|
assert_nothing_raised do
|
|
71
77
|
connection.create_table :datetimeoffset_precisions do |t|
|
|
72
78
|
t.datetimeoffset :precision_default
|
|
@@ -81,22 +87,30 @@ class ActiveSchemaTestSQLServer < ActiveRecord::TestCase
|
|
|
81
87
|
assert_equal columns.find { |column| column.name == "precision_5" }.precision, 5
|
|
82
88
|
assert_equal columns.find { |column| column.name == "precision_7" }.precision, 7
|
|
83
89
|
ensure
|
|
84
|
-
|
|
90
|
+
begin
|
|
91
|
+
connection.drop_table :datetimeoffset_precisions
|
|
92
|
+
rescue
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
85
95
|
end
|
|
86
96
|
|
|
87
|
-
it
|
|
97
|
+
it "invalid precision raises exception" do
|
|
88
98
|
assert_raise(ActiveRecord::ActiveRecordError) do
|
|
89
99
|
connection.create_table :datetimeoffset_precisions do |t|
|
|
90
100
|
t.datetimeoffset :precision_8, precision: 8
|
|
91
101
|
end
|
|
92
102
|
end
|
|
93
103
|
ensure
|
|
94
|
-
|
|
104
|
+
begin
|
|
105
|
+
connection.drop_table :datetimeoffset_precisions
|
|
106
|
+
rescue
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
95
109
|
end
|
|
96
110
|
end
|
|
97
111
|
|
|
98
|
-
describe
|
|
99
|
-
it
|
|
112
|
+
describe "time precision" do
|
|
113
|
+
it "valid precisions are correct" do
|
|
100
114
|
assert_nothing_raised do
|
|
101
115
|
connection.create_table :time_precisions do |t|
|
|
102
116
|
t.time :precision_default
|
|
@@ -111,17 +125,25 @@ class ActiveSchemaTestSQLServer < ActiveRecord::TestCase
|
|
|
111
125
|
assert_equal columns.find { |column| column.name == "precision_5" }.precision, 5
|
|
112
126
|
assert_equal columns.find { |column| column.name == "precision_7" }.precision, 7
|
|
113
127
|
ensure
|
|
114
|
-
|
|
128
|
+
begin
|
|
129
|
+
connection.drop_table :time_precisions
|
|
130
|
+
rescue
|
|
131
|
+
nil
|
|
132
|
+
end
|
|
115
133
|
end
|
|
116
134
|
|
|
117
|
-
it
|
|
135
|
+
it "invalid precision raises exception" do
|
|
118
136
|
assert_raise(ActiveRecord::ActiveRecordError) do
|
|
119
137
|
connection.create_table :time_precisions do |t|
|
|
120
138
|
t.time :precision_8, precision: 8
|
|
121
139
|
end
|
|
122
140
|
end
|
|
123
141
|
ensure
|
|
124
|
-
|
|
142
|
+
begin
|
|
143
|
+
connection.drop_table :time_precisions
|
|
144
|
+
rescue
|
|
145
|
+
nil
|
|
146
|
+
end
|
|
125
147
|
end
|
|
126
148
|
end
|
|
127
149
|
end
|
|
@@ -14,10 +14,10 @@ require "models/discount"
|
|
|
14
14
|
class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
15
15
|
fixtures :tasks
|
|
16
16
|
|
|
17
|
-
let(:arunit_connection)
|
|
17
|
+
let(:arunit_connection) { Topic.lease_connection }
|
|
18
18
|
let(:arunit2_connection) { College.lease_connection }
|
|
19
|
-
let(:arunit_database)
|
|
20
|
-
let(:arunit2_database)
|
|
19
|
+
let(:arunit_database) { arunit_connection.pool.db_config.database }
|
|
20
|
+
let(:arunit2_database) { arunit2_connection.pool.db_config.database }
|
|
21
21
|
|
|
22
22
|
let(:basic_insert_sql) { "INSERT INTO [funny_jokes] ([name]) VALUES('Knock knock')" }
|
|
23
23
|
let(:basic_merge_sql) { "MERGE INTO [ships] WITH (UPDLOCK, HOLDLOCK) AS target USING ( SELECT * FROM ( SELECT [id], [name], ROW_NUMBER() OVER ( PARTITION BY [id] ORDER BY [id] DESC ) AS rn_0 FROM ( VALUES (101, N'RSS Sir David Attenborough') ) AS t1 ([id], [name]) ) AS ranked_source WHERE rn_0 = 1 ) AS source ON (target.[id] = source.[id]) WHEN MATCHED THEN UPDATE SET target.[name] = source.[name]" }
|
|
@@ -50,20 +50,18 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
it "table exists works if table name prefixed by schema and owner" do
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
Topic.table_name = "topics"
|
|
66
|
-
end
|
|
53
|
+
assert_equal "topics", Topic.table_name
|
|
54
|
+
assert Topic.table_exists?
|
|
55
|
+
|
|
56
|
+
# Test when owner included in table name.
|
|
57
|
+
Topic.table_name = "dbo.topics"
|
|
58
|
+
assert Topic.table_exists?, "Topics table name of 'dbo.topics' should return true for exists."
|
|
59
|
+
|
|
60
|
+
# Test when database and owner included in table name.
|
|
61
|
+
Topic.table_name = "#{arunit_database}.dbo.topics"
|
|
62
|
+
assert Topic.table_exists?, "Topics table name of '[DATABASE].dbo.topics' should return true for exists."
|
|
63
|
+
ensure
|
|
64
|
+
Topic.table_name = "topics"
|
|
67
65
|
end
|
|
68
66
|
|
|
69
67
|
it "test table existence across database schemas" do
|
|
@@ -77,18 +75,18 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
77
75
|
assert_not_equal arunit_database, arunit2_database
|
|
78
76
|
|
|
79
77
|
# Assert that the Topics table exists when using the Topics connection.
|
|
80
|
-
assert arunit_connection.table_exists?(
|
|
81
|
-
assert arunit_connection.table_exists?(
|
|
82
|
-
assert arunit_connection.table_exists?("#{arunit_database}.dbo.topics"),
|
|
78
|
+
assert arunit_connection.table_exists?("topics"), "Topics table exists using table name"
|
|
79
|
+
assert arunit_connection.table_exists?("dbo.topics"), "Topics table exists using owner and table name"
|
|
80
|
+
assert arunit_connection.table_exists?("#{arunit_database}.dbo.topics"), "Topics table exists using database, owner and table name"
|
|
83
81
|
|
|
84
82
|
# Assert that the Colleges table exists when using the Colleges connection.
|
|
85
|
-
assert arunit2_connection.table_exists?(
|
|
86
|
-
assert arunit2_connection.table_exists?(
|
|
87
|
-
assert arunit2_connection.table_exists?("#{arunit2_database}.dbo.colleges"),
|
|
83
|
+
assert arunit2_connection.table_exists?("colleges"), "College table exists using table name"
|
|
84
|
+
assert arunit2_connection.table_exists?("dbo.colleges"), "College table exists using owner and table name"
|
|
85
|
+
assert arunit2_connection.table_exists?("#{arunit2_database}.dbo.colleges"), "College table exists using database, owner and table name"
|
|
88
86
|
|
|
89
87
|
# Assert that the tables exist when using each others connection.
|
|
90
|
-
assert arunit_connection.table_exists?("#{arunit2_database}.dbo.colleges"),
|
|
91
|
-
assert arunit2_connection.table_exists?("#{arunit_database}.dbo.topics"),
|
|
88
|
+
assert arunit_connection.table_exists?("#{arunit2_database}.dbo.colleges"), "Colleges table exists using Topics connection"
|
|
89
|
+
assert arunit2_connection.table_exists?("#{arunit_database}.dbo.topics"), "Topics table exists using Colleges connection"
|
|
92
90
|
end
|
|
93
91
|
|
|
94
92
|
it "return true to insert sql query for inserts only" do
|
|
@@ -118,20 +116,20 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
118
116
|
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
|
119
117
|
configuration = db_config.configuration_hash.merge(database: "nonexistent_activerecord_unittest")
|
|
120
118
|
assert_not ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(configuration),
|
|
121
|
-
|
|
119
|
+
"expected database #{configuration[:database]} to not exist"
|
|
122
120
|
end
|
|
123
121
|
|
|
124
122
|
it "test database exists returns true when the database exists" do
|
|
125
123
|
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
|
126
124
|
assert ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(db_config.configuration_hash),
|
|
127
|
-
|
|
125
|
+
"expected database #{db_config.database} to exist"
|
|
128
126
|
end
|
|
129
127
|
|
|
130
128
|
it "test primary key violation" do
|
|
131
|
-
Post.create!(id: 0, title:
|
|
129
|
+
Post.create!(id: 0, title: "Setup", body: "Create post with primary key of zero")
|
|
132
130
|
|
|
133
131
|
assert_raise ActiveRecord::RecordNotUnique do
|
|
134
|
-
Post.create!(id: 0, title:
|
|
132
|
+
Post.create!(id: 0, title: "Test", body: "Try to create another post with primary key of zero")
|
|
135
133
|
end
|
|
136
134
|
end
|
|
137
135
|
|
|
@@ -141,12 +139,20 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
141
139
|
end
|
|
142
140
|
|
|
143
141
|
after do
|
|
144
|
-
|
|
142
|
+
begin
|
|
143
|
+
connection.execute("SET LANGUAGE #{@default_language}")
|
|
144
|
+
rescue
|
|
145
|
+
nil
|
|
146
|
+
end
|
|
145
147
|
connection.send :initialize_dateformatter
|
|
146
148
|
end
|
|
147
149
|
|
|
148
150
|
it "memos users dateformat" do
|
|
149
|
-
|
|
151
|
+
begin
|
|
152
|
+
connection.execute("SET LANGUAGE us_english")
|
|
153
|
+
rescue
|
|
154
|
+
nil
|
|
155
|
+
end
|
|
150
156
|
dateformat = connection.instance_variable_get(:@database_dateformat)
|
|
151
157
|
assert_equal "mdy", dateformat
|
|
152
158
|
end
|
|
@@ -206,7 +212,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
206
212
|
connection.lowercase_schema_reflection = true
|
|
207
213
|
|
|
208
214
|
assert_nothing_raised do
|
|
209
|
-
post = Post.create!(title:
|
|
215
|
+
post = Post.create!(title: "Setup", body: "Record to be deleted")
|
|
210
216
|
post.destroy!
|
|
211
217
|
end
|
|
212
218
|
end
|
|
@@ -224,7 +230,6 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
224
230
|
@identity_merge_sql = "MERGE INTO [ships] WITH (UPDLOCK, HOLDLOCK) AS target USING ( SELECT * FROM ( SELECT [id], [name], ROW_NUMBER() OVER ( PARTITION BY [id] ORDER BY [id] DESC ) AS rn_0 FROM ( VALUES (101, N'RSS Sir David Attenborough') ) AS t1 ([id], [name]) ) AS ranked_source WHERE rn_0 = 1 ) AS source ON (target.[id] = source.[id]) WHEN MATCHED THEN UPDATE SET target.[name] = source.[name] WHEN NOT MATCHED BY TARGET THEN INSERT ([id], [name]) VALUES (source.[id], source.[name]) OUTPUT INSERTED.[id]"
|
|
225
231
|
@identity_merge_sql_unquoted = "MERGE INTO ships WITH (UPDLOCK, HOLDLOCK) AS target USING ( SELECT * FROM ( SELECT id, name, ROW_NUMBER() OVER ( PARTITION BY id ORDER BY id DESC ) AS rn_0 FROM ( VALUES (101, N'RSS Sir David Attenborough') ) AS t1 (id, name) ) AS ranked_source WHERE rn_0 = 1 ) AS source ON (target.id = source.id) WHEN MATCHED THEN UPDATE SET target.name = source.name WHEN NOT MATCHED BY TARGET THEN INSERT (id, name) VALUES (source.id, source.name) OUTPUT INSERTED.id"
|
|
226
232
|
@identity_merge_sql_unordered = "MERGE INTO [ships] WITH (UPDLOCK, HOLDLOCK) AS target USING ( SELECT * FROM ( SELECT [name], [id], ROW_NUMBER() OVER ( PARTITION BY [id] ORDER BY [id] DESC ) AS rn_0 FROM ( VALUES (101, N'RSS Sir David Attenborough') ) AS t1 ([name], [id]) ) AS ranked_source WHERE rn_0 = 1 ) AS source ON (target.[id] = source.[id]) WHEN MATCHED THEN UPDATE SET target.[name] = source.[name] WHEN NOT MATCHED BY TARGET THEN INSERT ([name], [id]) VALUES (source.[name], source.[id]) OUTPUT INSERTED.[id]"
|
|
227
|
-
@identity_merge_sql_aliased_target = @identity_merge_sql.sub(" WITH (UPDLOCK, HOLDLOCK)", "")
|
|
228
233
|
|
|
229
234
|
@identity_insert_sql_non_dbo = "INSERT INTO [test].[aliens] ([id],[name]) VALUES(420,'Mork')"
|
|
230
235
|
@identity_insert_sql_non_dbo_unquoted = "INSERT INTO test.aliens ([id],[name]) VALUES(420,'Mork')"
|
|
@@ -238,17 +243,16 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
238
243
|
end
|
|
239
244
|
|
|
240
245
|
it "return quoted table_name to #query_requires_identity_insert? when INSERT sql contains id column" do
|
|
241
|
-
assert_equal "[funny_jokes]",
|
|
242
|
-
assert_equal "[funny_jokes]",
|
|
243
|
-
assert_equal "[funny_jokes]",
|
|
244
|
-
assert_equal "[funny_jokes]",
|
|
245
|
-
assert_equal "[funny_jokes]",
|
|
246
|
-
assert_equal "[funny_jokes]",
|
|
246
|
+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql)
|
|
247
|
+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unquoted)
|
|
248
|
+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unordered)
|
|
249
|
+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_sp)
|
|
250
|
+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unquoted_sp)
|
|
251
|
+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unordered_sp)
|
|
247
252
|
|
|
248
253
|
assert_equal "[ships]", connection.send(:query_requires_identity_insert?, @identity_merge_sql)
|
|
249
254
|
assert_equal "[ships]", connection.send(:query_requires_identity_insert?, @identity_merge_sql_unquoted)
|
|
250
255
|
assert_equal "[ships]", connection.send(:query_requires_identity_insert?, @identity_merge_sql_unordered)
|
|
251
|
-
assert_equal "[ships]", connection.send(:query_requires_identity_insert?, @identity_merge_sql_aliased_target)
|
|
252
256
|
|
|
253
257
|
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo)
|
|
254
258
|
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unquoted)
|
|
@@ -328,7 +332,10 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
328
332
|
|
|
329
333
|
describe "disabling referential integrity" do
|
|
330
334
|
before do
|
|
331
|
-
connection.disable_referential_integrity {
|
|
335
|
+
connection.disable_referential_integrity {
|
|
336
|
+
SSTestHasPk.delete_all
|
|
337
|
+
SSTestHasFk.delete_all
|
|
338
|
+
}
|
|
332
339
|
@parent = SSTestHasPk.create!
|
|
333
340
|
@member = SSTestHasFk.create!(fk_id: @parent.id)
|
|
334
341
|
end
|
|
@@ -469,8 +476,8 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
469
476
|
assert_equal columns.size, SSTestCustomersView.columns.size
|
|
470
477
|
columns.each do |colname|
|
|
471
478
|
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
|
|
472
|
-
|
|
473
|
-
|
|
479
|
+
SSTestCustomersView.columns_hash[colname],
|
|
480
|
+
"Column name #{colname.inspect} was not found in these columns #{SSTestCustomersView.columns.map(&:name).inspect}"
|
|
474
481
|
end
|
|
475
482
|
end
|
|
476
483
|
|
|
@@ -496,8 +503,8 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
496
503
|
assert_equal columns.size, SSTestStringDefaultsView.columns.size
|
|
497
504
|
columns.each do |colname|
|
|
498
505
|
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
|
|
499
|
-
|
|
500
|
-
|
|
506
|
+
SSTestStringDefaultsView.columns_hash[colname],
|
|
507
|
+
"Column name #{colname.inspect} was not found in these columns #{SSTestStringDefaultsView.columns.map(&:name).inspect}"
|
|
501
508
|
end
|
|
502
509
|
end
|
|
503
510
|
|
|
@@ -509,7 +516,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
509
516
|
|
|
510
517
|
it "find default values" do
|
|
511
518
|
assert_equal "null", SSTestStringDefaultsView.new.pretend_null,
|
|
512
|
-
|
|
519
|
+
SSTestStringDefaultsView.columns_hash["pretend_null"].inspect
|
|
513
520
|
end
|
|
514
521
|
|
|
515
522
|
it "respond true to data_source_exists?" do
|
|
@@ -519,12 +526,12 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
519
526
|
# That have more than 4000 chars for their definition
|
|
520
527
|
|
|
521
528
|
it "cope with null returned for the definition" do
|
|
522
|
-
assert_nothing_raised
|
|
529
|
+
assert_nothing_raised { SSTestStringDefaultsBigView.columns }
|
|
523
530
|
end
|
|
524
531
|
|
|
525
532
|
it "using alternate view definition still be able to find real default" do
|
|
526
533
|
assert_equal "null", SSTestStringDefaultsBigView.new.pretend_null,
|
|
527
|
-
|
|
534
|
+
SSTestStringDefaultsBigView.columns_hash["pretend_null"].inspect
|
|
528
535
|
end
|
|
529
536
|
end
|
|
530
537
|
|
|
@@ -599,30 +606,30 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
599
606
|
end
|
|
600
607
|
end
|
|
601
608
|
|
|
602
|
-
describe
|
|
609
|
+
describe "table is in non-dbo schema" do
|
|
603
610
|
it "records can be created successfully" do
|
|
604
611
|
assert_difference("Alien.count", 1) do
|
|
605
|
-
Alien.create!(name:
|
|
612
|
+
Alien.create!(name: "Trisolarans")
|
|
606
613
|
end
|
|
607
614
|
end
|
|
608
615
|
|
|
609
|
-
it
|
|
616
|
+
it "records can be inserted using SQL" do
|
|
610
617
|
assert_difference("Alien.count", 2) do
|
|
611
618
|
Alien.lease_connection.exec_insert("insert into [test].[aliens] (id, name) VALUES(1, 'Trisolarans'), (2, 'Xenomorph')")
|
|
612
619
|
end
|
|
613
620
|
end
|
|
614
621
|
end
|
|
615
622
|
|
|
616
|
-
describe
|
|
617
|
-
it
|
|
623
|
+
describe "table names contains spaces" do
|
|
624
|
+
it "records can be created successfully" do
|
|
618
625
|
assert_difference("TableWithSpaces.count", 1) do
|
|
619
|
-
TableWithSpaces.create!(name:
|
|
626
|
+
TableWithSpaces.create!(name: "Bob")
|
|
620
627
|
end
|
|
621
628
|
end
|
|
622
629
|
end
|
|
623
630
|
|
|
624
631
|
describe "exec_insert" do
|
|
625
|
-
it
|
|
632
|
+
it "values clause should be case-insensitive" do
|
|
626
633
|
assert_difference("Post.count", 4) do
|
|
627
634
|
first_insert = connection.exec_insert("INSERT INTO [posts] ([id],[title],[body]) VALUES(100, 'Title', 'Body'), (102, 'Title', 'Body')")
|
|
628
635
|
second_insert = connection.exec_insert("INSERT INTO [posts] ([id],[title],[body]) values(113, 'Body', 'Body'), (114, 'Body', 'Body')")
|
|
@@ -638,33 +645,37 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
|
638
645
|
@conn = ActiveRecord::Base.lease_connection
|
|
639
646
|
end
|
|
640
647
|
|
|
641
|
-
it
|
|
648
|
+
it "raises an error when the foreign key is mismatched" do
|
|
642
649
|
error = assert_raises(ActiveRecord::MismatchedForeignKey) do
|
|
643
650
|
@conn.add_reference :engines, :old_car
|
|
644
651
|
@conn.add_foreign_key :engines, :old_cars
|
|
645
652
|
end
|
|
646
653
|
|
|
647
654
|
assert_match(
|
|
648
|
-
%r
|
|
655
|
+
%r{Column 'old_cars\.id' is not the same data type as referencing column 'engines\.old_car_id' in foreign key '.*'},
|
|
649
656
|
error.message
|
|
650
657
|
)
|
|
651
658
|
assert_not_nil error.cause
|
|
652
659
|
assert_equal @conn.pool, error.connection_pool
|
|
653
660
|
ensure
|
|
654
|
-
|
|
661
|
+
begin
|
|
662
|
+
@conn.execute("ALTER TABLE engines DROP COLUMN old_car_id")
|
|
663
|
+
rescue
|
|
664
|
+
nil
|
|
665
|
+
end
|
|
655
666
|
end
|
|
656
667
|
end
|
|
657
668
|
|
|
658
669
|
describe "placeholder conditions" do
|
|
659
|
-
it
|
|
670
|
+
it "using time placeholder" do
|
|
660
671
|
assert_equal Task.where("starting < ?", Time.now).count, 1
|
|
661
672
|
end
|
|
662
673
|
|
|
663
|
-
it
|
|
674
|
+
it "using date placeholder" do
|
|
664
675
|
assert_equal Task.where("starting < ?", Date.today).count, 1
|
|
665
676
|
end
|
|
666
677
|
|
|
667
|
-
it
|
|
678
|
+
it "using date-time placeholder" do
|
|
668
679
|
assert_equal Task.where("starting < ?", DateTime.current).count, 1
|
|
669
680
|
end
|
|
670
681
|
end
|