activerecord-sqlserver-adapter 6.1.2.1 → 7.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.devcontainer/Dockerfile +30 -0
- data/.devcontainer/boot.sh +22 -0
- data/.devcontainer/devcontainer.json +38 -0
- data/.devcontainer/docker-compose.yml +42 -0
- data/.github/workflows/ci.yml +7 -4
- data/.gitignore +3 -1
- data/CHANGELOG.md +19 -42
- data/Dockerfile.ci +3 -3
- data/Gemfile +6 -1
- data/MIT-LICENSE +1 -1
- data/README.md +113 -27
- data/RUNNING_UNIT_TESTS.md +27 -14
- data/Rakefile +2 -6
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +3 -3
- data/appveyor.yml +4 -6
- data/docker-compose.ci.yml +2 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/abstract_adapter.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +6 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -23
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +10 -7
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +12 -2
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +24 -16
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -31
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +143 -155
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +5 -5
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +57 -56
- data/lib/active_record/connection_adapters/sqlserver/savepoints.rb +26 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +14 -12
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +11 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +213 -57
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +13 -2
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +4 -6
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +19 -1
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +21 -10
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +187 -187
- data/lib/active_record/connection_adapters/sqlserver_column.rb +1 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +42 -33
- data/lib/arel/visitors/sqlserver.rb +77 -34
- data/test/cases/active_schema_test_sqlserver.rb +127 -0
- data/test/cases/adapter_test_sqlserver.rb +114 -26
- data/test/cases/coerced_tests.rb +1121 -340
- data/test/cases/column_test_sqlserver.rb +67 -64
- data/test/cases/connection_test_sqlserver.rb +3 -6
- data/test/cases/dbconsole.rb +19 -0
- data/test/cases/disconnected_test_sqlserver.rb +8 -5
- data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
- data/test/cases/enum_test_sqlserver.rb +49 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +9 -5
- data/test/cases/fetch_test_sqlserver.rb +19 -0
- data/test/cases/helper_sqlserver.rb +11 -5
- data/test/cases/index_test_sqlserver.rb +8 -6
- data/test/cases/json_test_sqlserver.rb +1 -1
- data/test/cases/lateral_test_sqlserver.rb +2 -2
- data/test/cases/migration_test_sqlserver.rb +19 -1
- data/test/cases/optimizer_hints_test_sqlserver.rb +21 -12
- data/test/cases/pessimistic_locking_test_sqlserver.rb +8 -7
- data/test/cases/primary_keys_test_sqlserver.rb +2 -2
- data/test/cases/rake_test_sqlserver.rb +10 -5
- data/test/cases/schema_dumper_test_sqlserver.rb +155 -109
- data/test/cases/schema_test_sqlserver.rb +64 -1
- data/test/cases/showplan_test_sqlserver.rb +7 -7
- data/test/cases/specific_schema_test_sqlserver.rb +17 -13
- data/test/cases/transaction_test_sqlserver.rb +13 -8
- data/test/cases/trigger_test_sqlserver.rb +20 -0
- data/test/cases/utils_test_sqlserver.rb +2 -2
- data/test/cases/uuid_test_sqlserver.rb +8 -0
- data/test/cases/view_test_sqlserver.rb +58 -0
- data/test/config.yml +1 -2
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +1 -1
- data/test/models/sqlserver/alien.rb +5 -0
- data/test/models/sqlserver/table_with_spaces.rb +5 -0
- data/test/models/sqlserver/trigger.rb +8 -0
- data/test/schema/sqlserver_specific_schema.rb +54 -6
- data/test/support/coerceable_test_sqlserver.rb +4 -4
- data/test/support/connection_reflection.rb +3 -9
- data/test/support/core_ext/query_cache.rb +7 -1
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
- data/test/support/query_assertions.rb +49 -0
- data/test/support/rake_helpers.rb +3 -1
- data/test/support/table_definition_sqlserver.rb +24 -0
- data/test/support/test_in_memory_oltp.rb +2 -2
- metadata +41 -17
- data/lib/active_record/sqlserver_base.rb +0 -18
- data/test/cases/scratchpad_test_sqlserver.rb +0 -8
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
- data/test/support/sql_counter_sqlserver.rb +0 -29
@@ -7,55 +7,64 @@ class OptimizerHitsTestSQLServer < ActiveRecord::TestCase
|
|
7
7
|
fixtures :companies
|
8
8
|
|
9
9
|
it "apply optimizations" do
|
10
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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.
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
14
|
-
assert_line :
|
15
|
-
assert_line :
|
16
|
-
assert_line :
|
17
|
-
assert_line :
|
18
|
-
assert_line :
|
19
|
-
assert_line :
|
20
|
-
assert_line :
|
21
|
-
assert_line :
|
22
|
-
assert_line :
|
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",
|
25
|
-
assert_line :real, type: "real",
|
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",
|
28
|
-
assert_line :datetime, type: "datetime",
|
29
|
-
if
|
30
|
-
assert_line :datetime2_7,
|
31
|
-
assert_line :datetime2_3,
|
32
|
-
assert_line :datetime2_1,
|
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,
|
35
|
-
if
|
36
|
-
assert_line :time_7,
|
37
|
-
assert_line :time_2,
|
38
|
-
assert_line :time_default,
|
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",
|
42
|
-
assert_line :varchar_50, type: "varchar",
|
43
|
-
assert_line :varchar_max, type: "varchar_max",
|
44
|
-
assert_line :text, type: "text_basic",
|
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",
|
47
|
-
assert_line :nvarchar_50, type: "string",
|
48
|
-
assert_line :nvarchar_max, type: "text",
|
49
|
-
assert_line :ntext, type: "ntext",
|
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",
|
52
|
-
assert_line :varbinary_49, type: "varbinary",
|
53
|
-
assert_line :varbinary_max, type: "binary"
|
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",
|
56
|
-
assert_line :timestamp, type: "ss_timestamp"
|
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
|
64
|
-
_(columns["bigint_col"].sql_type).must_equal
|
65
|
-
_(columns["boolean_col"].sql_type).must_equal
|
66
|
-
_(columns["decimal_col"].sql_type).must_equal
|
67
|
-
_(columns["float_col"].sql_type).must_equal
|
68
|
-
_(columns["string_col"].sql_type).must_equal
|
69
|
-
_(columns["text_col"].sql_type).must_equal
|
70
|
-
_(columns["
|
71
|
-
_(columns["
|
72
|
-
_(columns["
|
73
|
-
_(columns["
|
74
|
-
_(columns["
|
75
|
-
|
76
|
-
|
77
|
-
assert_line :
|
78
|
-
assert_line :
|
79
|
-
assert_line :
|
80
|
-
assert_line :
|
81
|
-
assert_line :
|
82
|
-
assert_line :
|
83
|
-
assert_line :
|
84
|
-
assert_line :
|
85
|
-
assert_line :
|
86
|
-
assert_line :
|
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
|
89
|
-
_(columns["money_col"].sql_type).must_equal
|
90
|
-
_(columns["smalldatetime_col"].sql_type).must_equal
|
91
|
-
_(columns["datetime2_col"].sql_type).must_equal
|
92
|
-
_(columns["datetimeoffset"].sql_type).must_equal
|
93
|
-
_(columns["smallmoney_col"].sql_type).must_equal
|
94
|
-
_(columns["char_col"].sql_type).must_equal
|
95
|
-
_(columns["varchar_col"].sql_type).must_equal
|
96
|
-
_(columns["text_basic_col"].sql_type).must_equal
|
97
|
-
_(columns["nchar_col"].sql_type).must_equal
|
98
|
-
_(columns["ntext_col"].sql_type).must_equal
|
99
|
-
_(columns["binary_basic_col"].sql_type).must_equal
|
100
|
-
_(columns["
|
101
|
-
_(columns["
|
102
|
-
_(columns["
|
103
|
-
_(columns["
|
104
|
-
|
105
|
-
|
106
|
-
assert_line :
|
107
|
-
assert_line :
|
108
|
-
assert_line :
|
109
|
-
assert_line :
|
110
|
-
assert_line :
|
111
|
-
assert_line :
|
112
|
-
assert_line :
|
113
|
-
assert_line :
|
114
|
-
assert_line :
|
115
|
-
assert_line :
|
116
|
-
assert_line :
|
117
|
-
assert_line :
|
118
|
-
assert_line :
|
119
|
-
assert_line :
|
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"
|
126
|
-
assert_line :string_default_collation, type: "varchar"
|
127
|
-
assert_line :string_with_collation,
|
128
|
-
assert_line :varchar_with_collation,
|
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, "
|
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"
|
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
|
-
|
157
|
-
stream = StringIO.new
|
188
|
+
previous_ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
|
158
189
|
ActiveRecord::SchemaDumper.ignore_tables = all_tables - table_names
|
159
|
-
|
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,
|
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
|
-
|
181
|
-
|
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
|
-
|
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)
|
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
|