activerecord-jdbcsqlserver-adapter 50.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.travis.yml +27 -0
- data/CHANGELOG.md +124 -0
- data/CODE_OF_CONDUCT.md +31 -0
- data/Dockerfile +20 -0
- data/Gemfile +77 -0
- data/Guardfile +29 -0
- data/MIT-LICENSE +20 -0
- data/RAILS5-TODO.md +5 -0
- data/README.md +93 -0
- data/RUNNING_UNIT_TESTS.md +96 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/activerecord-jdbcsqlserver-adapter.gemspec +21 -0
- data/appveyor.yml +39 -0
- data/docker-compose.ci.yml +11 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +27 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +25 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/date_time.rb +58 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +47 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +4 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +362 -0
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +67 -0
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +7 -0
- data/lib/active_record/connection_adapters/sqlserver/jdbc_overrides.rb +192 -0
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +99 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +34 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +517 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +66 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +66 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +22 -0
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +112 -0
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +64 -0
- data/lib/active_record/connection_adapters/sqlserver/type.rb +49 -0
- data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +21 -0
- data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/char.rb +32 -0
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +30 -0
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +61 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +71 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +17 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +23 -0
- data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +21 -0
- data/lib/active_record/connection_adapters/sqlserver/type/float.rb +19 -0
- data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/json.rb +11 -0
- data/lib/active_record/connection_adapters/sqlserver/type/money.rb +25 -0
- data/lib/active_record/connection_adapters/sqlserver/type/real.rb +19 -0
- data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +25 -0
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +29 -0
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/text.rb +19 -0
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +68 -0
- data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +93 -0
- data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +19 -0
- data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +25 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +21 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +19 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +26 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +24 -0
- data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +36 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +26 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +24 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +26 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +24 -0
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +146 -0
- data/lib/active_record/connection_adapters/sqlserver/version.rb +11 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +445 -0
- data/lib/active_record/connection_adapters/sqlserver_column.rb +28 -0
- data/lib/active_record/jdbc_sqlserver_connection_methods.rb +31 -0
- data/lib/active_record/sqlserver_base.rb +16 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +131 -0
- data/lib/activerecord-jdbcsqlserver-adapter.rb +24 -0
- data/lib/activerecord-sqlserver-adapter.rb +1 -0
- data/lib/arel/visitors/sqlserver.rb +205 -0
- data/lib/arel_sqlserver.rb +3 -0
- data/test/appveyor/dbsetup.ps1 +27 -0
- data/test/appveyor/dbsetup.sql +11 -0
- data/test/bin/wait-for.sh +79 -0
- data/test/cases/adapter_test_sqlserver.rb +430 -0
- data/test/cases/coerced_tests.rb +845 -0
- data/test/cases/column_test_sqlserver.rb +812 -0
- data/test/cases/connection_test_sqlserver.rb +71 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +45 -0
- data/test/cases/fetch_test_sqlserver.rb +57 -0
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +76 -0
- data/test/cases/helper_sqlserver.rb +44 -0
- data/test/cases/index_test_sqlserver.rb +47 -0
- data/test/cases/json_test_sqlserver.rb +32 -0
- data/test/cases/migration_test_sqlserver.rb +61 -0
- data/test/cases/order_test_sqlserver.rb +147 -0
- data/test/cases/pessimistic_locking_test_sqlserver.rb +94 -0
- data/test/cases/rake_test_sqlserver.rb +169 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +234 -0
- data/test/cases/schema_test_sqlserver.rb +54 -0
- data/test/cases/scratchpad_test_sqlserver.rb +8 -0
- data/test/cases/showplan_test_sqlserver.rb +65 -0
- data/test/cases/specific_schema_test_sqlserver.rb +180 -0
- data/test/cases/transaction_test_sqlserver.rb +91 -0
- data/test/cases/utils_test_sqlserver.rb +129 -0
- data/test/cases/uuid_test_sqlserver.rb +49 -0
- data/test/config.yml +38 -0
- data/test/debug.rb +14 -0
- data/test/fixtures/1px.gif +0 -0
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
- data/test/models/sqlserver/booking.rb +3 -0
- data/test/models/sqlserver/customers_view.rb +3 -0
- data/test/models/sqlserver/datatype.rb +3 -0
- data/test/models/sqlserver/datatype_migration.rb +8 -0
- data/test/models/sqlserver/dollar_table_name.rb +3 -0
- data/test/models/sqlserver/dot_table_name.rb +3 -0
- data/test/models/sqlserver/edge_schema.rb +13 -0
- data/test/models/sqlserver/fk_has_fk.rb +3 -0
- data/test/models/sqlserver/fk_has_pk.rb +3 -0
- data/test/models/sqlserver/natural_pk_data.rb +4 -0
- data/test/models/sqlserver/natural_pk_int_data.rb +3 -0
- data/test/models/sqlserver/no_pk_data.rb +3 -0
- data/test/models/sqlserver/object_default.rb +3 -0
- data/test/models/sqlserver/quoted_table.rb +7 -0
- data/test/models/sqlserver/quoted_view_1.rb +3 -0
- data/test/models/sqlserver/quoted_view_2.rb +3 -0
- data/test/models/sqlserver/sst_memory.rb +3 -0
- data/test/models/sqlserver/string_default.rb +3 -0
- data/test/models/sqlserver/string_defaults_big_view.rb +3 -0
- data/test/models/sqlserver/string_defaults_view.rb +3 -0
- data/test/models/sqlserver/tinyint_pk.rb +3 -0
- data/test/models/sqlserver/upper.rb +3 -0
- data/test/models/sqlserver/uppered.rb +3 -0
- data/test/models/sqlserver/uuid.rb +3 -0
- data/test/schema/datatypes/2012.sql +55 -0
- data/test/schema/enable-in-memory-oltp.sql +81 -0
- data/test/schema/sqlserver_specific_schema.rb +238 -0
- data/test/support/coerceable_test_sqlserver.rb +49 -0
- data/test/support/connection_reflection.rb +34 -0
- data/test/support/load_schema_sqlserver.rb +29 -0
- data/test/support/minitest_sqlserver.rb +1 -0
- data/test/support/paths_sqlserver.rb +50 -0
- data/test/support/rake_helpers.rb +41 -0
- data/test/support/sql_counter_sqlserver.rb +28 -0
- data/test/support/test_in_memory_oltp.rb +15 -0
- 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
|
+
|