activerecord-sqlserver-adapter 6.1.2.1 → 7.2.4
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 +30 -0
- data/.devcontainer/boot.sh +22 -0
- data/.devcontainer/devcontainer.json +38 -0
- data/.devcontainer/docker-compose.yml +42 -0
- data/.github/workflows/ci.yml +7 -4
- data/.gitignore +3 -1
- data/CHANGELOG.md +19 -42
- data/Dockerfile.ci +3 -3
- data/Gemfile +6 -1
- data/MIT-LICENSE +1 -1
- data/README.md +113 -27
- data/RUNNING_UNIT_TESTS.md +27 -14
- data/Rakefile +2 -6
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +3 -3
- data/appveyor.yml +4 -6
- data/docker-compose.ci.yml +2 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/abstract_adapter.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +6 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -23
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +10 -7
- 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 +12 -2
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +24 -16
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -31
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +143 -155
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +5 -5
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +57 -56
- data/lib/active_record/connection_adapters/sqlserver/savepoints.rb +26 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +14 -12
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +11 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +213 -57
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +13 -2
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +4 -6
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +19 -1
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +21 -10
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +187 -187
- data/lib/active_record/connection_adapters/sqlserver_column.rb +1 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +42 -33
- data/lib/arel/visitors/sqlserver.rb +77 -34
- data/test/cases/active_schema_test_sqlserver.rb +127 -0
- data/test/cases/adapter_test_sqlserver.rb +114 -26
- data/test/cases/coerced_tests.rb +1121 -340
- data/test/cases/column_test_sqlserver.rb +67 -64
- data/test/cases/connection_test_sqlserver.rb +3 -6
- data/test/cases/dbconsole.rb +19 -0
- data/test/cases/disconnected_test_sqlserver.rb +8 -5
- data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
- data/test/cases/enum_test_sqlserver.rb +49 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +9 -5
- data/test/cases/fetch_test_sqlserver.rb +19 -0
- data/test/cases/helper_sqlserver.rb +11 -5
- data/test/cases/index_test_sqlserver.rb +8 -6
- data/test/cases/json_test_sqlserver.rb +1 -1
- data/test/cases/lateral_test_sqlserver.rb +2 -2
- data/test/cases/migration_test_sqlserver.rb +19 -1
- data/test/cases/optimizer_hints_test_sqlserver.rb +21 -12
- data/test/cases/pessimistic_locking_test_sqlserver.rb +8 -7
- data/test/cases/primary_keys_test_sqlserver.rb +2 -2
- data/test/cases/rake_test_sqlserver.rb +10 -5
- data/test/cases/schema_dumper_test_sqlserver.rb +155 -109
- data/test/cases/schema_test_sqlserver.rb +64 -1
- data/test/cases/showplan_test_sqlserver.rb +7 -7
- data/test/cases/specific_schema_test_sqlserver.rb +17 -13
- data/test/cases/transaction_test_sqlserver.rb +13 -8
- data/test/cases/trigger_test_sqlserver.rb +20 -0
- data/test/cases/utils_test_sqlserver.rb +2 -2
- data/test/cases/uuid_test_sqlserver.rb +8 -0
- data/test/cases/view_test_sqlserver.rb +58 -0
- data/test/config.yml +1 -2
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +1 -1
- data/test/models/sqlserver/alien.rb +5 -0
- data/test/models/sqlserver/table_with_spaces.rb +5 -0
- data/test/models/sqlserver/trigger.rb +8 -0
- data/test/schema/sqlserver_specific_schema.rb +54 -6
- data/test/support/coerceable_test_sqlserver.rb +4 -4
- data/test/support/connection_reflection.rb +3 -9
- data/test/support/core_ext/query_cache.rb +7 -1
- 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/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
- data/test/support/query_assertions.rb +49 -0
- data/test/support/rake_helpers.rb +3 -1
- data/test/support/table_definition_sqlserver.rb +24 -0
- data/test/support/test_in_memory_oltp.rb +2 -2
- metadata +41 -17
- data/lib/active_record/sqlserver_base.rb +0 -18
- data/test/cases/scratchpad_test_sqlserver.rb +0 -8
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
- data/test/support/sql_counter_sqlserver.rb +0 -29
@@ -21,6 +21,7 @@ class SchemaTestSQLServer < ActiveRecord::TestCase
|
|
21
21
|
|
22
22
|
it "have only one identity column" do
|
23
23
|
columns = connection.columns("test.sst_schema_identity")
|
24
|
+
|
24
25
|
assert_equal 2, columns.size
|
25
26
|
assert_equal 1, columns.select { |c| c.is_identity? }.size
|
26
27
|
end
|
@@ -29,6 +30,7 @@ class SchemaTestSQLServer < ActiveRecord::TestCase
|
|
29
30
|
test_columns = connection.columns("test.sst_schema_columns")
|
30
31
|
dbo_columns = connection.columns("dbo.sst_schema_columns")
|
31
32
|
columns = connection.columns("sst_schema_columns") # This returns table from dbo schema
|
33
|
+
|
32
34
|
assert_equal 7, test_columns.size
|
33
35
|
assert_equal 2, dbo_columns.size
|
34
36
|
assert_equal 2, columns.size
|
@@ -37,12 +39,73 @@ class SchemaTestSQLServer < ActiveRecord::TestCase
|
|
37
39
|
assert_equal 1, columns.select { |c| c.is_identity? }.size
|
38
40
|
end
|
39
41
|
|
40
|
-
it "return correct varchar and nvarchar column limit length when table is in non
|
42
|
+
it "return correct varchar and nvarchar column limit length when table is in non-dbo schema" do
|
41
43
|
columns = connection.columns("test.sst_schema_columns")
|
44
|
+
|
42
45
|
assert_equal 255, columns.find { |c| c.name == "name" }.limit
|
43
46
|
assert_equal 1000, columns.find { |c| c.name == "description" }.limit
|
44
47
|
assert_equal 255, columns.find { |c| c.name == "n_name" }.limit
|
45
48
|
assert_equal 1000, columns.find { |c| c.name == "n_description" }.limit
|
46
49
|
end
|
47
50
|
end
|
51
|
+
|
52
|
+
describe "parsing table name from raw SQL" do
|
53
|
+
describe 'SELECT statements' do
|
54
|
+
it do
|
55
|
+
assert_equal "[sst_schema_columns]", connection.send(:get_raw_table_name, "SELECT [sst_schema_columns].[id] FROM [sst_schema_columns]")
|
56
|
+
end
|
57
|
+
|
58
|
+
it do
|
59
|
+
assert_equal "sst_schema_columns", connection.send(:get_raw_table_name, "SELECT [sst_schema_columns].[id] FROM sst_schema_columns")
|
60
|
+
end
|
61
|
+
|
62
|
+
it do
|
63
|
+
assert_equal "[WITH - SPACES]", connection.send(:get_raw_table_name, "SELECT id FROM [WITH - SPACES]")
|
64
|
+
end
|
65
|
+
|
66
|
+
it do
|
67
|
+
assert_equal "[WITH - SPACES$DOLLAR]", connection.send(:get_raw_table_name, "SELECT id FROM [WITH - SPACES$DOLLAR]")
|
68
|
+
end
|
69
|
+
|
70
|
+
it do
|
71
|
+
assert_nil connection.send(:get_raw_table_name, nil)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'INSERT statements' do
|
76
|
+
it do
|
77
|
+
assert_equal "[dashboards]", connection.send(:get_raw_table_name, "INSERT INTO [dashboards] DEFAULT VALUES; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident")
|
78
|
+
end
|
79
|
+
|
80
|
+
it do
|
81
|
+
assert_equal "lock_without_defaults", connection.send(:get_raw_table_name, "INSERT INTO lock_without_defaults(title) VALUES('title1')")
|
82
|
+
end
|
83
|
+
|
84
|
+
it do
|
85
|
+
assert_equal "json_data_type", connection.send(:get_raw_table_name, "insert into json_data_type (payload) VALUES ('null')")
|
86
|
+
end
|
87
|
+
|
88
|
+
it do
|
89
|
+
assert_equal "[auto_increments]", connection.send(:get_raw_table_name, "INSERT INTO [auto_increments] OUTPUT INSERTED.[id] DEFAULT VALUES")
|
90
|
+
end
|
91
|
+
|
92
|
+
it do
|
93
|
+
assert_equal "[WITH - SPACES]", connection.send(:get_raw_table_name, "EXEC sp_executesql N'INSERT INTO [WITH - SPACES] ([external_id]) OUTPUT INSERTED.[id] VALUES (@0)', N'@0 bigint', @0 = 10")
|
94
|
+
end
|
95
|
+
|
96
|
+
it do
|
97
|
+
assert_equal "[test].[aliens]", connection.send(:get_raw_table_name, "EXEC sp_executesql N'INSERT INTO [test].[aliens] ([name]) OUTPUT INSERTED.[id] VALUES (@0)', N'@0 varchar(255)', @0 = 'Trisolarans'")
|
98
|
+
end
|
99
|
+
|
100
|
+
it do
|
101
|
+
assert_equal "[with].[select notation]", connection.send(:get_raw_table_name, "INSERT INTO [with].[select notation] SELECT * FROM [table_name]")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe 'CREATE VIEW statements' do
|
106
|
+
it do
|
107
|
+
assert_equal "test_table_as", connection.send(:get_raw_table_name, "CREATE VIEW test_views ( test_table_a_id, test_table_b_id ) AS SELECT test_table_as.id as test_table_a_id, test_table_bs.id as test_table_b_id FROM (test_table_as with(nolock) LEFT JOIN test_table_bs with(nolock) ON (test_table_as.id = test_table_bs.test_table_a_id))")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
48
111
|
end
|
@@ -8,32 +8,32 @@ class ShowplanTestSQLServer < ActiveRecord::TestCase
|
|
8
8
|
|
9
9
|
describe "Unprepare previously prepared SQL" do
|
10
10
|
it "from simple statement" do
|
11
|
-
plan = Car.where(id: 1).explain
|
11
|
+
plan = Car.where(id: 1).explain.inspect
|
12
12
|
_(plan).must_include "SELECT [cars].* FROM [cars] WHERE [cars].[id] = 1"
|
13
13
|
_(plan).must_include "Clustered Index Seek", "make sure we do not showplan the sp_executesql"
|
14
14
|
end
|
15
15
|
|
16
16
|
it "from multiline statement" do
|
17
|
-
plan = Car.where("\n id = 1 \n").explain
|
17
|
+
plan = Car.where("\n id = 1 \n").explain.inspect
|
18
18
|
_(plan).must_include "SELECT [cars].* FROM [cars] WHERE (\n id = 1 \n)"
|
19
19
|
_(plan).must_include "Clustered Index Seek", "make sure we do not showplan the sp_executesql"
|
20
20
|
end
|
21
21
|
|
22
22
|
it "from prepared statement" do
|
23
|
-
plan = Car.where(name: ",").limit(1).explain
|
23
|
+
plan = Car.where(name: ",").limit(1).explain.inspect
|
24
24
|
_(plan).must_include "SELECT [cars].* FROM [cars] WHERE [cars].[name]"
|
25
25
|
_(plan).must_include "TOP EXPRESSION", "make sure we do not showplan the sp_executesql"
|
26
26
|
_(plan).must_include "Clustered Index Scan", "make sure we do not showplan the sp_executesql"
|
27
27
|
end
|
28
28
|
|
29
29
|
it "from array condition using index" do
|
30
|
-
plan = Car.where(id: [1, 2]).explain
|
30
|
+
plan = Car.where(id: [1, 2]).explain.inspect
|
31
31
|
_(plan).must_include "SELECT [cars].* FROM [cars] WHERE [cars].[id] IN (1, 2)"
|
32
32
|
_(plan).must_include "Clustered Index Seek", "make sure we do not showplan the sp_executesql"
|
33
33
|
end
|
34
34
|
|
35
35
|
it "from array condition" do
|
36
|
-
plan = Car.where(name: ["honda", "zyke"]).explain
|
36
|
+
plan = Car.where(name: ["honda", "zyke"]).explain.inspect
|
37
37
|
_(plan).must_include " SELECT [cars].* FROM [cars] WHERE [cars].[name] IN (N'honda', N'zyke')"
|
38
38
|
_(plan).must_include "Clustered Index Scan", "make sure we do not showplan the sp_executesql"
|
39
39
|
end
|
@@ -42,7 +42,7 @@ class ShowplanTestSQLServer < ActiveRecord::TestCase
|
|
42
42
|
describe "With SHOWPLAN_TEXT option" do
|
43
43
|
it "use simple table printer" do
|
44
44
|
with_showplan_option("SHOWPLAN_TEXT") do
|
45
|
-
plan = Car.where(id: 1).explain
|
45
|
+
plan = Car.where(id: 1).explain.inspect
|
46
46
|
_(plan).must_include "SELECT [cars].* FROM [cars] WHERE [cars].[id]"
|
47
47
|
_(plan).must_include "Clustered Index Seek", "make sure we do not showplan the sp_executesql"
|
48
48
|
end
|
@@ -52,7 +52,7 @@ class ShowplanTestSQLServer < ActiveRecord::TestCase
|
|
52
52
|
describe "With SHOWPLAN_XML option" do
|
53
53
|
it "show formatted xml" do
|
54
54
|
with_showplan_option("SHOWPLAN_XML") do
|
55
|
-
plan = Car.where(id: 1).explain
|
55
|
+
plan = Car.where(id: 1).explain.inspect
|
56
56
|
_(plan).must_include "ShowPlanXML"
|
57
57
|
end
|
58
58
|
end
|
@@ -6,8 +6,12 @@ class SpecificSchemaTestSQLServer < ActiveRecord::TestCase
|
|
6
6
|
after { SSTestEdgeSchema.delete_all }
|
7
7
|
|
8
8
|
it "handle dollar symbols" do
|
9
|
-
SSTestDollarTableName.
|
10
|
-
|
9
|
+
assert_difference("SSTestDollarTableName.count", 1) do
|
10
|
+
SSTestDollarTableName.create!
|
11
|
+
end
|
12
|
+
assert_nothing_raised do
|
13
|
+
SSTestDollarTableName.limit(20).offset(1)
|
14
|
+
end
|
11
15
|
end
|
12
16
|
|
13
17
|
it "models can use tinyint pk tables" do
|
@@ -93,7 +97,7 @@ class SpecificSchemaTestSQLServer < ActiveRecord::TestCase
|
|
93
97
|
|
94
98
|
it "use primary key for row table order in pagination sql" do
|
95
99
|
sql = /ORDER BY \[sst_natural_pk_data\]\.\[legacy_id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY/
|
96
|
-
|
100
|
+
assert_queries_match(sql) { SSTestNaturalPkData.limit(5).offset(5).load }
|
97
101
|
end
|
98
102
|
|
99
103
|
# Special quoted column
|
@@ -112,16 +116,16 @@ class SpecificSchemaTestSQLServer < ActiveRecord::TestCase
|
|
112
116
|
end
|
113
117
|
end
|
114
118
|
# Using ActiveRecord's quoted_id feature for objects.
|
115
|
-
|
116
|
-
|
119
|
+
assert_queries_match(/@0 = 'T'/) { SSTestDatatypeMigration.where(char_col: value.new).first }
|
120
|
+
assert_queries_match(/@0 = 'T'/) { SSTestDatatypeMigration.where(varchar_col: value.new).first }
|
117
121
|
# Using our custom char type data.
|
118
122
|
type = ActiveRecord::Type::SQLServer::Char
|
119
123
|
data = ActiveRecord::Type::SQLServer::Data
|
120
|
-
|
121
|
-
|
124
|
+
assert_queries_match(/@0 = 'T'/) { SSTestDatatypeMigration.where(char_col: data.new("T", type.new)).first }
|
125
|
+
assert_queries_match(/@0 = 'T'/) { SSTestDatatypeMigration.where(varchar_col: data.new("T", type.new)).first }
|
122
126
|
# Taking care of everything.
|
123
|
-
|
124
|
-
|
127
|
+
assert_queries_match(/@0 = 'T'/) { SSTestDatatypeMigration.where(char_col: "T").first }
|
128
|
+
assert_queries_match(/@0 = 'T'/) { SSTestDatatypeMigration.where(varchar_col: "T").first }
|
125
129
|
end
|
126
130
|
|
127
131
|
it "can update and hence properly quoted non-national char/varchar columns" do
|
@@ -159,15 +163,15 @@ class SpecificSchemaTestSQLServer < ActiveRecord::TestCase
|
|
159
163
|
|
160
164
|
it "returns a new id via connection newid_function" do
|
161
165
|
acceptable_uuid = ActiveRecord::ConnectionAdapters::SQLServer::Type::Uuid::ACCEPTABLE_UUID
|
162
|
-
db_uuid = ActiveRecord::Base.
|
166
|
+
db_uuid = ActiveRecord::Base.lease_connection.newid_function
|
163
167
|
_(db_uuid).must_match(acceptable_uuid)
|
164
168
|
end
|
165
169
|
|
166
170
|
# with similar table definition in two schemas
|
167
171
|
|
168
172
|
it "returns the correct primary columns" do
|
169
|
-
connection = ActiveRecord::Base.
|
170
|
-
assert_equal "field_1", connection.columns("test.
|
171
|
-
assert_equal "field_2", connection.columns("test2.
|
173
|
+
connection = ActiveRecord::Base.lease_connection
|
174
|
+
assert_equal "field_1", connection.columns("test.sst_schema_test_multiple_schema").detect(&:is_primary?).name
|
175
|
+
assert_equal "field_2", connection.columns("test2.sst_schema_test_multiple_schema").detect(&:is_primary?).name
|
172
176
|
end
|
173
177
|
end
|
@@ -42,6 +42,9 @@ class TransactionTestSQLServer < ActiveRecord::TestCase
|
|
42
42
|
after_level = connection.user_options_isolation_level
|
43
43
|
_(in_level).must_match %r{serializable}i
|
44
44
|
_(after_level).must_match %r{read committed}i
|
45
|
+
ensure
|
46
|
+
# Reset all connections. Otherwise, the next test may fail with error 'DBPROCESS is dead or not enabled'. Not sure why.
|
47
|
+
ActiveRecord::Base.connection_handler.clear_all_connections!(:all)
|
45
48
|
end
|
46
49
|
|
47
50
|
it "can use an isolation level and reverts back to starting isolation level under exceptions" do
|
@@ -50,20 +53,16 @@ class TransactionTestSQLServer < ActiveRecord::TestCase
|
|
50
53
|
Ship.transaction(isolation: :serializable) { Ship.create! }
|
51
54
|
}).must_raise(ActiveRecord::RecordInvalid)
|
52
55
|
_(connection.user_options_isolation_level).must_match %r{read committed}i
|
56
|
+
ensure
|
57
|
+
# Reset all connections. Otherwise, the next test may fail with error 'DBPROCESS is dead or not enabled'. Not sure why.
|
58
|
+
ActiveRecord::Base.connection_handler.clear_all_connections!(:all)
|
53
59
|
end
|
54
60
|
|
55
61
|
describe "when READ_COMMITTED_SNAPSHOT is set" do
|
56
|
-
|
62
|
+
it "should use READ COMMITTED as an isolation level" do
|
57
63
|
connection.execute "ALTER DATABASE [#{connection.current_database}] SET ALLOW_SNAPSHOT_ISOLATION ON"
|
58
64
|
connection.execute "ALTER DATABASE [#{connection.current_database}] SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE"
|
59
|
-
end
|
60
|
-
|
61
|
-
after do
|
62
|
-
connection.execute "ALTER DATABASE [#{connection.current_database}] SET ALLOW_SNAPSHOT_ISOLATION OFF"
|
63
|
-
connection.execute "ALTER DATABASE [#{connection.current_database}] SET READ_COMMITTED_SNAPSHOT OFF WITH ROLLBACK IMMEDIATE"
|
64
|
-
end
|
65
65
|
|
66
|
-
it "should use READ COMMITTED as an isolation level" do
|
67
66
|
_(connection.user_options_isolation_level).must_match "read committed snapshot"
|
68
67
|
|
69
68
|
Ship.transaction(isolation: :serializable) do
|
@@ -74,6 +73,12 @@ class TransactionTestSQLServer < ActiveRecord::TestCase
|
|
74
73
|
# "READ COMMITTED", and that no exception was raised (it's reported back
|
75
74
|
# by SQL Server as "read committed snapshot").
|
76
75
|
_(connection.user_options_isolation_level).must_match "read committed snapshot"
|
76
|
+
ensure
|
77
|
+
connection.execute "ALTER DATABASE [#{connection.current_database}] SET ALLOW_SNAPSHOT_ISOLATION OFF"
|
78
|
+
connection.execute "ALTER DATABASE [#{connection.current_database}] SET READ_COMMITTED_SNAPSHOT OFF WITH ROLLBACK IMMEDIATE"
|
79
|
+
|
80
|
+
# Reset all connections. Otherwise, the next test may fail with error 'DBPROCESS is dead or not enabled'. Not sure why.
|
81
|
+
ActiveRecord::Base.connection_handler.clear_all_connections!(:all)
|
77
82
|
end
|
78
83
|
end
|
79
84
|
|
@@ -28,4 +28,24 @@ class SQLServerTriggerTest < ActiveRecord::TestCase
|
|
28
28
|
_(obj.id).must_be :present?
|
29
29
|
_(obj.id.to_s).must_equal SSTestTriggerHistory.first.id_source
|
30
30
|
end
|
31
|
+
|
32
|
+
it "can insert into a table with composite pk with output inserted - with a true setting for table name" do
|
33
|
+
exclude_output_inserted_table_names["sst_table_with_composite_pk_trigger"] = true
|
34
|
+
assert SSTestTriggerHistory.all.empty?
|
35
|
+
obj = SSTestTriggerCompositePk.create! pk_col_one: 123, pk_col_two: 42, event_name: "test trigger"
|
36
|
+
_(obj.event_name).must_equal "test trigger"
|
37
|
+
_(obj.pk_col_one).must_equal 123
|
38
|
+
_(obj.pk_col_two).must_equal 42
|
39
|
+
_(obj.pk_col_one.to_s).must_equal SSTestTriggerHistory.first.id_source
|
40
|
+
end
|
41
|
+
|
42
|
+
it "can insert into a table with composite pk with different data type with output inserted - with a hash setting for table name" do
|
43
|
+
exclude_output_inserted_table_names["sst_table_with_composite_pk_trigger_with_different_data_type"] = { pk_col_one: "uniqueidentifier", pk_col_two: "int" }
|
44
|
+
assert SSTestTriggerHistory.all.empty?
|
45
|
+
obj = SSTestTriggerCompositePkWithDefferentDataType.create! pk_col_two: 123, event_name: "test trigger"
|
46
|
+
_(obj.event_name).must_equal "test trigger"
|
47
|
+
_(obj.pk_col_one).must_be :present?
|
48
|
+
_(obj.pk_col_two).must_equal 123
|
49
|
+
_(obj.pk_col_one.to_s).must_equal SSTestTriggerHistory.first.id_source
|
50
|
+
end
|
31
51
|
end
|
@@ -77,7 +77,7 @@ class UtilsTestSQLServer < ActiveRecord::TestCase
|
|
77
77
|
_(extract_identifiers(" ").object).must_be_nil
|
78
78
|
end
|
79
79
|
|
80
|
-
it "has a #quoted that returns a fully quoted name with all identifiers as
|
80
|
+
it "has a #quoted that returns a fully quoted name with all identifiers as originally passed in" do
|
81
81
|
_(extract_identifiers("object").quoted).must_equal "[object]"
|
82
82
|
_(extract_identifiers("server.database..object").quoted).must_equal "[server].[database]..[object]"
|
83
83
|
_(extract_identifiers("[server]...[object]").quoted).must_equal "[server]...[object]"
|
@@ -92,7 +92,7 @@ class UtilsTestSQLServer < ActiveRecord::TestCase
|
|
92
92
|
_(extract_identifiers("[obj.name].[foo]").quoted).must_equal "[obj.name].[foo]"
|
93
93
|
end
|
94
94
|
|
95
|
-
it "should indicate if a name is fully
|
95
|
+
it "should indicate if a name is fully qualified" do
|
96
96
|
_(extract_identifiers("object").fully_qualified?).must_equal false
|
97
97
|
_(extract_identifiers("schema.object").fully_qualified?).must_equal false
|
98
98
|
_(extract_identifiers("database.schema.object").fully_qualified?).must_equal false
|
@@ -43,4 +43,12 @@ class SQLServerUuidTest < ActiveRecord::TestCase
|
|
43
43
|
obj = with_use_output_inserted_disabled { SSTestUuid.create!(name: "😢") }
|
44
44
|
_(obj.id).must_be :nil?
|
45
45
|
end
|
46
|
+
|
47
|
+
it "can add column with proc as default" do
|
48
|
+
table_name = SSTestUuid.table_name
|
49
|
+
connection.add_column table_name, :thingy, :uuid, null: false, default: -> { "NEWSEQUENTIALID()" }
|
50
|
+
SSTestUuid.reset_column_information
|
51
|
+
column = SSTestUuid.columns_hash["thingy"]
|
52
|
+
_(column.default_function).must_equal "newsequentialid()"
|
53
|
+
end
|
46
54
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cases/helper_sqlserver"
|
4
|
+
|
5
|
+
class ViewTestSQLServer < ActiveRecord::TestCase
|
6
|
+
let(:connection) { ActiveRecord::Base.lease_connection }
|
7
|
+
|
8
|
+
describe 'view with default values' do
|
9
|
+
before do
|
10
|
+
connection.drop_table :view_casing_table rescue nil
|
11
|
+
connection.create_table :view_casing_table, force: true do |t|
|
12
|
+
t.boolean :Default_Falsey, null: false, default: false
|
13
|
+
t.boolean :Default_Truthy, null: false, default: true
|
14
|
+
t.string :default_string_null, null: true, default: nil
|
15
|
+
t.string :default_string, null: false, default: "abc"
|
16
|
+
end
|
17
|
+
|
18
|
+
connection.execute("DROP VIEW IF EXISTS view_casing_table_view;")
|
19
|
+
connection.execute <<-SQL
|
20
|
+
CREATE VIEW view_casing_table_view AS
|
21
|
+
SELECT id AS id,
|
22
|
+
default_falsey AS falsey,
|
23
|
+
default_truthy AS truthy,
|
24
|
+
default_string_null AS s_null,
|
25
|
+
default_string AS s
|
26
|
+
FROM view_casing_table
|
27
|
+
SQL
|
28
|
+
end
|
29
|
+
|
30
|
+
it "default values are correct when column casing used in tables and views are different" do
|
31
|
+
klass = Class.new(ActiveRecord::Base) do
|
32
|
+
self.table_name = "view_casing_table_view"
|
33
|
+
end
|
34
|
+
|
35
|
+
obj = klass.new
|
36
|
+
assert_equal false, obj.falsey
|
37
|
+
assert_equal true, obj.truthy
|
38
|
+
assert_equal "abc", obj.s
|
39
|
+
assert_nil obj.s_null
|
40
|
+
assert_equal 0, klass.count
|
41
|
+
|
42
|
+
obj.save!
|
43
|
+
assert_equal false, obj.falsey
|
44
|
+
assert_equal true, obj.truthy
|
45
|
+
assert_equal "abc", obj.s
|
46
|
+
assert_nil obj.s_null
|
47
|
+
assert_equal 1, klass.count
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'identity insert' do
|
52
|
+
it "identity insert works with views" do
|
53
|
+
assert_difference("SSTestCustomersView.count", 1) do
|
54
|
+
SSTestCustomersView.create!(id: 5, name: "Bob")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/test/config.yml
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
|
2
2
|
default_connection_info: &default_connection_info
|
3
3
|
adapter: sqlserver
|
4
|
-
mode: <%= ENV['ARCONN'] || 'dblib' %>
|
5
4
|
host: <%= ENV['ACTIVERECORD_UNITTEST_HOST'] || 'localhost' %>
|
6
5
|
port: <%= ENV['ACTIVERECORD_UNITTEST_PORT'] %>
|
7
6
|
database: activerecord_unittest
|
@@ -12,7 +11,7 @@ default_connection_info: &default_connection_info
|
|
12
11
|
|
13
12
|
connections:
|
14
13
|
|
15
|
-
|
14
|
+
sqlserver:
|
16
15
|
arunit:
|
17
16
|
<<: *default_connection_info
|
18
17
|
appname: SQLServerAdptrUnit
|
@@ -7,3 +7,11 @@ end
|
|
7
7
|
class SSTestTriggerUuid < ActiveRecord::Base
|
8
8
|
self.table_name = "sst_table_with_uuid_trigger"
|
9
9
|
end
|
10
|
+
|
11
|
+
class SSTestTriggerCompositePk < ActiveRecord::Base
|
12
|
+
self.table_name = "sst_table_with_composite_pk_trigger"
|
13
|
+
end
|
14
|
+
|
15
|
+
class SSTestTriggerCompositePkWithDefferentDataType < ActiveRecord::Base
|
16
|
+
self.table_name = "sst_table_with_composite_pk_trigger_with_different_data_type"
|
17
|
+
end
|
@@ -14,8 +14,9 @@ ActiveRecord::Schema.define do
|
|
14
14
|
t.float :float_col
|
15
15
|
t.string :string_col
|
16
16
|
t.text :text_col
|
17
|
-
t.datetime :
|
18
|
-
t.
|
17
|
+
t.datetime :datetime_nil_precision_col, precision: nil
|
18
|
+
t.datetime :datetime_col # Precision defaults to 6
|
19
|
+
t.timestamp :timestamp_col # Precision defaults to 6
|
19
20
|
t.time :time_col
|
20
21
|
t.date :date_col
|
21
22
|
t.binary :binary_col
|
@@ -32,6 +33,7 @@ ActiveRecord::Schema.define do
|
|
32
33
|
t.nchar :nchar_col
|
33
34
|
t.ntext :ntext_col
|
34
35
|
t.binary_basic :binary_basic_col
|
36
|
+
t.binary_basic :binary_basic_16_col, limit: 16
|
35
37
|
t.varbinary :varbinary_col
|
36
38
|
t.uuid :uuid_col
|
37
39
|
t.ss_timestamp :sstimestamp_col
|
@@ -150,6 +152,10 @@ ActiveRecord::Schema.define do
|
|
150
152
|
SELECT GETUTCDATE() utcdate
|
151
153
|
SQL
|
152
154
|
|
155
|
+
create_table 'A Table With Spaces', force: true do |t|
|
156
|
+
t.string :name
|
157
|
+
end
|
158
|
+
|
153
159
|
# Constraints
|
154
160
|
|
155
161
|
create_table(:sst_has_fks, force: true) do |t|
|
@@ -231,6 +237,40 @@ ActiveRecord::Schema.define do
|
|
231
237
|
SELECT id AS id_source, event_name FROM INSERTED
|
232
238
|
SQL
|
233
239
|
|
240
|
+
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_table_with_composite_pk_trigger') DROP TABLE sst_table_with_composite_pk_trigger"
|
241
|
+
execute <<-SQL
|
242
|
+
CREATE TABLE sst_table_with_composite_pk_trigger(
|
243
|
+
pk_col_one int NOT NULL,
|
244
|
+
pk_col_two int NOT NULL,
|
245
|
+
event_name nvarchar(255),
|
246
|
+
CONSTRAINT PK_sst_table_with_composite_pk_trigger PRIMARY KEY (pk_col_one, pk_col_two)
|
247
|
+
)
|
248
|
+
SQL
|
249
|
+
execute <<-SQL
|
250
|
+
CREATE TRIGGER sst_table_with_composite_pk_trigger_t ON sst_table_with_composite_pk_trigger
|
251
|
+
FOR INSERT
|
252
|
+
AS
|
253
|
+
INSERT INTO sst_table_with_trigger_history (id_source, event_name)
|
254
|
+
SELECT pk_col_one AS id_source, event_name FROM INSERTED
|
255
|
+
SQL
|
256
|
+
|
257
|
+
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_table_with_composite_pk_trigger_with_different_data_type') DROP TABLE sst_table_with_composite_pk_trigger_with_different_data_type"
|
258
|
+
execute <<-SQL
|
259
|
+
CREATE TABLE sst_table_with_composite_pk_trigger_with_different_data_type(
|
260
|
+
pk_col_one uniqueidentifier DEFAULT NEWID(),
|
261
|
+
pk_col_two int NOT NULL,
|
262
|
+
event_name nvarchar(255),
|
263
|
+
CONSTRAINT PK_sst_table_with_composite_pk_trigger_with_different_data_type PRIMARY KEY (pk_col_one, pk_col_two)
|
264
|
+
)
|
265
|
+
SQL
|
266
|
+
execute <<-SQL
|
267
|
+
CREATE TRIGGER sst_table_with_composite_pk_trigger_with_different_data_type_t ON sst_table_with_composite_pk_trigger_with_different_data_type
|
268
|
+
FOR INSERT
|
269
|
+
AS
|
270
|
+
INSERT INTO sst_table_with_trigger_history (id_source, event_name)
|
271
|
+
SELECT pk_col_one AS id_source, event_name FROM INSERTED
|
272
|
+
SQL
|
273
|
+
|
234
274
|
# Another schema.
|
235
275
|
|
236
276
|
create_table :sst_schema_columns, force: true do |t|
|
@@ -269,17 +309,17 @@ ActiveRecord::Schema.define do
|
|
269
309
|
)
|
270
310
|
NATURALPKTABLESQLINOTHERSCHEMA
|
271
311
|
|
272
|
-
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '
|
312
|
+
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_schema_test_multiple_schema' and TABLE_SCHEMA = 'test') DROP TABLE test.sst_schema_test_multiple_schema"
|
273
313
|
execute <<-SCHEMATESTMULTIPLESCHEMA
|
274
|
-
CREATE TABLE test.
|
314
|
+
CREATE TABLE test.sst_schema_test_multiple_schema(
|
275
315
|
field_1 int NOT NULL PRIMARY KEY,
|
276
316
|
field_2 int,
|
277
317
|
)
|
278
318
|
SCHEMATESTMULTIPLESCHEMA
|
279
319
|
execute "IF NOT EXISTS(SELECT * FROM sys.schemas WHERE name = 'test2') EXEC sp_executesql N'CREATE SCHEMA test2'"
|
280
|
-
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '
|
320
|
+
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_schema_test_multiple_schema' and TABLE_SCHEMA = 'test2') DROP TABLE test2.sst_schema_test_multiple_schema"
|
281
321
|
execute <<-SCHEMATESTMULTIPLESCHEMA
|
282
|
-
CREATE TABLE test2.
|
322
|
+
CREATE TABLE test2.sst_schema_test_multiple_schema(
|
283
323
|
field_1 int,
|
284
324
|
field_2 int NOT NULL PRIMARY KEY,
|
285
325
|
)
|
@@ -312,4 +352,12 @@ ActiveRecord::Schema.define do
|
|
312
352
|
CONSTRAINT PK_sst_composite_with_identity PRIMARY KEY (pk_col_one, pk_col_two)
|
313
353
|
);
|
314
354
|
COMPOSITE_WITH_IDENTITY
|
355
|
+
|
356
|
+
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'aliens' and TABLE_SCHEMA = 'test') DROP TABLE test.aliens"
|
357
|
+
execute <<-TABLE_IN_OTHER_SCHEMA_USED_BY_MODEL
|
358
|
+
CREATE TABLE test.aliens(
|
359
|
+
id int IDENTITY NOT NULL primary key,
|
360
|
+
name varchar(255)
|
361
|
+
)
|
362
|
+
TABLE_IN_OTHER_SCHEMA_USED_BY_MODEL
|
315
363
|
end
|
@@ -13,7 +13,7 @@ module ARTest
|
|
13
13
|
module ClassMethods
|
14
14
|
def coerce_tests!(*methods)
|
15
15
|
methods.each do |method|
|
16
|
-
|
16
|
+
coerced_tests.push(method)
|
17
17
|
coerced_test_warning(method)
|
18
18
|
end
|
19
19
|
end
|
@@ -24,7 +24,7 @@ module ARTest
|
|
24
24
|
|
25
25
|
undef_method(method)
|
26
26
|
end
|
27
|
-
STDOUT.puts "🙉 🙈 🙊 Undefined all tests: #{
|
27
|
+
STDOUT.puts "🙉 🙈 🙊 Undefined all tests: #{name}"
|
28
28
|
end
|
29
29
|
|
30
30
|
private
|
@@ -43,9 +43,9 @@ module ARTest
|
|
43
43
|
end
|
44
44
|
|
45
45
|
if result.blank?
|
46
|
-
STDOUT.puts "🐳 Unfound coerced test: #{
|
46
|
+
STDOUT.puts "🐳 Unfound coerced test: #{name}##{m}"
|
47
47
|
else
|
48
|
-
STDOUT.puts "🐵 Undefined coerced test: #{
|
48
|
+
STDOUT.puts "🐵 Undefined coerced test: #{name}##{m}"
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
@@ -8,20 +8,14 @@ module ARTest
|
|
8
8
|
included { extend ConnectionReflection }
|
9
9
|
|
10
10
|
def connection
|
11
|
-
ActiveRecord::Base.
|
11
|
+
ActiveRecord::Base.lease_connection
|
12
12
|
end
|
13
13
|
|
14
14
|
def connection_options
|
15
|
-
connection.instance_variable_get :@
|
15
|
+
connection.instance_variable_get :@connection_parameters
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
connection_options[:mode] == :dblib
|
20
|
-
end
|
21
|
-
|
22
|
-
def connection_dblib_73?
|
23
|
-
return false unless connection_dblib?
|
24
|
-
|
18
|
+
def connection_tds_73
|
25
19
|
rc = connection.raw_connection
|
26
20
|
rc.respond_to?(:tds_73?) && rc.tds_73?
|
27
21
|
end
|
@@ -22,7 +22,13 @@ module SqlIgnoredCache
|
|
22
22
|
# compromising cache outside tests.
|
23
23
|
def cache_sql(sql, name, binds)
|
24
24
|
result = super
|
25
|
-
|
25
|
+
|
26
|
+
@query_cache.instance_variable_get(:@map).delete_if do |cache_key, _v|
|
27
|
+
# Query cache key generated by `sql` or `[sql, binds]`, so need to retrieve `sql` for both cases.
|
28
|
+
cache_key_sql = Array(cache_key).first
|
29
|
+
Regexp.union(IGNORED_SQL).match?(cache_key_sql)
|
30
|
+
end
|
31
|
+
|
26
32
|
result
|
27
33
|
end
|
28
34
|
end
|
Binary file
|