activerecord-jdbcsqlserver-adapter 50.0.0

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 (148) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.travis.yml +27 -0
  4. data/CHANGELOG.md +124 -0
  5. data/CODE_OF_CONDUCT.md +31 -0
  6. data/Dockerfile +20 -0
  7. data/Gemfile +77 -0
  8. data/Guardfile +29 -0
  9. data/MIT-LICENSE +20 -0
  10. data/RAILS5-TODO.md +5 -0
  11. data/README.md +93 -0
  12. data/RUNNING_UNIT_TESTS.md +96 -0
  13. data/Rakefile +46 -0
  14. data/VERSION +1 -0
  15. data/activerecord-jdbcsqlserver-adapter.gemspec +21 -0
  16. data/appveyor.yml +39 -0
  17. data/docker-compose.ci.yml +11 -0
  18. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +27 -0
  19. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +25 -0
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/date_time.rb +58 -0
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +47 -0
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +4 -0
  23. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
  24. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +362 -0
  25. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +67 -0
  26. data/lib/active_record/connection_adapters/sqlserver/errors.rb +7 -0
  27. data/lib/active_record/connection_adapters/sqlserver/jdbc_overrides.rb +192 -0
  28. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +99 -0
  29. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +34 -0
  30. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +16 -0
  31. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +517 -0
  32. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +66 -0
  33. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +66 -0
  34. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +22 -0
  35. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +20 -0
  36. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +112 -0
  37. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +64 -0
  38. data/lib/active_record/connection_adapters/sqlserver/type.rb +49 -0
  39. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +19 -0
  40. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +21 -0
  41. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +15 -0
  42. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +32 -0
  43. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +30 -0
  44. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +61 -0
  45. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +71 -0
  46. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +17 -0
  47. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +23 -0
  48. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +21 -0
  49. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +19 -0
  50. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +15 -0
  51. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +11 -0
  52. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +25 -0
  53. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +19 -0
  54. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +15 -0
  55. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +25 -0
  56. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +29 -0
  57. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +12 -0
  58. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +19 -0
  59. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +68 -0
  60. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +93 -0
  61. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +19 -0
  62. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +25 -0
  63. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +21 -0
  64. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +12 -0
  65. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +19 -0
  66. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +26 -0
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +24 -0
  68. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +36 -0
  69. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +26 -0
  70. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +24 -0
  71. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +26 -0
  72. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +24 -0
  73. data/lib/active_record/connection_adapters/sqlserver/utils.rb +146 -0
  74. data/lib/active_record/connection_adapters/sqlserver/version.rb +11 -0
  75. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +445 -0
  76. data/lib/active_record/connection_adapters/sqlserver_column.rb +28 -0
  77. data/lib/active_record/jdbc_sqlserver_connection_methods.rb +31 -0
  78. data/lib/active_record/sqlserver_base.rb +16 -0
  79. data/lib/active_record/tasks/sqlserver_database_tasks.rb +131 -0
  80. data/lib/activerecord-jdbcsqlserver-adapter.rb +24 -0
  81. data/lib/activerecord-sqlserver-adapter.rb +1 -0
  82. data/lib/arel/visitors/sqlserver.rb +205 -0
  83. data/lib/arel_sqlserver.rb +3 -0
  84. data/test/appveyor/dbsetup.ps1 +27 -0
  85. data/test/appveyor/dbsetup.sql +11 -0
  86. data/test/bin/wait-for.sh +79 -0
  87. data/test/cases/adapter_test_sqlserver.rb +430 -0
  88. data/test/cases/coerced_tests.rb +845 -0
  89. data/test/cases/column_test_sqlserver.rb +812 -0
  90. data/test/cases/connection_test_sqlserver.rb +71 -0
  91. data/test/cases/execute_procedure_test_sqlserver.rb +45 -0
  92. data/test/cases/fetch_test_sqlserver.rb +57 -0
  93. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +76 -0
  94. data/test/cases/helper_sqlserver.rb +44 -0
  95. data/test/cases/index_test_sqlserver.rb +47 -0
  96. data/test/cases/json_test_sqlserver.rb +32 -0
  97. data/test/cases/migration_test_sqlserver.rb +61 -0
  98. data/test/cases/order_test_sqlserver.rb +147 -0
  99. data/test/cases/pessimistic_locking_test_sqlserver.rb +94 -0
  100. data/test/cases/rake_test_sqlserver.rb +169 -0
  101. data/test/cases/schema_dumper_test_sqlserver.rb +234 -0
  102. data/test/cases/schema_test_sqlserver.rb +54 -0
  103. data/test/cases/scratchpad_test_sqlserver.rb +8 -0
  104. data/test/cases/showplan_test_sqlserver.rb +65 -0
  105. data/test/cases/specific_schema_test_sqlserver.rb +180 -0
  106. data/test/cases/transaction_test_sqlserver.rb +91 -0
  107. data/test/cases/utils_test_sqlserver.rb +129 -0
  108. data/test/cases/uuid_test_sqlserver.rb +49 -0
  109. data/test/config.yml +38 -0
  110. data/test/debug.rb +14 -0
  111. data/test/fixtures/1px.gif +0 -0
  112. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
  113. data/test/models/sqlserver/booking.rb +3 -0
  114. data/test/models/sqlserver/customers_view.rb +3 -0
  115. data/test/models/sqlserver/datatype.rb +3 -0
  116. data/test/models/sqlserver/datatype_migration.rb +8 -0
  117. data/test/models/sqlserver/dollar_table_name.rb +3 -0
  118. data/test/models/sqlserver/dot_table_name.rb +3 -0
  119. data/test/models/sqlserver/edge_schema.rb +13 -0
  120. data/test/models/sqlserver/fk_has_fk.rb +3 -0
  121. data/test/models/sqlserver/fk_has_pk.rb +3 -0
  122. data/test/models/sqlserver/natural_pk_data.rb +4 -0
  123. data/test/models/sqlserver/natural_pk_int_data.rb +3 -0
  124. data/test/models/sqlserver/no_pk_data.rb +3 -0
  125. data/test/models/sqlserver/object_default.rb +3 -0
  126. data/test/models/sqlserver/quoted_table.rb +7 -0
  127. data/test/models/sqlserver/quoted_view_1.rb +3 -0
  128. data/test/models/sqlserver/quoted_view_2.rb +3 -0
  129. data/test/models/sqlserver/sst_memory.rb +3 -0
  130. data/test/models/sqlserver/string_default.rb +3 -0
  131. data/test/models/sqlserver/string_defaults_big_view.rb +3 -0
  132. data/test/models/sqlserver/string_defaults_view.rb +3 -0
  133. data/test/models/sqlserver/tinyint_pk.rb +3 -0
  134. data/test/models/sqlserver/upper.rb +3 -0
  135. data/test/models/sqlserver/uppered.rb +3 -0
  136. data/test/models/sqlserver/uuid.rb +3 -0
  137. data/test/schema/datatypes/2012.sql +55 -0
  138. data/test/schema/enable-in-memory-oltp.sql +81 -0
  139. data/test/schema/sqlserver_specific_schema.rb +238 -0
  140. data/test/support/coerceable_test_sqlserver.rb +49 -0
  141. data/test/support/connection_reflection.rb +34 -0
  142. data/test/support/load_schema_sqlserver.rb +29 -0
  143. data/test/support/minitest_sqlserver.rb +1 -0
  144. data/test/support/paths_sqlserver.rb +50 -0
  145. data/test/support/rake_helpers.rb +41 -0
  146. data/test/support/sql_counter_sqlserver.rb +28 -0
  147. data/test/support/test_in_memory_oltp.rb +15 -0
  148. metadata +310 -0
@@ -0,0 +1,94 @@
1
+ require 'cases/helper_sqlserver'
2
+ require 'models/person'
3
+ require 'models/reader'
4
+
5
+ class PessimisticLockingTestSQLServer < ActiveRecord::TestCase
6
+
7
+ fixtures :people, :readers
8
+
9
+ before do
10
+ Person.columns
11
+ Reader.columns
12
+ end
13
+
14
+ it 'uses with updlock by default' do
15
+ assert_sql %r|SELECT \[people\]\.\* FROM \[people\] WITH\(UPDLOCK\)| do
16
+ Person.lock(true).to_a.must_equal Person.all.to_a
17
+ end
18
+ end
19
+
20
+ describe 'For simple finds with default lock option' do
21
+
22
+ it 'lock with simple find' do
23
+ assert_nothing_raised do
24
+ Person.transaction do
25
+ Person.lock(true).find(1).must_equal Person.find(1)
26
+ end
27
+ end
28
+ end
29
+
30
+ it 'lock with scoped find' do
31
+ assert_nothing_raised do
32
+ Person.transaction do
33
+ Person.lock(true).scoping do
34
+ Person.find(1).must_equal Person.find(1)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ it 'lock with eager find' do
41
+ assert_nothing_raised do
42
+ Person.transaction do
43
+ person = Person.lock(true).includes(:readers).find(1)
44
+ person.must_equal Person.find(1)
45
+ end
46
+ end
47
+ end
48
+
49
+ it 'reload with lock when #lock! called' do
50
+ assert_nothing_raised do
51
+ Person.transaction do
52
+ person = Person.find 1
53
+ old, person.first_name = person.first_name, 'fooman'
54
+ person.lock!
55
+ assert_equal old, person.first_name
56
+ end
57
+ end
58
+ end
59
+
60
+ it 'can add a custom lock directive' do
61
+ assert_sql %r|SELECT \[people\]\.\* FROM \[people\] WITH\(HOLDLOCK, ROWLOCK\)| do
62
+ Person.lock('WITH(HOLDLOCK, ROWLOCK)').load
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ describe 'For paginated finds' do
69
+
70
+ before do
71
+ Person.delete_all
72
+ 20.times { |n| Person.create!(first_name: "Thing_#{n}") }
73
+ end
74
+
75
+ it 'copes with eager loading un-locked paginated' do
76
+ if defined? JRUBY_VERSION
77
+ 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 \? ROWS FETCH NEXT \? ROWS ONLY/
78
+ else
79
+ 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/
80
+ end
81
+ loader_sql = /SELECT.*FROM \[people\] WITH\(UPDLOCK\).*WHERE \[people\]\.\[id\] IN/
82
+ assert_sql(eager_ids_sql, loader_sql) do
83
+ people = Person.lock(true).limit(5).offset(10).includes(:readers).references(:readers).to_a
84
+ people[0].first_name.must_equal 'Thing_10'
85
+ people[1].first_name.must_equal 'Thing_11'
86
+ people[2].first_name.must_equal 'Thing_12'
87
+ people[3].first_name.must_equal 'Thing_13'
88
+ people[4].first_name.must_equal 'Thing_14'
89
+ end
90
+ end
91
+
92
+ end
93
+
94
+ end
@@ -0,0 +1,169 @@
1
+ require 'cases/helper_sqlserver'
2
+
3
+ class SQLServerRakeTest < ActiveRecord::TestCase
4
+
5
+ self.use_transactional_tests = false
6
+
7
+ cattr_accessor :azure_skip
8
+ self.azure_skip = connection_sqlserver_azure?
9
+
10
+ let(:db_tasks) { ActiveRecord::Tasks::DatabaseTasks }
11
+ let(:new_database) { 'activerecord_unittest_tasks' }
12
+ let(:default_configuration) { ARTest.connection_config['arunit'] }
13
+ let(:configuration) { default_configuration.merge('database' => new_database) }
14
+
15
+ before { skip 'on azure' if azure_skip }
16
+ before { disconnect! unless azure_skip }
17
+ after { reconnect unless azure_skip }
18
+
19
+ private
20
+
21
+ def disconnect!
22
+ connection.disconnect!
23
+ end
24
+
25
+ def reconnect
26
+ config = default_configuration
27
+ if connection_sqlserver_azure?
28
+ ActiveRecord::Base.establish_connection(config.merge('database' => 'master'))
29
+ connection.drop_database(new_database) rescue nil
30
+ disconnect!
31
+ ActiveRecord::Base.establish_connection(config)
32
+ else
33
+ ActiveRecord::Base.establish_connection(config)
34
+ connection.drop_database(new_database) rescue nil
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ class SQLServerRakeCreateTest < SQLServerRakeTest
41
+
42
+ self.azure_skip = false
43
+
44
+ it 'establishes connection to database after create ' do
45
+ quietly { db_tasks.create configuration }
46
+ connection.current_database.must_equal(new_database)
47
+ end
48
+
49
+ it 'creates database with default collation' do
50
+ quietly { db_tasks.create configuration }
51
+ connection.collation.must_equal 'SQL_Latin1_General_CP1_CI_AS'
52
+ end
53
+
54
+ it 'creates database with given collation' do
55
+ quietly { db_tasks.create configuration.merge('collation' => 'Latin1_General_CI_AS') }
56
+ connection.collation.must_equal 'Latin1_General_CI_AS'
57
+ end
58
+
59
+ it 'prints error message when database exists' do
60
+ quietly { db_tasks.create configuration }
61
+ message = capture(:stderr) { db_tasks.create configuration }
62
+ message.must_match %r{activerecord_unittest_tasks.*already exists}
63
+ end
64
+
65
+ end
66
+
67
+ class SQLServerRakeDropTest < SQLServerRakeTest
68
+
69
+ self.azure_skip = false
70
+
71
+ it 'drops database and uses master' do
72
+ quietly do
73
+ db_tasks.create configuration
74
+ db_tasks.drop configuration
75
+ end
76
+ connection.current_database.must_equal 'master'
77
+ end
78
+
79
+ it 'prints error message when database does not exist' do
80
+ message = capture(:stderr) { db_tasks.drop configuration.merge('database' => 'doesnotexist') }
81
+ message.must_match %r{'doesnotexist' does not exist}
82
+ end
83
+
84
+ end
85
+
86
+ class SQLServerRakePurgeTest < SQLServerRakeTest
87
+
88
+ before do
89
+ quietly { db_tasks.create(configuration) }
90
+ connection.create_table :users, force: true do |t|
91
+ t.string :name, :email
92
+ t.timestamps null: false
93
+ end
94
+ end
95
+
96
+ it 'clears active connections, drops database, and recreates with established connection' do
97
+ connection.current_database.must_equal(new_database)
98
+ connection.tables.must_include 'users'
99
+ quietly { db_tasks.purge(configuration) }
100
+ connection.current_database.must_equal(new_database)
101
+ connection.tables.wont_include 'users'
102
+ end
103
+
104
+ end
105
+
106
+ class SQLServerRakeCharsetTest < SQLServerRakeTest
107
+
108
+ before do
109
+ quietly { db_tasks.create(configuration) }
110
+ end
111
+
112
+ it 'retrieves charset' do
113
+ db_tasks.charset(configuration).must_equal 'iso_1'
114
+ end
115
+
116
+ end
117
+
118
+ class SQLServerRakeCollationTest < SQLServerRakeTest
119
+
120
+ before do
121
+ quietly { db_tasks.create(configuration) }
122
+ end
123
+
124
+ it 'retrieves collation' do
125
+ db_tasks.collation(configuration).must_equal 'SQL_Latin1_General_CP1_CI_AS'
126
+ end
127
+
128
+ end
129
+
130
+ class SQLServerRakeStructureDumpLoadTest < SQLServerRakeTest
131
+
132
+ let(:filename) { File.join ARTest::SQLServer.migrations_root, 'structure.sql' }
133
+ let(:filedata) { File.read(filename) }
134
+
135
+ before do
136
+ quietly { db_tasks.create(configuration) }
137
+ connection.create_table :users, force: true do |t|
138
+ t.string :name, :email
139
+ t.text :background1
140
+ t.text_basic :background2
141
+ t.timestamps null: false
142
+ end
143
+ end
144
+
145
+ after do
146
+ FileUtils.rm_rf(filename)
147
+ end
148
+
149
+ it 'dumps structure and accounts for defncopy oddities' do
150
+ skip 'debug defncopy on windows later' if host_windows?
151
+ quietly { db_tasks.structure_dump configuration, filename }
152
+ filedata.wont_match %r{\AUSE.*\z}
153
+ filedata.wont_match %r{\AGO.*\z}
154
+ filedata.must_match %r{email\s+nvarchar\(4000\)}
155
+ filedata.must_match %r{background1\s+nvarchar\(max\)}
156
+ filedata.must_match %r{background2\s+text\s+}
157
+ end
158
+
159
+ it 'can load dumped structure' do
160
+ skip 'debug defncopy on windows later' if host_windows?
161
+ quietly { db_tasks.structure_dump configuration, filename }
162
+ filedata.must_match %r{CREATE TABLE dbo\.users}
163
+ db_tasks.purge(configuration)
164
+ connection.tables.wont_include 'users'
165
+ db_tasks.load_schema configuration, :sql, filename
166
+ connection.tables.must_include 'users'
167
+ end
168
+
169
+ end
@@ -0,0 +1,234 @@
1
+ require 'cases/helper_sqlserver'
2
+
3
+ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
4
+
5
+ before { all_tables }
6
+
7
+ let(:all_tables) { ActiveRecord::Base.connection.tables }
8
+ let(:schema) { @generated_schema }
9
+
10
+ it 'sst_datatypes' do
11
+ generate_schema_for_table 'sst_datatypes'
12
+ assert_line :bigint, type: 'bigint', limit: nil, precision: nil, scale: nil, default: 42
13
+ assert_line :int, type: 'integer', limit: nil, precision: nil, scale: nil, default: 42
14
+ assert_line :smallint, type: 'integer', limit: 2, precision: nil, scale: nil, default: 42
15
+ assert_line :tinyint, type: 'integer', limit: 1, precision: nil, scale: nil, default: 42
16
+ assert_line :bit, type: 'boolean', limit: nil, precision: nil, scale: nil, default: true
17
+ assert_line :decimal_9_2, type: 'decimal', limit: nil, precision: 9, scale: 2, default: 12345.01
18
+ assert_line :numeric_18_0, type: 'decimal', limit: nil, precision: 18, scale: 0, default: 191.0
19
+ assert_line :numeric_36_2, type: 'decimal', limit: nil, precision: 36, scale: 2, default: 12345678901234567890.01
20
+ assert_line :money, type: 'money', limit: nil, precision: 19, scale: 4, default: 4.2
21
+ assert_line :smallmoney, type: 'smallmoney', limit: nil, precision: 10, scale: 4, default: 4.2
22
+ # Approximate Numerics
23
+ assert_line :float, type: 'float', limit: nil, precision: nil, scale: nil, default: 123.00000001
24
+ assert_line :real, type: 'real', limit: nil, precision: nil, scale: nil, default: 123.45
25
+ # Date and Time
26
+ assert_line :date, type: 'date', limit: nil, precision: nil, scale: nil, default: "01-01-0001"
27
+ assert_line :datetime, type: 'datetime', limit: nil, precision: nil, scale: nil, default: "01-01-1753 00:00:00.123"
28
+ if connection_dblib_73?
29
+ assert_line :datetime2_7, type: 'datetime', limit: nil, precision: 7, scale: nil, default: "12-31-9999 23:59:59.9999999"
30
+ assert_line :datetime2_3, type: 'datetime', limit: nil, precision: 3, scale: nil, default: nil
31
+ assert_line :datetime2_1, type: 'datetime', limit: nil, precision: 1, scale: nil, default: nil
32
+ end
33
+ assert_line :smalldatetime, type: 'smalldatetime',limit: nil, precision: nil, scale: nil, default: "01-01-1901 15:45:00.0"
34
+ if connection_dblib_73?
35
+ assert_line :time_7, type: 'time', limit: nil, precision: 7, scale: nil, default: "04:20:00.2883215"
36
+ assert_line :time_2, type: 'time', limit: nil, precision: 2, scale: nil, default: nil
37
+ end
38
+ # Character Strings
39
+ assert_line :char_10, type: 'char', limit: 10, precision: nil, scale: nil, default: "1234567890", collation: nil
40
+ assert_line :varchar_50, type: 'varchar', limit: 50, precision: nil, scale: nil, default: "test varchar_50", collation: nil
41
+ assert_line :varchar_max, type: 'varchar_max', limit: 2147483647, precision: nil, scale: nil, default: "test varchar_max", collation: nil
42
+ assert_line :text, type: 'text_basic', limit: 2147483647, precision: nil, scale: nil, default: "test text", collation: nil
43
+ # Unicode Character Strings
44
+ assert_line :nchar_10, type: 'nchar', limit: 10, precision: nil, scale: nil, default: "12345678åå", collation: nil
45
+ assert_line :nvarchar_50, type: 'string', limit: 50, precision: nil, scale: nil, default: "test nvarchar_50 åå", collation: nil
46
+ assert_line :nvarchar_max, type: 'text', limit: 2147483647, precision: nil, scale: nil, default: "test nvarchar_max åå", collation: nil
47
+ assert_line :ntext, type: 'ntext', limit: 2147483647, precision: nil, scale: nil, default: "test ntext åå", collation: nil
48
+ # Binary Strings
49
+ assert_line :binary_49, type: 'binary_basic', limit: 49, precision: nil, scale: nil, default: nil
50
+ assert_line :varbinary_49, type: 'varbinary', limit: 49, precision: nil, scale: nil, default: nil
51
+ assert_line :varbinary_max, type: 'binary', limit: 2147483647, precision: nil, scale: nil, default: nil
52
+ # Other Data Types
53
+ assert_line :uniqueidentifier, type: 'uuid', limit: nil, precision: nil, scale: nil, default: -> { "newid()" }
54
+ assert_line :timestamp, type: 'ss_timestamp', limit: nil, precision: nil, scale: nil, default: nil
55
+ end
56
+
57
+ it 'sst_datatypes_migration' do
58
+ columns = SSTestDatatypeMigration.columns_hash
59
+ generate_schema_for_table 'sst_datatypes_migration'
60
+ # Simple Rails conventions
61
+ columns['integer_col'].sql_type.must_equal 'int(4)'
62
+ columns['bigint_col'].sql_type.must_equal 'bigint(8)'
63
+ columns['boolean_col'].sql_type.must_equal 'bit'
64
+ columns['decimal_col'].sql_type.must_equal 'decimal(18,0)'
65
+ columns['float_col'].sql_type.must_equal 'float'
66
+ columns['string_col'].sql_type.must_equal 'nvarchar(4000)'
67
+ columns['text_col'].sql_type.must_equal 'nvarchar(max)'
68
+ columns['datetime_col'].sql_type.must_equal 'datetime'
69
+ columns['timestamp_col'].sql_type.must_equal 'datetime'
70
+ columns['time_col'].sql_type.must_equal 'time(7)'
71
+ columns['date_col'].sql_type.must_equal 'date'
72
+ columns['binary_col'].sql_type.must_equal 'varbinary(max)'
73
+ assert_line :integer_col, type: 'integer', limit: nil, precision: nil, scale: nil, default: nil
74
+ assert_line :bigint_col, type: 'bigint', limit: nil, precision: nil, scale: nil, default: nil
75
+ assert_line :boolean_col, type: 'boolean', limit: nil, precision: nil, scale: nil, default: nil
76
+ assert_line :decimal_col, type: 'decimal', limit: nil, precision: 18, scale: 0, default: nil
77
+ assert_line :float_col, type: 'float', limit: nil, precision: nil, scale: nil, default: nil
78
+ assert_line :string_col, type: 'string', limit: nil, precision: nil, scale: nil, default: nil
79
+ assert_line :text_col, type: 'text', limit: 2147483647, precision: nil, scale: nil, default: nil
80
+ assert_line :datetime_col, type: 'datetime', limit: nil, precision: nil, scale: nil, default: nil
81
+ assert_line :timestamp_col, type: 'datetime', limit: nil, precision: nil, scale: nil, default: nil
82
+ assert_line :time_col, type: 'time', limit: nil, precision: 7, scale: nil, default: nil
83
+ assert_line :date_col, type: 'date', limit: nil, precision: nil, scale: nil, default: nil
84
+ assert_line :binary_col, type: 'binary', limit: 2147483647, precision: nil, scale: nil, default: nil
85
+ # Our type methods.
86
+ columns['real_col'].sql_type.must_equal 'real'
87
+ columns['money_col'].sql_type.must_equal 'money'
88
+ columns['smalldatetime_col'].sql_type.must_equal 'smalldatetime'
89
+ columns['datetime2_col'].sql_type.must_equal 'datetime2(7)'
90
+ columns['datetimeoffset'].sql_type.must_equal 'datetimeoffset(7)'
91
+ columns['smallmoney_col'].sql_type.must_equal 'smallmoney'
92
+ columns['char_col'].sql_type.must_equal 'char(1)'
93
+ columns['varchar_col'].sql_type.must_equal 'varchar(8000)'
94
+ columns['text_basic_col'].sql_type.must_equal 'text'
95
+ columns['nchar_col'].sql_type.must_equal 'nchar(1)'
96
+ columns['ntext_col'].sql_type.must_equal 'ntext'
97
+ columns['binary_basic_col'].sql_type.must_equal 'binary(1)'
98
+ columns['varbinary_col'].sql_type.must_equal 'varbinary(8000)'
99
+ columns['uuid_col'].sql_type.must_equal 'uniqueidentifier'
100
+ columns['sstimestamp_col'].sql_type.must_equal 'timestamp'
101
+ columns['json_col'].sql_type.must_equal 'nvarchar(max)'
102
+ assert_line :real_col, type: 'real', limit: nil, precision: nil, scale: nil, default: nil
103
+ assert_line :money_col, type: 'money', limit: nil, precision: 19, scale: 4, default: nil
104
+ assert_line :smalldatetime_col, type: 'smalldatetime', limit: nil, precision: nil, scale: nil, default: nil
105
+ assert_line :datetime2_col, type: 'datetime', limit: nil, precision: 7, scale: nil, default: nil
106
+ assert_line :datetimeoffset, type: 'datetimeoffset', limit: nil, precision: 7, scale: nil, default: nil
107
+ assert_line :smallmoney_col, type: 'smallmoney', limit: nil, precision: 10, scale: 4, default: nil
108
+ assert_line :char_col, type: 'char', limit: 1, precision: nil, scale: nil, default: nil
109
+ assert_line :varchar_col, type: 'varchar', limit: nil, precision: nil, scale: nil, default: nil
110
+ assert_line :text_basic_col, type: 'text_basic', limit: 2147483647, precision: nil, scale: nil, default: nil
111
+ assert_line :nchar_col, type: 'nchar', limit: 1, precision: nil, scale: nil, default: nil
112
+ assert_line :ntext_col, type: 'ntext', limit: 2147483647, precision: nil, scale: nil, default: nil
113
+ assert_line :binary_basic_col, type: 'binary_basic', limit: 1, precision: nil, scale: nil, default: nil
114
+ assert_line :varbinary_col, type: 'varbinary', limit: nil, precision: nil, scale: nil, default: nil
115
+ assert_line :uuid_col, type: 'uuid', limit: nil, precision: nil, scale: nil, default: nil
116
+ assert_line :sstimestamp_col, type: 'ss_timestamp', limit: nil, precision: nil, scale: nil, default: nil
117
+ assert_line :json_col, type: 'text', limit: 2147483647, precision: nil, scale: nil, default: nil
118
+ end
119
+
120
+ # Special Cases
121
+
122
+ it 'honor nonstandard primary keys' do
123
+ generate_schema_for_table('movies') do |output|
124
+ match = output.match(%r{create_table "movies"(.*)do})
125
+ assert_not_nil(match, "nonstandardpk table not found")
126
+ assert_match %r(primary_key: "movieid"), match[1], "non-standard primary key not preserved"
127
+ end
128
+ end
129
+
130
+ it 'no id with model driven primary key' do
131
+ output = generate_schema_for_table 'sst_no_pk_data'
132
+ output.must_match %r{create_table "sst_no_pk_data".*id:\sfalse.*do}
133
+ assert_line :name, type: 'string', limit: nil, default: nil, collation: nil
134
+ end
135
+
136
+
137
+ private
138
+
139
+ def generate_schema_for_table(*table_names)
140
+ require 'stringio'
141
+ stream = StringIO.new
142
+ ActiveRecord::SchemaDumper.ignore_tables = all_tables - table_names
143
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
144
+ @generated_schema = stream.string
145
+ yield @generated_schema if block_given?
146
+ @schema_lines = Hash.new
147
+ type_matcher = /\A\s+t\.\w+\s+"(.*?)"[,\n]/
148
+ @generated_schema.each_line do |line|
149
+ next unless line =~ type_matcher
150
+ @schema_lines[Regexp.last_match[1]] = SchemaLine.new(line)
151
+ end
152
+ @generated_schema
153
+ end
154
+
155
+ def line(column_name)
156
+ @schema_lines[column_name.to_s]
157
+ end
158
+
159
+ def assert_line(column_name, options={})
160
+ line = line(column_name)
161
+ assert line, "Count not find line with column name: #{column_name.inspect} in schema:\n#{schema}"
162
+ [:type, :limit, :precision, :scale, :collation, :default].each do |key|
163
+ next unless options.key?(key)
164
+ actual = key == :type ? line.send(:type_method) : line.send(key)
165
+ expected = options[key]
166
+ message = "#{key.to_s.titleize} of #{expected.inspect} not found in:\n#{line}"
167
+ if expected.nil?
168
+ actual.must_be_nil message
169
+ elsif expected.is_a?(Array)
170
+ actual.must_include expected, message
171
+ elsif expected.is_a?(Float)
172
+ actual.must_be_close_to expected, 0.001
173
+ elsif expected.is_a?(Proc)
174
+ actual.call.must_equal(expected.call)
175
+ else
176
+ actual.must_equal expected, message
177
+ end
178
+ end
179
+ end
180
+
181
+ class SchemaLine
182
+
183
+ LINE_PARSER = %r{t\.(\w+)\s+"(.*?)"[,\s+](.*)}
184
+
185
+ attr_reader :line,
186
+ :type_method,
187
+ :col_name,
188
+ :options
189
+
190
+ def self.option(method_name)
191
+ define_method(method_name) { options.present? ? options[method_name.to_sym] : nil }
192
+ end
193
+
194
+ def initialize(line)
195
+ @line = line
196
+ @type_method, @col_name, @options = parse_line
197
+ end
198
+
199
+ option :limit
200
+ option :precision
201
+ option :scale
202
+ option :default
203
+ option :collation
204
+
205
+ def to_s
206
+ line.squish
207
+ end
208
+
209
+ def inspect
210
+ "#<SchemaLine col_name=#{col_name.inspect}, options=#{options.inspect}>"
211
+ end
212
+
213
+ private
214
+
215
+ def parse_line
216
+ _all, type_method, col_name, options = @line.match(LINE_PARSER).to_a
217
+ options = parse_options(options)
218
+ [type_method, col_name, options]
219
+ end
220
+
221
+ def parse_options(opts)
222
+ if opts.present?
223
+ eval "{#{opts}}"
224
+ else
225
+ {}
226
+ end
227
+ rescue SyntaxError
228
+ {}
229
+ end
230
+
231
+ end
232
+
233
+ end
234
+