activerecord-sqlserver-adapter_new 4.2.15

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