activerecord-sqlserver-adapter 6.0.0.rc2 → 6.1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +26 -0
  3. data/CHANGELOG.md +20 -41
  4. data/README.md +32 -3
  5. data/RUNNING_UNIT_TESTS.md +1 -1
  6. data/VERSION +1 -1
  7. data/activerecord-sqlserver-adapter.gemspec +1 -1
  8. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +0 -9
  9. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +7 -2
  10. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
  11. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +28 -16
  12. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +7 -7
  13. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -1
  14. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -3
  15. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +31 -8
  16. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +27 -7
  17. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +0 -1
  18. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +2 -2
  19. data/lib/active_record/connection_adapters/sqlserver/type.rb +1 -0
  20. data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
  21. data/lib/active_record/connection_adapters/sqlserver/utils.rb +1 -1
  22. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +100 -68
  23. data/lib/active_record/connection_adapters/sqlserver_column.rb +17 -0
  24. data/lib/active_record/sqlserver_base.rb +9 -15
  25. data/lib/active_record/tasks/sqlserver_database_tasks.rb +17 -14
  26. data/lib/arel/visitors/sqlserver.rb +111 -39
  27. data/test/cases/adapter_test_sqlserver.rb +48 -14
  28. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  29. data/test/cases/coerced_tests.rb +598 -78
  30. data/test/cases/column_test_sqlserver.rb +5 -2
  31. data/test/cases/disconnected_test_sqlserver.rb +39 -0
  32. data/test/cases/execute_procedure_test_sqlserver.rb +9 -0
  33. data/test/cases/in_clause_test_sqlserver.rb +27 -0
  34. data/test/cases/lateral_test_sqlserver.rb +35 -0
  35. data/test/cases/migration_test_sqlserver.rb +51 -0
  36. data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
  37. data/test/cases/order_test_sqlserver.rb +7 -0
  38. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  39. data/test/cases/rake_test_sqlserver.rb +3 -2
  40. data/test/cases/schema_dumper_test_sqlserver.rb +20 -3
  41. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  42. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  43. data/test/schema/sqlserver_specific_schema.rb +17 -0
  44. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  45. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  46. data/test/support/sql_counter_sqlserver.rb +14 -12
  47. metadata +32 -13
  48. data/.travis.yml +0 -23
@@ -157,13 +157,16 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
157
157
  _(col.default).must_equal BigDecimal("191")
158
158
  _(obj.numeric_18_0).must_equal BigDecimal("191")
159
159
  _(col.default_function).must_be_nil
160
+
160
161
  type = connection.lookup_cast_type_from_column(col)
161
- _(type).must_be_instance_of Type::Decimal
162
+ _(type).must_be_instance_of Type::DecimalWithoutScale
162
163
  _(type.limit).must_be_nil
163
164
  _(type.precision).must_equal 18
164
- _(type.scale).must_equal 0
165
+ _(type.scale).must_be_nil
166
+
165
167
  obj.numeric_18_0 = "192.1"
166
168
  _(obj.numeric_18_0).must_equal BigDecimal("192")
169
+
167
170
  obj.save!
168
171
  _(obj.reload.numeric_18_0).must_equal BigDecimal("192")
169
172
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+
5
+ class TestDisconnectedAdapter < ActiveRecord::TestCase
6
+ self.use_transactional_tests = false
7
+
8
+ def setup
9
+ @connection = ActiveRecord::Base.connection
10
+ end
11
+
12
+ teardown do
13
+ return if in_memory_db?
14
+ db_config = ActiveRecord::Base.connection_db_config
15
+ ActiveRecord::Base.establish_connection(db_config)
16
+ end
17
+
18
+ test "can't execute procedures while disconnected" do
19
+ @connection.execute_procedure :sp_tables, "sst_datatypes"
20
+ @connection.disconnect!
21
+ assert_raises(ActiveRecord::ConnectionNotEstablished, 'SQL Server client is not connected') do
22
+ @connection.execute_procedure :sp_tables, "sst_datatypes"
23
+ end
24
+ end
25
+
26
+ test "can't execute query while disconnected" do
27
+ sql = "SELECT count(*) from products WHERE id IN(@0, @1)"
28
+ binds = [
29
+ ActiveRecord::Relation::QueryAttribute.new("id", 2, ActiveRecord::Type::BigInteger.new),
30
+ ActiveRecord::Relation::QueryAttribute.new("id", 2, ActiveRecord::Type::BigInteger.new)
31
+ ]
32
+
33
+ @connection.exec_query sql, "TEST", binds
34
+ @connection.disconnect!
35
+ assert_raises(ActiveRecord::ConnectionNotEstablished, 'SQL Server client is not connected') do
36
+ @connection.exec_query sql, "TEST", binds
37
+ end
38
+ end
39
+ end
@@ -41,4 +41,13 @@ class ExecuteProcedureTestSQLServer < ActiveRecord::TestCase
41
41
  date_base = connection.select_value("select GETUTCDATE()")
42
42
  assert_equal date_base.change(usec: 0), date_proc.change(usec: 0)
43
43
  end
44
+
45
+ it 'test deprecation with transaction return when executing procedure' do
46
+ assert_deprecated do
47
+ ActiveRecord::Base.transaction do
48
+ connection.execute_procedure("my_getutcdate")
49
+ return
50
+ end
51
+ end
52
+ end
44
53
  end
@@ -33,4 +33,31 @@ class InClauseTestSQLServer < ActiveRecord::TestCase
33
33
  assert_includes posts.to_sql, "ORDER BY [authors].[name]"
34
34
  assert_equal 8, posts.length
35
35
  end
36
+
37
+ it "removes ordering from 'not' subqueries" do
38
+ authors_subquery = Author.where.not(name: ["Mary", "Bob"]).order(:name)
39
+ posts = Post.where(author: authors_subquery)
40
+
41
+ assert_includes authors_subquery.to_sql, "ORDER BY [authors].[name]"
42
+ assert_not_includes posts.to_sql, "ORDER BY [authors].[name]"
43
+ assert_equal 5, posts.length
44
+ end
45
+
46
+ it "does not remove ordering from 'not' subquery that includes a limit" do
47
+ authors_subquery = Author.where.not(name: ["Ronan", "Mary", "Bob"]).order(:name).limit(2)
48
+ posts = Post.where(author: authors_subquery)
49
+
50
+ assert_includes authors_subquery.to_sql, "ORDER BY [authors].[name]"
51
+ assert_includes posts.to_sql, "ORDER BY [authors].[name]"
52
+ assert_equal 5, posts.length
53
+ end
54
+
55
+ it "does not remove ordering from 'not' subquery that includes an offset" do
56
+ authors_subquery = Author.where.not(name: ["David", "Ronan", "Cian"]).order(:name).offset(1)
57
+ posts = Post.where(author: authors_subquery)
58
+
59
+ assert_includes authors_subquery.to_sql, "ORDER BY [authors].[name]"
60
+ assert_includes posts.to_sql, "ORDER BY [authors].[name]"
61
+ assert_equal 3, posts.length
62
+ end
36
63
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+ require "models/post"
5
+ require "models/author"
6
+
7
+ class LateralTestSQLServer < ActiveRecord::TestCase
8
+ fixtures :posts, :authors
9
+
10
+ it 'uses OUTER APPLY for OUTER JOIN LATERAL' do
11
+ post = Arel::Table.new(:posts)
12
+ author = Arel::Table.new(:authors)
13
+ subselect = post.project(Arel.star).take(1).where(post[:author_id].eq(author[:id])).where(post[:id].eq(42))
14
+
15
+ one = Arel::Nodes::Quoted.new(1)
16
+ eq = Arel::Nodes::Equality.new(one, one)
17
+
18
+ sql = author.project(Arel.star).where(author[:name].matches("David")).outer_join(subselect.lateral.as("bar")).on(eq).to_sql
19
+ results = ActiveRecord::Base.connection.exec_query sql
20
+ assert_equal sql, "SELECT * FROM [authors] OUTER APPLY (SELECT * FROM [posts] WHERE [posts].[author_id] = [authors].[id] AND [posts].[id] = 42 ORDER BY [posts].[id] ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) AS bar WHERE [authors].[name] LIKE N'David'"
21
+ assert_equal results.length, 1
22
+ end
23
+
24
+ it 'uses CROSS APPLY for INNER JOIN LATERAL' do
25
+ post = Arel::Table.new(:posts)
26
+ author = Arel::Table.new(:authors)
27
+ subselect = post.project(Arel.star).take(1).where(post[:author_id].eq(author[:id])).where(post[:id].eq(42))
28
+
29
+ sql = author.project(Arel.star).where(author[:name].matches("David")).join(subselect.lateral.as("bar")).to_sql
30
+ results = ActiveRecord::Base.connection.exec_query sql
31
+
32
+ assert_equal sql, "SELECT * FROM [authors] CROSS APPLY (SELECT * FROM [posts] WHERE [posts].[author_id] = [authors].[id] AND [posts].[id] = 42 ORDER BY [posts].[id] ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) AS bar WHERE [authors].[name] LIKE N'David'"
33
+ assert_equal results.length, 0
34
+ end
35
+ end
@@ -63,5 +63,56 @@ class MigrationTestSQLServer < ActiveRecord::TestCase
63
63
  it "change null and default" do
64
64
  assert_nothing_raised { connection.change_column :people, :first_name, :text, null: true, default: nil }
65
65
  end
66
+
67
+ it "change collation" do
68
+ assert_nothing_raised { connection.change_column :sst_string_collation, :string_with_collation, :varchar, collation: :SQL_Latin1_General_CP437_BIN }
69
+
70
+ SstStringCollation.reset_column_information
71
+ assert_equal "SQL_Latin1_General_CP437_BIN", SstStringCollation.columns_hash['string_with_collation'].collation
72
+ end
73
+ end
74
+
75
+ describe "#create_schema" do
76
+ it "creates a new schema" do
77
+ connection.create_schema("some schema")
78
+
79
+ schemas = connection.exec_query("select name from sys.schemas").to_a
80
+
81
+ assert_includes schemas, { "name" => "some schema" }
82
+ end
83
+
84
+ it "creates a new schema with an owner" do
85
+ connection.create_schema("some schema", :guest)
86
+
87
+ schemas = connection.exec_query("select name, principal_id from sys.schemas").to_a
88
+
89
+ assert_includes schemas, { "name" => "some schema", "principal_id" => 2 }
90
+ end
91
+ end
92
+
93
+ describe "#change_table_schema" do
94
+ before { connection.create_schema("foo") }
95
+
96
+ it "transfer the given table to the given schema" do
97
+ connection.change_table_schema("foo", "orders")
98
+
99
+ assert connection.data_source_exists?("foo.orders")
100
+ end
101
+ end
102
+
103
+ describe "#drop_schema" do
104
+ before { connection.create_schema("some schema") }
105
+
106
+ it "drops a schema" do
107
+ schemas = connection.exec_query("select name from sys.schemas").to_a
108
+
109
+ assert_includes schemas, { "name" => "some schema" }
110
+
111
+ connection.drop_schema("some schema")
112
+
113
+ schemas = connection.exec_query("select name from sys.schemas").to_a
114
+
115
+ refute_includes schemas, { "name" => "some schema" }
116
+ end
66
117
  end
67
118
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+ require "models/company"
5
+
6
+ class OptimizerHitsTestSQLServer < ActiveRecord::TestCase
7
+ fixtures :companies
8
+
9
+ it "apply optimizations" do
10
+ assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
11
+ companies = Company.optimizer_hints("HASH GROUP")
12
+ companies = companies.distinct.select("firm_id")
13
+ assert_includes companies.explain, "| Hash Match | Aggregate |"
14
+ end
15
+
16
+ assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(ORDER GROUP\)\z}) do
17
+ companies = Company.optimizer_hints("ORDER GROUP")
18
+ companies = companies.distinct.select("firm_id")
19
+ assert_includes companies.explain, "| Stream Aggregate | Aggregate |"
20
+ end
21
+ end
22
+
23
+ it "apply multiple optimizations" do
24
+ assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP, FAST 1\)\z}) do
25
+ companies = Company.optimizer_hints("HASH GROUP", "FAST 1")
26
+ companies = companies.distinct.select("firm_id")
27
+ assert_includes companies.explain, "| Hash Match | Flow Distinct |"
28
+ end
29
+ end
30
+
31
+ it "support subqueries" do
32
+ assert_sql(%r{.*'SELECT COUNT\(count_column\) FROM \(SELECT .*\) subquery_for_count OPTION \(MAXDOP 2\)'.*}) do
33
+ companies = Company.optimizer_hints("MAXDOP 2")
34
+ companies = companies.select(:id).where(firm_id: [0, 1]).limit(3)
35
+ assert_equal 3, companies.count
36
+ end
37
+ end
38
+
39
+ it "sanitize values" do
40
+ assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
41
+ companies = Company.optimizer_hints("OPTION (HASH GROUP)")
42
+ companies = companies.distinct.select("firm_id")
43
+ companies.to_a
44
+ end
45
+
46
+ assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
47
+ companies = Company.optimizer_hints("OPTION(HASH GROUP)")
48
+ companies = companies.distinct.select("firm_id")
49
+ companies.to_a
50
+ end
51
+
52
+ assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(TABLE HINT \(\[companies\], INDEX\(1\)\)\)\z}) do
53
+ companies = Company.optimizer_hints("OPTION(TABLE HINT ([companies], INDEX(1)))")
54
+ companies = companies.distinct.select("firm_id")
55
+ companies.to_a
56
+ end
57
+
58
+ assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
59
+ companies = Company.optimizer_hints("Option(HASH GROUP)")
60
+ companies = companies.distinct.select("firm_id")
61
+ companies.to_a
62
+ end
63
+ end
64
+
65
+ it "skip optimization after unscope" do
66
+ assert_sql("SELECT DISTINCT [companies].[firm_id] FROM [companies]") do
67
+ companies = Company.optimizer_hints("HASH GROUP")
68
+ companies = companies.distinct.select("firm_id")
69
+ companies.unscope(:optimizer_hints).load
70
+ end
71
+ end
72
+ end
@@ -143,4 +143,11 @@ class OrderTestSQLServer < ActiveRecord::TestCase
143
143
  assert_equal post1, Post.order(Arel.sql(order)).first
144
144
  assert_equal post2, Post.order(Arel.sql(order)).second
145
145
  end
146
+
147
+ # Executing this kind of queries will raise "A column has been specified more than once in the order by list"
148
+ # This test shows that we don't do anything to prevent this
149
+ it "doesn't deduplicate semantically equal orders" do
150
+ sql = Post.order(:id).order("posts.id ASC").to_sql
151
+ assert_equal "SELECT [posts].* FROM [posts] ORDER BY [posts].[id] ASC, posts.id ASC", sql
152
+ end
146
153
  end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+ require "support/schema_dumping_helper"
5
+
6
+ class PrimaryKeyUuidTypeTest < ActiveRecord::TestCase
7
+ include SchemaDumpingHelper
8
+
9
+ self.use_transactional_tests = false
10
+
11
+ class Barcode < ActiveRecord::Base
12
+ end
13
+
14
+ setup do
15
+ @connection = ActiveRecord::Base.connection
16
+ @connection.create_table(:barcodes, primary_key: "code", id: :uuid, force: true)
17
+ end
18
+
19
+ teardown do
20
+ @connection.drop_table(:barcodes, if_exists: true)
21
+ end
22
+
23
+ def test_any_type_primary_key
24
+ assert_equal "code", Barcode.primary_key
25
+
26
+ column = Barcode.column_for_attribute(Barcode.primary_key)
27
+ assert_not column.null
28
+ assert_equal :uuid, column.type
29
+ assert_not_predicate column, :is_identity?
30
+ assert_predicate column, :is_primary?
31
+ ensure
32
+ Barcode.reset_column_information
33
+ end
34
+
35
+ test "schema dump primary key includes default" do
36
+ schema = dump_table_schema "barcodes"
37
+ assert_match %r/create_table "barcodes", primary_key: "code", id: :uuid, default: -> { "newid\(\)" }/, schema
38
+ end
39
+ end
40
+
41
+ class PrimaryKeyIntegerTest < ActiveRecord::TestCase
42
+ include SchemaDumpingHelper
43
+
44
+ self.use_transactional_tests = false
45
+
46
+ class Barcode < ActiveRecord::Base
47
+ end
48
+
49
+ class Widget < ActiveRecord::Base
50
+ end
51
+
52
+ setup do
53
+ @connection = ActiveRecord::Base.connection
54
+ end
55
+
56
+ teardown do
57
+ @connection.drop_table :barcodes, if_exists: true
58
+ @connection.drop_table :widgets, if_exists: true
59
+ end
60
+
61
+ test "integer primary key without default" do
62
+ @connection.create_table(:widgets, id: :integer, force: true)
63
+ column = @connection.columns(:widgets).find { |c| c.name == "id" }
64
+ assert_predicate column, :is_primary?
65
+ assert_predicate column, :is_identity?
66
+ assert_equal :integer, column.type
67
+ assert_not_predicate column, :bigint?
68
+
69
+ schema = dump_table_schema "widgets"
70
+ assert_match %r/create_table "widgets", id: :integer, force: :cascade do/, schema
71
+ end
72
+
73
+ test "bigint primary key without default" do
74
+ @connection.create_table(:widgets, id: :bigint, force: true)
75
+ column = @connection.columns(:widgets).find { |c| c.name == "id" }
76
+ assert_predicate column, :is_primary?
77
+ assert_predicate column, :is_identity?
78
+ assert_equal :integer, column.type
79
+ assert_predicate column, :bigint?
80
+
81
+ schema = dump_table_schema "widgets"
82
+ assert_match %r/create_table "widgets", force: :cascade do/, schema
83
+ end
84
+
85
+ test "don't set identity to integer and bigint when there is a default" do
86
+ @connection.create_table(:barcodes, id: :integer, default: nil, force: true)
87
+ @connection.create_table(:widgets, id: :bigint, default: nil, force: true)
88
+
89
+ column = @connection.columns(:widgets).find { |c| c.name == "id" }
90
+ assert_predicate column, :is_primary?
91
+ assert_not_predicate column, :is_identity?
92
+
93
+ schema = dump_table_schema "widgets"
94
+ assert_match %r/create_table "widgets", id: :bigint, default: nil, force: :cascade do/, schema
95
+
96
+ column = @connection.columns(:barcodes).find { |c| c.name == "id" }
97
+ assert_predicate column, :is_primary?
98
+ assert_not_predicate column, :is_identity?
99
+
100
+ schema = dump_table_schema "barcodes"
101
+ assert_match %r/create_table "barcodes", id: :integer, default: nil, force: :cascade do/, schema
102
+ end
103
+ end
@@ -10,8 +10,9 @@ class SQLServerRakeTest < ActiveRecord::TestCase
10
10
 
11
11
  let(:db_tasks) { ActiveRecord::Tasks::DatabaseTasks }
12
12
  let(:new_database) { "activerecord_unittest_tasks" }
13
- let(:default_configuration) { ARTest.connection_config["arunit"] }
13
+ let(:default_configuration) { ARTest.test_configuration_hashes["arunit"] }
14
14
  let(:configuration) { default_configuration.merge("database" => new_database) }
15
+ let(:db_config) { ActiveRecord::Base.configurations.resolve(configuration) }
15
16
 
16
17
  before { skip "on azure" if azure_skip }
17
18
  before { disconnect! unless azure_skip }
@@ -151,7 +152,7 @@ class SQLServerRakeStructureDumpLoadTest < SQLServerRakeTest
151
152
  _(filedata).must_match %r{CREATE TABLE dbo\.users}
152
153
  db_tasks.purge(configuration)
153
154
  _(connection.tables).wont_include "users"
154
- db_tasks.load_schema configuration, :sql, filename
155
+ db_tasks.load_schema db_config, :sql, filename
155
156
  _(connection.tables).must_include "users"
156
157
  end
157
158
  end
@@ -16,7 +16,7 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
16
16
  assert_line :tinyint, type: "integer", limit: 1, precision: nil, scale: nil, default: 42
17
17
  assert_line :bit, type: "boolean", limit: nil, precision: nil, scale: nil, default: true
18
18
  assert_line :decimal_9_2, type: "decimal", limit: nil, precision: 9, scale: 2, default: 12345.01
19
- assert_line :numeric_18_0, type: "decimal", limit: nil, precision: 18, scale: 0, default: 191.0
19
+ assert_line :numeric_18_0, type: "decimal", limit: nil, precision: 18, scale: nil, default: 191
20
20
  assert_line :numeric_36_2, type: "decimal", limit: nil, precision: 36, scale: 2, default: 12345678901234567890.01
21
21
  assert_line :money, type: "money", limit: nil, precision: 19, scale: 4, default: 4.2
22
22
  assert_line :smallmoney, type: "smallmoney", limit: nil, precision: 10, scale: 4, default: 4.2
@@ -75,7 +75,7 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
75
75
  assert_line :integer_col, type: "integer", limit: nil, precision: nil, scale: nil, default: nil
76
76
  assert_line :bigint_col, type: "bigint", limit: nil, precision: nil, scale: nil, default: nil
77
77
  assert_line :boolean_col, type: "boolean", limit: nil, precision: nil, scale: nil, default: nil
78
- assert_line :decimal_col, type: "decimal", limit: nil, precision: 18, scale: 0, default: nil
78
+ assert_line :decimal_col, type: "decimal", limit: nil, precision: 18, scale: nil, default: nil
79
79
  assert_line :float_col, type: "float", limit: nil, precision: nil, scale: nil, default: nil
80
80
  assert_line :string_col, type: "string", limit: nil, precision: nil, scale: nil, default: nil
81
81
  assert_line :text_col, type: "text", limit: nil, precision: nil, scale: nil, default: nil
@@ -119,6 +119,15 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
119
119
  assert_line :json_col, type: "text", limit: nil, precision: nil, scale: nil, default: nil
120
120
  end
121
121
 
122
+ it "dump column collation" do
123
+ generate_schema_for_table('sst_string_collation')
124
+
125
+ assert_line :string_without_collation, type: "string", limit: nil, default: nil, collation: nil
126
+ assert_line :string_default_collation, type: "varchar", limit: nil, default: nil, collation: nil
127
+ assert_line :string_with_collation, type: "varchar", limit: nil, default: nil, collation: "SQL_Latin1_General_CP1_CS_AS"
128
+ assert_line :varchar_with_collation, type: "varchar", limit: nil, default: nil, collation: "SQL_Latin1_General_CP1_CS_AS"
129
+ end
130
+
122
131
  # Special Cases
123
132
 
124
133
  it "honor nonstandard primary keys" do
@@ -135,6 +144,12 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
135
144
  assert_line :name, type: "string", limit: nil, default: nil, collation: nil
136
145
  end
137
146
 
147
+ it "dumps field with unique key constraints only once" do
148
+ output = generate_schema_for_table "unique_key_dumped_table"
149
+
150
+ _(output.scan('t.integer "unique_field"').length).must_equal(1)
151
+ end
152
+
138
153
  private
139
154
 
140
155
  def generate_schema_for_table(*table_names)
@@ -160,13 +175,15 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
160
175
 
161
176
  def assert_line(column_name, options = {})
162
177
  line = line(column_name)
163
- assert line, "Count not find line with column name: #{column_name.inspect} in schema:\n#{schema}"
178
+ assert line, "Could not find line with column name: #{column_name.inspect} in schema:\n#{schema}"
179
+
164
180
  [:type, :limit, :precision, :scale, :collation, :default].each do |key|
165
181
  next unless options.key?(key)
166
182
 
167
183
  actual = key == :type ? line.send(:type_method) : line.send(key)
168
184
  expected = options[key]
169
185
  message = "#{key.to_s.titleize} of #{expected.inspect} not found in:\n#{line}"
186
+
170
187
  if expected.nil?
171
188
  _(actual).must_be_nil message
172
189
  elsif expected.is_a?(Array)