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
@@ -7,55 +7,64 @@ class OptimizerHitsTestSQLServer < ActiveRecord::TestCase
7
7
  fixtures :companies
8
8
 
9
9
  it "apply optimizations" do
10
- assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
10
+ assert_queries_match(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
11
11
  companies = Company.optimizer_hints("HASH GROUP")
12
12
  companies = companies.distinct.select("firm_id")
13
- assert_includes companies.explain, "| Hash Match | Aggregate |"
13
+ assert_includes companies.explain.inspect, "| Hash Match | Aggregate |"
14
14
  end
15
15
 
16
- assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(ORDER GROUP\)\z}) do
16
+ assert_queries_match(%r{\ASELECT .+ FROM .+ OPTION \(ORDER GROUP\)\z}) do
17
17
  companies = Company.optimizer_hints("ORDER GROUP")
18
18
  companies = companies.distinct.select("firm_id")
19
- assert_includes companies.explain, "| Stream Aggregate | Aggregate |"
19
+ assert_includes companies.explain.inspect, "| Stream Aggregate | Aggregate |"
20
20
  end
21
21
  end
22
22
 
23
23
  it "apply multiple optimizations" do
24
- assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP, FAST 1\)\z}) do
24
+ assert_queries_match(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP, FAST 1\)\z}) do
25
25
  companies = Company.optimizer_hints("HASH GROUP", "FAST 1")
26
26
  companies = companies.distinct.select("firm_id")
27
- assert_includes companies.explain, "| Hash Match | Flow Distinct |"
27
+ assert_includes companies.explain.inspect, "| Hash Match | Flow Distinct |"
28
28
  end
29
29
  end
30
30
 
31
31
  it "support subqueries" do
32
- assert_sql(%r{.*'SELECT COUNT\(count_column\) FROM \(SELECT .*\) subquery_for_count OPTION \(MAXDOP 2\)'.*}) do
32
+ assert_queries_match(%r{.*'SELECT COUNT\(count_column\) FROM \(SELECT .*\) subquery_for_count OPTION \(MAXDOP 2\)'.*}) do
33
33
  companies = Company.optimizer_hints("MAXDOP 2")
34
34
  companies = companies.select(:id).where(firm_id: [0, 1]).limit(3)
35
35
  assert_equal 3, companies.count
36
36
  end
37
37
  end
38
38
 
39
+
40
+ it "support order" do
41
+ assert_queries_match(%r{\ASELECT .+ FROM .+ ORDER .+ OPTION .+\z}) do
42
+ companies = Company.optimizer_hints("LABEL='FindCompanies'")
43
+ companies = companies.order(:id)
44
+ companies.to_a
45
+ end
46
+ end
47
+
39
48
  it "sanitize values" do
40
- assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
49
+ assert_queries_match(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
41
50
  companies = Company.optimizer_hints("OPTION (HASH GROUP)")
42
51
  companies = companies.distinct.select("firm_id")
43
52
  companies.to_a
44
53
  end
45
54
 
46
- assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
55
+ assert_queries_match(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
47
56
  companies = Company.optimizer_hints("OPTION(HASH GROUP)")
48
57
  companies = companies.distinct.select("firm_id")
49
58
  companies.to_a
50
59
  end
51
60
 
52
- assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(TABLE HINT \(\[companies\], INDEX\(1\)\)\)\z}) do
61
+ assert_queries_match(%r{\ASELECT .+ FROM .+ OPTION \(TABLE HINT \(\[companies\], INDEX\(1\)\)\)\z}) do
53
62
  companies = Company.optimizer_hints("OPTION(TABLE HINT ([companies], INDEX(1)))")
54
63
  companies = companies.distinct.select("firm_id")
55
64
  companies.to_a
56
65
  end
57
66
 
58
- assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
67
+ assert_queries_match(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
59
68
  companies = Company.optimizer_hints("Option(HASH GROUP)")
60
69
  companies = companies.distinct.select("firm_id")
61
70
  companies.to_a
@@ -63,7 +72,7 @@ class OptimizerHitsTestSQLServer < ActiveRecord::TestCase
63
72
  end
64
73
 
65
74
  it "skip optimization after unscope" do
66
- assert_sql("SELECT DISTINCT [companies].[firm_id] FROM [companies]") do
75
+ assert_queries_match("SELECT DISTINCT [companies].[firm_id] FROM [companies]") do
67
76
  companies = Company.optimizer_hints("HASH GROUP")
68
77
  companies = companies.distinct.select("firm_id")
69
78
  companies.unscope(:optimizer_hints).load
@@ -13,7 +13,7 @@ class PessimisticLockingTestSQLServer < ActiveRecord::TestCase
13
13
  end
14
14
 
15
15
  it "uses with updlock by default" do
16
- assert_sql %r|SELECT \[people\]\.\* FROM \[people\] WITH\(UPDLOCK\)| do
16
+ assert_queries_match %r|SELECT \[people\]\.\* FROM \[people\] WITH\(UPDLOCK\)| do
17
17
  _(Person.lock(true).to_a).must_equal Person.all.to_a
18
18
  end
19
19
  end
@@ -47,32 +47,32 @@ class PessimisticLockingTestSQLServer < ActiveRecord::TestCase
47
47
  end
48
48
 
49
49
  it "can add a custom lock directive" do
50
- assert_sql %r|SELECT \[people\]\.\* FROM \[people\] WITH\(HOLDLOCK, ROWLOCK\)| do
50
+ assert_queries_match %r|SELECT \[people\]\.\* FROM \[people\] WITH\(HOLDLOCK, ROWLOCK\)| do
51
51
  Person.lock("WITH(HOLDLOCK, ROWLOCK)").load
52
52
  end
53
53
  end
54
54
 
55
55
  describe "joining tables" do
56
56
  it "joined tables use updlock by default" do
57
- assert_sql %r|SELECT \[people\]\.\* FROM \[people\] WITH\(UPDLOCK\) INNER JOIN \[readers\] WITH\(UPDLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| do
57
+ assert_queries_match %r|SELECT \[people\]\.\* FROM \[people\] WITH\(UPDLOCK\) INNER JOIN \[readers\] WITH\(UPDLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| do
58
58
  Person.lock(true).joins(:readers).load
59
59
  end
60
60
  end
61
61
 
62
62
  it "joined tables can use custom lock directive" do
63
- assert_sql %r|SELECT \[people\]\.\* FROM \[people\] WITH\(NOLOCK\) INNER JOIN \[readers\] WITH\(NOLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| do
63
+ assert_queries_match %r|SELECT \[people\]\.\* FROM \[people\] WITH\(NOLOCK\) INNER JOIN \[readers\] WITH\(NOLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| do
64
64
  Person.lock("WITH(NOLOCK)").joins(:readers).load
65
65
  end
66
66
  end
67
67
 
68
68
  it "left joined tables use updlock by default" do
69
- assert_sql %r|SELECT \[people\]\.\* FROM \[people\] WITH\(UPDLOCK\) LEFT OUTER JOIN \[readers\] WITH\(UPDLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| do
69
+ assert_queries_match %r|SELECT \[people\]\.\* FROM \[people\] WITH\(UPDLOCK\) LEFT OUTER JOIN \[readers\] WITH\(UPDLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| do
70
70
  Person.lock(true).left_joins(:readers).load
71
71
  end
72
72
  end
73
73
 
74
74
  it "left joined tables can use custom lock directive" do
75
- assert_sql %r|SELECT \[people\]\.\* FROM \[people\] WITH\(NOLOCK\) LEFT OUTER JOIN \[readers\] WITH\(NOLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| do
75
+ assert_queries_match %r|SELECT \[people\]\.\* FROM \[people\] WITH\(NOLOCK\) LEFT OUTER JOIN \[readers\] WITH\(NOLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| do
76
76
  Person.lock("WITH(NOLOCK)").left_joins(:readers).load
77
77
  end
78
78
  end
@@ -88,7 +88,8 @@ class PessimisticLockingTestSQLServer < ActiveRecord::TestCase
88
88
  it "copes with eager loading un-locked paginated" do
89
89
  eager_ids_sql = /SELECT\s+DISTINCT \[people\].\[id\] FROM \[people\] WITH\(UPDLOCK\) LEFT OUTER JOIN \[readers\] WITH\(UPDLOCK\)\s+ON \[readers\].\[person_id\] = \[people\].\[id\]\s+ORDER BY \[people\].\[id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY/
90
90
  loader_sql = /SELECT.*FROM \[people\] WITH\(UPDLOCK\).*WHERE \[people\]\.\[id\] IN/
91
- assert_sql(eager_ids_sql, loader_sql) do
91
+
92
+ assert_queries_match(/#{eager_ids_sql}|#{loader_sql}/, count: 2) do
92
93
  people = Person.lock(true).limit(5).offset(10).includes(:readers).references(:readers).to_a
93
94
  _(people[0].first_name).must_equal "Thing_10"
94
95
  _(people[1].first_name).must_equal "Thing_11"
@@ -12,7 +12,7 @@ class PrimaryKeyUuidTypeTest < ActiveRecord::TestCase
12
12
  end
13
13
 
14
14
  setup do
15
- @connection = ActiveRecord::Base.connection
15
+ @connection = ActiveRecord::Base.lease_connection
16
16
  @connection.create_table(:barcodes, primary_key: "code", id: :uuid, force: true)
17
17
  end
18
18
 
@@ -50,7 +50,7 @@ class PrimaryKeyIntegerTest < ActiveRecord::TestCase
50
50
  end
51
51
 
52
52
  setup do
53
- @connection = ActiveRecord::Base.connection
53
+ @connection = ActiveRecord::Base.lease_connection
54
54
  end
55
55
 
56
56
  teardown do
@@ -138,18 +138,22 @@ class SQLServerRakeStructureDumpLoadTest < SQLServerRakeTest
138
138
 
139
139
  it "dumps structure and accounts for defncopy oddities" do
140
140
  skip "debug defncopy on windows later" if host_windows?
141
+
141
142
  quietly { db_tasks.structure_dump configuration, filename }
143
+
142
144
  _(filedata).wont_match %r{\AUSE.*\z}
143
145
  _(filedata).wont_match %r{\AGO.*\z}
144
- _(filedata).must_match %r{email\s+nvarchar\(4000\)}
145
- _(filedata).must_match %r{background1\s+nvarchar\(max\)}
146
- _(filedata).must_match %r{background2\s+text\s+}
146
+ _(filedata).must_match %r{\[email\]\s+nvarchar\(4000\)}
147
+ _(filedata).must_match %r{\[background1\]\s+nvarchar\(max\)}
148
+ _(filedata).must_match %r{\[background2\]\s+text\s+}
147
149
  end
148
150
 
149
151
  it "can load dumped structure" do
150
152
  skip "debug defncopy on windows later" if host_windows?
153
+
151
154
  quietly { db_tasks.structure_dump configuration, filename }
152
- _(filedata).must_match %r{CREATE TABLE dbo\.users}
155
+
156
+ _(filedata).must_match %r{CREATE TABLE \[dbo\]\.\[users\]}
153
157
  db_tasks.purge(configuration)
154
158
  _(connection.tables).wont_include "users"
155
159
  db_tasks.load_schema db_config, :sql, filename
@@ -176,7 +180,8 @@ class SQLServerRakeSchemaCacheDumpLoadTest < SQLServerRakeTest
176
180
  it "dumps schema cache with SQL Server metadata" do
177
181
  quietly { db_tasks.dump_schema_cache connection, filename }
178
182
 
179
- schema_cache = YAML.load(File.read(filename))
183
+ filedata = File.read(filename)
184
+ _schema_cache = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(filedata) : YAML.load(filedata)
180
185
 
181
186
  col_id, col_name = connection.schema_cache.columns("users")
182
187
 
@@ -1,131 +1,142 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "cases/helper_sqlserver"
4
+ require "stringio"
4
5
 
5
6
  class SchemaDumperTestSQLServer < ActiveRecord::TestCase
6
7
  before { all_tables }
7
8
 
8
- let(:all_tables) { ActiveRecord::Base.connection.tables }
9
+ let(:all_tables) { ActiveRecord::Base.lease_connection.tables }
9
10
  let(:schema) { @generated_schema }
10
11
 
11
12
  it "sst_datatypes" do
12
13
  generate_schema_for_table "sst_datatypes"
13
- assert_line :bigint, type: "bigint", limit: nil, precision: nil, scale: nil, default: 42
14
- assert_line :int, type: "integer", limit: nil, precision: nil, scale: nil, default: 42
15
- assert_line :smallint, type: "integer", limit: 2, precision: nil, scale: nil, default: 42
16
- assert_line :tinyint, type: "integer", limit: 1, precision: nil, scale: nil, default: 42
17
- assert_line :bit, type: "boolean", limit: nil, precision: nil, scale: nil, default: true
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: nil, default: 191
20
- assert_line :numeric_36_2, type: "decimal", limit: nil, precision: 36, scale: 2, default: 12345678901234567890.01
21
- assert_line :money, type: "money", limit: nil, precision: 19, scale: 4, default: 4.2
22
- assert_line :smallmoney, type: "smallmoney", limit: nil, precision: 10, scale: 4, default: 4.2
14
+
15
+ assert_line :bigint, type: "bigint", default: 42
16
+ assert_line :int, type: "integer", default: 42
17
+ assert_line :smallint, type: "integer", limit: 2, default: 42
18
+ assert_line :tinyint, type: "integer", limit: 1, default: 42
19
+ assert_line :bit, type: "boolean", default: true
20
+ assert_line :decimal_9_2, type: "decimal", precision: 9, scale: 2, default: 12345.01
21
+ assert_line :numeric_18_0, type: "decimal", precision: 18, default: 191
22
+ assert_line :numeric_36_2, type: "decimal", precision: 36, scale: 2, default: 12345678901234567890.01
23
+ assert_line :money, type: "money", precision: 19, scale: 4, default: 4.2
24
+ assert_line :smallmoney, type: "smallmoney", precision: 10, scale: 4, default: 4.2
23
25
  # Approximate Numerics
24
- assert_line :float, type: "float", limit: nil, precision: nil, scale: nil, default: 123.00000001
25
- assert_line :real, type: "real", limit: nil, precision: nil, scale: nil, default: 123.45
26
+ assert_line :float, type: "float", default: 123.00000001
27
+ assert_line :real, type: "real", default: 123.45
26
28
  # Date and Time
27
- assert_line :date, type: "date", limit: nil, precision: nil, scale: nil, default: "01-01-0001"
28
- assert_line :datetime, type: "datetime", limit: nil, precision: nil, scale: nil, default: "01-01-1753 00:00:00.123"
29
- if connection_dblib_73?
30
- assert_line :datetime2_7, type: "datetime", limit: nil, precision: 7, scale: nil, default: "12-31-9999 23:59:59.9999999"
31
- assert_line :datetime2_3, type: "datetime", limit: nil, precision: 3, scale: nil, default: nil
32
- assert_line :datetime2_1, type: "datetime", limit: nil, precision: 1, scale: nil, default: nil
29
+ assert_line :date, type: "date", default: "01-01-0001"
30
+ assert_line :datetime, type: "datetime", precision: nil, default: "01-01-1753 00:00:00.123"
31
+ if connection_tds_73
32
+ assert_line :datetime2_7, type: "datetime", precision: 7, default: "12-31-9999 23:59:59.9999999"
33
+ assert_line :datetime2_3, type: "datetime", precision: 3
34
+ assert_line :datetime2_1, type: "datetime", precision: 1
33
35
  end
34
- assert_line :smalldatetime, type: "smalldatetime", limit: nil, precision: nil, scale: nil, default: "01-01-1901 15:45:00.0"
35
- if connection_dblib_73?
36
- assert_line :time_7, type: "time", limit: nil, precision: 7, scale: nil, default: "04:20:00.2883215"
37
- assert_line :time_2, type: "time", limit: nil, precision: 2, scale: nil, default: nil
38
- assert_line :time_default, type: "time", limit: nil, precision: 7, scale: nil, default: "15:03:42.0621978"
36
+ assert_line :smalldatetime, type: "smalldatetime", default: "01-01-1901 15:45:00.0"
37
+ if connection_tds_73
38
+ assert_line :time_7, type: "time", precision: 7, default: "04:20:00.2883215"
39
+ assert_line :time_2, type: "time", precision: 2
40
+ assert_line :time_default, type: "time", precision: 7, default: "15:03:42.0621978"
39
41
  end
40
42
  # Character Strings
41
- assert_line :char_10, type: "char", limit: 10, precision: nil, scale: nil, default: "1234567890", collation: nil
42
- assert_line :varchar_50, type: "varchar", limit: 50, precision: nil, scale: nil, default: "test varchar_50", collation: nil
43
- assert_line :varchar_max, type: "varchar_max", limit: nil, precision: nil, scale: nil, default: "test varchar_max", collation: nil
44
- assert_line :text, type: "text_basic", limit: nil, precision: nil, scale: nil, default: "test text", collation: nil
43
+ assert_line :char_10, type: "char", limit: 10, default: "1234567890"
44
+ assert_line :varchar_50, type: "varchar", limit: 50, default: "test varchar_50"
45
+ assert_line :varchar_max, type: "varchar_max", default: "test varchar_max"
46
+ assert_line :text, type: "text_basic", default: "test text"
45
47
  # Unicode Character Strings
46
- assert_line :nchar_10, type: "nchar", limit: 10, precision: nil, scale: nil, default: "12345678åå", collation: nil
47
- assert_line :nvarchar_50, type: "string", limit: 50, precision: nil, scale: nil, default: "test nvarchar_50 åå", collation: nil
48
- assert_line :nvarchar_max, type: "text", limit: nil, precision: nil, scale: nil, default: "test nvarchar_max åå", collation: nil
49
- assert_line :ntext, type: "ntext", limit: nil, precision: nil, scale: nil, default: "test ntext åå", collation: nil
48
+ assert_line :nchar_10, type: "nchar", limit: 10, default: "12345678åå"
49
+ assert_line :nvarchar_50, type: "string", limit: 50, default: "test nvarchar_50 åå"
50
+ assert_line :nvarchar_max, type: "text", default: "test nvarchar_max åå"
51
+ assert_line :ntext, type: "ntext", default: "test ntext åå"
50
52
  # Binary Strings
51
- assert_line :binary_49, type: "binary_basic", limit: 49, precision: nil, scale: nil, default: nil
52
- assert_line :varbinary_49, type: "varbinary", limit: 49, precision: nil, scale: nil, default: nil
53
- assert_line :varbinary_max, type: "binary", limit: nil, precision: nil, scale: nil, default: nil
53
+ assert_line :binary_49, type: "binary_basic", limit: 49
54
+ assert_line :varbinary_49, type: "varbinary", limit: 49
55
+ assert_line :varbinary_max, type: "binary"
54
56
  # Other Data Types
55
- assert_line :uniqueidentifier, type: "uuid", limit: nil, precision: nil, scale: nil, default: -> { "newid()" }
56
- assert_line :timestamp, type: "ss_timestamp", limit: nil, precision: nil, scale: nil, default: nil
57
+ assert_line :uniqueidentifier, type: "uuid", default: -> { "newid()" }
58
+ assert_line :timestamp, type: "ss_timestamp"
57
59
  end
58
60
 
59
61
  it "sst_datatypes_migration" do
60
62
  columns = SSTestDatatypeMigration.columns_hash
61
63
  generate_schema_for_table "sst_datatypes_migration"
64
+
62
65
  # Simple Rails conventions
63
- _(columns["integer_col"].sql_type).must_equal "int(4)"
64
- _(columns["bigint_col"].sql_type).must_equal "bigint(8)"
65
- _(columns["boolean_col"].sql_type).must_equal "bit"
66
- _(columns["decimal_col"].sql_type).must_equal "decimal(18,0)"
67
- _(columns["float_col"].sql_type).must_equal "float"
68
- _(columns["string_col"].sql_type).must_equal "nvarchar(4000)"
69
- _(columns["text_col"].sql_type).must_equal "nvarchar(max)"
70
- _(columns["datetime_col"].sql_type).must_equal "datetime"
71
- _(columns["timestamp_col"].sql_type).must_equal "datetime"
72
- _(columns["time_col"].sql_type).must_equal "time(7)"
73
- _(columns["date_col"].sql_type).must_equal "date"
74
- _(columns["binary_col"].sql_type).must_equal "varbinary(max)"
75
- assert_line :integer_col, type: "integer", limit: nil, precision: nil, scale: nil, default: nil
76
- assert_line :bigint_col, type: "bigint", limit: nil, precision: nil, scale: nil, default: nil
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: nil, default: nil
79
- assert_line :float_col, type: "float", limit: nil, precision: nil, scale: nil, default: nil
80
- assert_line :string_col, type: "string", limit: nil, precision: nil, scale: nil, default: nil
81
- assert_line :text_col, type: "text", limit: nil, precision: nil, scale: nil, default: nil
82
- assert_line :datetime_col, type: "datetime", limit: nil, precision: nil, scale: nil, default: nil
83
- assert_line :timestamp_col, type: "datetime", limit: nil, precision: nil, scale: nil, default: nil
84
- assert_line :time_col, type: "time", limit: nil, precision: 7, scale: nil, default: nil
85
- assert_line :date_col, type: "date", limit: nil, precision: nil, scale: nil, default: nil
86
- assert_line :binary_col, type: "binary", limit: nil, precision: nil, scale: nil, default: nil
66
+ _(columns["integer_col"].sql_type).must_equal "int(4)"
67
+ _(columns["bigint_col"].sql_type).must_equal "bigint(8)"
68
+ _(columns["boolean_col"].sql_type).must_equal "bit"
69
+ _(columns["decimal_col"].sql_type).must_equal "decimal(18,0)"
70
+ _(columns["float_col"].sql_type).must_equal "float"
71
+ _(columns["string_col"].sql_type).must_equal "nvarchar(4000)"
72
+ _(columns["text_col"].sql_type).must_equal "nvarchar(max)"
73
+ _(columns["datetime_nil_precision_col"].sql_type).must_equal "datetime"
74
+ _(columns["datetime_col"].sql_type).must_equal "datetime2(6)"
75
+ _(columns["timestamp_col"].sql_type).must_equal "datetime2(6)"
76
+ _(columns["time_col"].sql_type).must_equal "time(7)"
77
+ _(columns["date_col"].sql_type).must_equal "date"
78
+ _(columns["binary_col"].sql_type).must_equal "varbinary(max)"
79
+
80
+ assert_line :integer_col, type: "integer"
81
+ assert_line :bigint_col, type: "bigint"
82
+ assert_line :boolean_col, type: "boolean"
83
+ assert_line :decimal_col, type: "decimal", precision: 18
84
+ assert_line :float_col, type: "float"
85
+ assert_line :string_col, type: "string"
86
+ assert_line :text_col, type: "text"
87
+ assert_line :datetime_nil_precision_col, type: "datetime", precision: nil
88
+ assert_line :datetime_col, type: "datetime"
89
+ assert_line :datetime_col, type: "datetime"
90
+ assert_line :timestamp_col, type: "datetime"
91
+ assert_line :time_col, type: "time", precision: 7
92
+ assert_line :date_col, type: "date"
93
+ assert_line :binary_col, type: "binary"
94
+
87
95
  # Our type methods.
88
- _(columns["real_col"].sql_type).must_equal "real"
89
- _(columns["money_col"].sql_type).must_equal "money"
90
- _(columns["smalldatetime_col"].sql_type).must_equal "smalldatetime"
91
- _(columns["datetime2_col"].sql_type).must_equal "datetime2(7)"
92
- _(columns["datetimeoffset"].sql_type).must_equal "datetimeoffset(7)"
93
- _(columns["smallmoney_col"].sql_type).must_equal "smallmoney"
94
- _(columns["char_col"].sql_type).must_equal "char(1)"
95
- _(columns["varchar_col"].sql_type).must_equal "varchar(8000)"
96
- _(columns["text_basic_col"].sql_type).must_equal "text"
97
- _(columns["nchar_col"].sql_type).must_equal "nchar(1)"
98
- _(columns["ntext_col"].sql_type).must_equal "ntext"
99
- _(columns["binary_basic_col"].sql_type).must_equal "binary(1)"
100
- _(columns["varbinary_col"].sql_type).must_equal "varbinary(8000)"
101
- _(columns["uuid_col"].sql_type).must_equal "uniqueidentifier"
102
- _(columns["sstimestamp_col"].sql_type).must_equal "timestamp"
103
- _(columns["json_col"].sql_type).must_equal "nvarchar(max)"
104
- assert_line :real_col, type: "real", limit: nil, precision: nil, scale: nil, default: nil
105
- assert_line :money_col, type: "money", limit: nil, precision: 19, scale: 4, default: nil
106
- assert_line :smalldatetime_col, type: "smalldatetime", limit: nil, precision: nil, scale: nil, default: nil
107
- assert_line :datetime2_col, type: "datetime", limit: nil, precision: 7, scale: nil, default: nil
108
- assert_line :datetimeoffset, type: "datetimeoffset", limit: nil, precision: 7, scale: nil, default: nil
109
- assert_line :smallmoney_col, type: "smallmoney", limit: nil, precision: 10, scale: 4, default: nil
110
- assert_line :char_col, type: "char", limit: 1, precision: nil, scale: nil, default: nil
111
- assert_line :varchar_col, type: "varchar", limit: nil, precision: nil, scale: nil, default: nil
112
- assert_line :text_basic_col, type: "text_basic", limit: nil, precision: nil, scale: nil, default: nil
113
- assert_line :nchar_col, type: "nchar", limit: 1, precision: nil, scale: nil, default: nil
114
- assert_line :ntext_col, type: "ntext", limit: nil, precision: nil, scale: nil, default: nil
115
- assert_line :binary_basic_col, type: "binary_basic", limit: 1, precision: nil, scale: nil, default: nil
116
- assert_line :varbinary_col, type: "varbinary", limit: nil, precision: nil, scale: nil, default: nil
117
- assert_line :uuid_col, type: "uuid", limit: nil, precision: nil, scale: nil, default: nil
118
- assert_line :sstimestamp_col, type: "ss_timestamp", limit: nil, precision: nil, scale: nil, default: nil
119
- assert_line :json_col, type: "text", limit: nil, precision: nil, scale: nil, default: nil
96
+ _(columns["real_col"].sql_type).must_equal "real"
97
+ _(columns["money_col"].sql_type).must_equal "money"
98
+ _(columns["smalldatetime_col"].sql_type).must_equal "smalldatetime"
99
+ _(columns["datetime2_col"].sql_type).must_equal "datetime2(7)"
100
+ _(columns["datetimeoffset"].sql_type).must_equal "datetimeoffset(7)"
101
+ _(columns["smallmoney_col"].sql_type).must_equal "smallmoney"
102
+ _(columns["char_col"].sql_type).must_equal "char(1)"
103
+ _(columns["varchar_col"].sql_type).must_equal "varchar(8000)"
104
+ _(columns["text_basic_col"].sql_type).must_equal "text"
105
+ _(columns["nchar_col"].sql_type).must_equal "nchar(1)"
106
+ _(columns["ntext_col"].sql_type).must_equal "ntext"
107
+ _(columns["binary_basic_col"].sql_type).must_equal "binary(1)"
108
+ _(columns["binary_basic_16_col"].sql_type).must_equal "binary(16)"
109
+ _(columns["varbinary_col"].sql_type).must_equal "varbinary(8000)"
110
+ _(columns["uuid_col"].sql_type).must_equal "uniqueidentifier"
111
+ _(columns["sstimestamp_col"].sql_type).must_equal "timestamp"
112
+ _(columns["json_col"].sql_type).must_equal "nvarchar(max)"
113
+
114
+ assert_line :real_col, type: "real"
115
+ assert_line :money_col, type: "money", precision: 19, scale: 4
116
+ assert_line :smalldatetime_col, type: "smalldatetime"
117
+ assert_line :datetime2_col, type: "datetime", precision: 7
118
+ assert_line :datetimeoffset, type: "datetimeoffset", precision: 7
119
+ assert_line :smallmoney_col, type: "smallmoney", precision: 10, scale: 4
120
+ assert_line :char_col, type: "char", limit: 1
121
+ assert_line :varchar_col, type: "varchar"
122
+ assert_line :text_basic_col, type: "text_basic"
123
+ assert_line :nchar_col, type: "nchar", limit: 1
124
+ assert_line :ntext_col, type: "ntext"
125
+ assert_line :binary_basic_col, type: "binary_basic", limit: 1
126
+ assert_line :binary_basic_16_col, type: "binary_basic", limit: 16
127
+ assert_line :varbinary_col, type: "varbinary"
128
+ assert_line :uuid_col, type: "uuid"
129
+ assert_line :sstimestamp_col, type: "ss_timestamp", null: false
130
+ assert_line :json_col, type: "text"
120
131
  end
121
132
 
122
133
  it "dump column collation" do
123
134
  generate_schema_for_table('sst_string_collation')
124
135
 
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"
136
+ assert_line :string_without_collation, type: "string"
137
+ assert_line :string_default_collation, type: "varchar"
138
+ assert_line :string_with_collation, type: "varchar", collation: "SQL_Latin1_General_CP1_CS_AS"
139
+ assert_line :varchar_with_collation, type: "varchar", collation: "SQL_Latin1_General_CP1_CS_AS"
129
140
  end
130
141
 
131
142
  # Special Cases
@@ -133,15 +144,16 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
133
144
  it "honor nonstandard primary keys" do
134
145
  generate_schema_for_table("movies") do |output|
135
146
  match = output.match(%r{create_table "movies"(.*)do})
136
- assert_not_nil(match, "nonstandardpk table not found")
147
+ assert_not_nil(match, "non-standard primary key table not found")
137
148
  assert_match %r(primary_key: "movieid"), match[1], "non-standard primary key not preserved"
138
149
  end
139
150
  end
140
151
 
141
152
  it "no id with model driven primary key" do
142
153
  output = generate_schema_for_table "sst_no_pk_data"
154
+
143
155
  _(output).must_match %r{create_table "sst_no_pk_data".*id:\sfalse.*do}
144
- assert_line :name, type: "string", limit: nil, default: nil, collation: nil
156
+ assert_line :name, type: "string"
145
157
  end
146
158
 
147
159
  it "dumps field with unique key constraints only once" do
@@ -150,13 +162,35 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
150
162
  _(output.scan('t.integer "unique_field"').length).must_equal(1)
151
163
  end
152
164
 
165
+ it "schemas are dumped and tables names only include non-default schema" do
166
+ stream = StringIO.new
167
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream)
168
+ generated_schema = stream.string
169
+
170
+ # Only generate non-default schemas. Default schema is 'dbo'.
171
+ assert_not_includes generated_schema, 'create_schema "dbo"'
172
+ assert_not_includes generated_schema, 'create_schema "db_owner"'
173
+ assert_not_includes generated_schema, 'create_schema "INFORMATION_SCHEMA"'
174
+ assert_not_includes generated_schema, 'create_schema "sys"'
175
+ assert_not_includes generated_schema, 'create_schema "guest"'
176
+ assert_includes generated_schema, 'create_schema "test"'
177
+ assert_includes generated_schema, 'create_schema "test2"'
178
+
179
+ # Only non-default schemas should be included in table names. Default schema is 'dbo'.
180
+ assert_includes generated_schema, 'create_table "accounts"'
181
+ assert_includes generated_schema, 'create_table "test.aliens"'
182
+ assert_includes generated_schema, 'create_table "test2.sst_schema_test_multiple_schema"'
183
+ end
184
+
153
185
  private
154
186
 
155
187
  def generate_schema_for_table(*table_names)
156
- require "stringio"
157
- stream = StringIO.new
188
+ previous_ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
158
189
  ActiveRecord::SchemaDumper.ignore_tables = all_tables - table_names
159
- ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
190
+
191
+ stream = StringIO.new
192
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream)
193
+
160
194
  @generated_schema = stream.string
161
195
  yield @generated_schema if block_given?
162
196
  @schema_lines = Hash.new
@@ -167,21 +201,27 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
167
201
  @schema_lines[Regexp.last_match[1]] = SchemaLine.new(line)
168
202
  end
169
203
  @generated_schema
204
+ ensure
205
+ ActiveRecord::SchemaDumper.ignore_tables = previous_ignore_tables
170
206
  end
171
207
 
172
208
  def line(column_name)
173
209
  @schema_lines[column_name.to_s]
174
210
  end
175
211
 
176
- def assert_line(column_name, options = {})
212
+ def assert_line(column_name, expected_options = {})
177
213
  line = line(column_name)
178
214
  assert line, "Could not find line with column name: #{column_name.inspect} in schema:\n#{schema}"
179
215
 
180
- [:type, :limit, :precision, :scale, :collation, :default].each do |key|
181
- next unless options.key?(key)
216
+ # Check that the expected and actual option keys.
217
+ expected_options_keys = expected_options.keys
218
+ expected_options_keys.delete(:type)
219
+ _(expected_options_keys.sort).must_equal (line.options.keys.sort), "For column '#{column_name}' expected schema options and actual schema options do not match."
182
220
 
221
+ # Check the expected and actual option values.
222
+ expected_options.each do |key, expected|
183
223
  actual = key == :type ? line.send(:type_method) : line.send(key)
184
- expected = options[key]
224
+
185
225
  message = "#{key.to_s.titleize} of #{expected.inspect} not found in:\n#{line}"
186
226
 
187
227
  if expected.nil?
@@ -207,7 +247,13 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
207
247
  :options
208
248
 
209
249
  def self.option(method_name)
210
- define_method(method_name) { options.present? ? options[method_name.to_sym] : nil }
250
+ define_method(method_name) do
251
+ if options.key?(method_name.to_sym)
252
+ options[method_name.to_sym]
253
+ else
254
+ throw "Schema line does include the '#{method_name}' option!"
255
+ end
256
+ end
211
257
  end
212
258
 
213
259
  def initialize(line)
@@ -220,6 +266,7 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
220
266
  option :scale
221
267
  option :default
222
268
  option :collation
269
+ option :null
223
270
 
224
271
  def to_s
225
272
  line.squish
@@ -234,6 +281,7 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
234
281
  def parse_line
235
282
  _all, type_method, col_name, options = @line.match(LINE_PARSER).to_a
236
283
  options = parse_options(options)
284
+
237
285
  [type_method, col_name, options]
238
286
  end
239
287
 
@@ -243,8 +291,6 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
243
291
  else
244
292
  {}
245
293
  end
246
- rescue SyntaxError
247
- {}
248
294
  end
249
295
  end
250
296
  end