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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +30 -0
  3. data/.devcontainer/boot.sh +22 -0
  4. data/.devcontainer/devcontainer.json +38 -0
  5. data/.devcontainer/docker-compose.yml +42 -0
  6. data/.github/workflows/ci.yml +7 -4
  7. data/.gitignore +3 -1
  8. data/CHANGELOG.md +19 -42
  9. data/Dockerfile.ci +3 -3
  10. data/Gemfile +6 -1
  11. data/MIT-LICENSE +1 -1
  12. data/README.md +113 -27
  13. data/RUNNING_UNIT_TESTS.md +27 -14
  14. data/Rakefile +2 -6
  15. data/VERSION +1 -1
  16. data/activerecord-sqlserver-adapter.gemspec +3 -3
  17. data/appveyor.yml +4 -6
  18. data/docker-compose.ci.yml +2 -1
  19. data/lib/active_record/connection_adapters/sqlserver/core_ext/abstract_adapter.rb +20 -0
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +6 -4
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -23
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +10 -7
  23. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
  24. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +12 -2
  25. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +24 -16
  26. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -31
  27. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +143 -155
  28. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +5 -5
  29. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +57 -56
  30. data/lib/active_record/connection_adapters/sqlserver/savepoints.rb +26 -0
  31. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +14 -12
  32. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +11 -0
  33. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +213 -57
  34. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +3 -3
  35. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +13 -2
  36. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +4 -6
  37. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +19 -1
  38. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +1 -1
  39. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +1 -1
  40. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -1
  41. data/lib/active_record/connection_adapters/sqlserver/utils.rb +21 -10
  42. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +187 -187
  43. data/lib/active_record/connection_adapters/sqlserver_column.rb +1 -0
  44. data/lib/active_record/tasks/sqlserver_database_tasks.rb +42 -33
  45. data/lib/arel/visitors/sqlserver.rb +77 -34
  46. data/test/cases/active_schema_test_sqlserver.rb +127 -0
  47. data/test/cases/adapter_test_sqlserver.rb +114 -26
  48. data/test/cases/coerced_tests.rb +1121 -340
  49. data/test/cases/column_test_sqlserver.rb +67 -64
  50. data/test/cases/connection_test_sqlserver.rb +3 -6
  51. data/test/cases/dbconsole.rb +19 -0
  52. data/test/cases/disconnected_test_sqlserver.rb +8 -5
  53. data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
  54. data/test/cases/enum_test_sqlserver.rb +49 -0
  55. data/test/cases/execute_procedure_test_sqlserver.rb +9 -5
  56. data/test/cases/fetch_test_sqlserver.rb +19 -0
  57. data/test/cases/helper_sqlserver.rb +11 -5
  58. data/test/cases/index_test_sqlserver.rb +8 -6
  59. data/test/cases/json_test_sqlserver.rb +1 -1
  60. data/test/cases/lateral_test_sqlserver.rb +2 -2
  61. data/test/cases/migration_test_sqlserver.rb +19 -1
  62. data/test/cases/optimizer_hints_test_sqlserver.rb +21 -12
  63. data/test/cases/pessimistic_locking_test_sqlserver.rb +8 -7
  64. data/test/cases/primary_keys_test_sqlserver.rb +2 -2
  65. data/test/cases/rake_test_sqlserver.rb +10 -5
  66. data/test/cases/schema_dumper_test_sqlserver.rb +155 -109
  67. data/test/cases/schema_test_sqlserver.rb +64 -1
  68. data/test/cases/showplan_test_sqlserver.rb +7 -7
  69. data/test/cases/specific_schema_test_sqlserver.rb +17 -13
  70. data/test/cases/transaction_test_sqlserver.rb +13 -8
  71. data/test/cases/trigger_test_sqlserver.rb +20 -0
  72. data/test/cases/utils_test_sqlserver.rb +2 -2
  73. data/test/cases/uuid_test_sqlserver.rb +8 -0
  74. data/test/cases/view_test_sqlserver.rb +58 -0
  75. data/test/config.yml +1 -2
  76. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +1 -1
  77. data/test/models/sqlserver/alien.rb +5 -0
  78. data/test/models/sqlserver/table_with_spaces.rb +5 -0
  79. data/test/models/sqlserver/trigger.rb +8 -0
  80. data/test/schema/sqlserver_specific_schema.rb +54 -6
  81. data/test/support/coerceable_test_sqlserver.rb +4 -4
  82. data/test/support/connection_reflection.rb +3 -9
  83. data/test/support/core_ext/query_cache.rb +7 -1
  84. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
  85. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  86. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
  87. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
  88. data/test/support/query_assertions.rb +49 -0
  89. data/test/support/rake_helpers.rb +3 -1
  90. data/test/support/table_definition_sqlserver.rb +24 -0
  91. data/test/support/test_in_memory_oltp.rb +2 -2
  92. metadata +41 -17
  93. data/lib/active_record/sqlserver_base.rb +0 -18
  94. data/test/cases/scratchpad_test_sqlserver.rb +0 -8
  95. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  96. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  97. 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 dbo schema" do
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.create!
10
- SSTestDollarTableName.limit(20).offset(1)
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
- assert_sql(sql) { SSTestNaturalPkData.limit(5).offset(5).load }
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
- assert_sql(/@0 = 'T'/) { SSTestDatatypeMigration.where(char_col: value.new).first }
116
- assert_sql(/@0 = 'T'/) { SSTestDatatypeMigration.where(varchar_col: value.new).first }
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
- assert_sql(/@0 = 'T'/) { SSTestDatatypeMigration.where(char_col: data.new("T", type.new)).first }
121
- assert_sql(/@0 = 'T'/) { SSTestDatatypeMigration.where(varchar_col: data.new("T", type.new)).first }
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
- assert_sql(/@0 = 'T'/) { SSTestDatatypeMigration.where(char_col: "T").first }
124
- assert_sql(/@0 = 'T'/) { SSTestDatatypeMigration.where(varchar_col: "T").first }
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.connection.newid_function
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.connection
170
- assert_equal "field_1", connection.columns("test.sst_schema_test_mulitple_schema").detect(&:is_primary?).name
171
- assert_equal "field_2", connection.columns("test2.sst_schema_test_mulitple_schema").detect(&:is_primary?).name
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
- before do
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 orginially passed in" do
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 qualitified" do
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
- dblib:
14
+ sqlserver:
16
15
  arunit:
17
16
  <<: *default_connection_info
18
17
  appname: SQLServerAdptrUnit
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class TableWillNeverBeCreated < ActiveRecord::Migration
3
+ class TableWillNeverBeCreated < ActiveRecord::Migration[5.2]
4
4
  def self.up
5
5
  create_table(:sqlserver_trans_table1) {}
6
6
  create_table(:sqlserver_trans_table2) { raise("HELL") }
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Alien < ActiveRecord::Base
4
+ self.table_name = "test.aliens"
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TableWithSpaces < ActiveRecord::Base
4
+ self.table_name = "A Table With Spaces"
5
+ end
@@ -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 :datetime_col
18
- t.timestamp :timestamp_col
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 = 'sst_schema_test_mulitple_schema' and TABLE_SCHEMA = 'test') DROP TABLE test.sst_schema_test_mulitple_schema"
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.sst_schema_test_mulitple_schema(
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 = 'sst_schema_test_mulitple_schema' and TABLE_SCHEMA = 'test2') DROP TABLE test2.sst_schema_test_mulitple_schema"
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.sst_schema_test_mulitple_schema(
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
- self.coerced_tests.push(method)
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: #{self.name}"
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: #{self.name}##{m}"
46
+ STDOUT.puts "🐳 Unfound coerced test: #{name}##{m}"
47
47
  else
48
- STDOUT.puts "🐵 Undefined coerced test: #{self.name}##{m}"
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.connection
11
+ ActiveRecord::Base.lease_connection
12
12
  end
13
13
 
14
14
  def connection_options
15
- connection.instance_variable_get :@connection_options
15
+ connection.instance_variable_get :@connection_parameters
16
16
  end
17
17
 
18
- def connection_dblib?
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
- @query_cache.delete_if { |k, v| k =~ Regexp.union(IGNORED_SQL) }
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