activerecord-jdbcsqlserver-adapter 50.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+