pt-osc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +22 -0
  2. data/Gemfile +4 -0
  3. data/README.md +35 -0
  4. data/Rakefile +20 -0
  5. data/lib/active_record/connection_adapters/pt_osc_adapter.rb +134 -0
  6. data/lib/active_record/pt_osc_migration.rb +150 -0
  7. data/lib/pt-osc.rb +3 -0
  8. data/lib/pt-osc/version.rb +5 -0
  9. data/pt-osc.gemspec +32 -0
  10. data/test/config/database.yml +6 -0
  11. data/test/dummy/.gitignore +1 -0
  12. data/test/dummy/README.rdoc +261 -0
  13. data/test/dummy/Rakefile +7 -0
  14. data/test/dummy/app/assets/javascripts/application.js +15 -0
  15. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  16. data/test/dummy/app/controllers/application_controller.rb +3 -0
  17. data/test/dummy/app/helpers/application_helper.rb +2 -0
  18. data/test/dummy/app/mailers/.gitkeep +0 -0
  19. data/test/dummy/app/models/.gitkeep +0 -0
  20. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  21. data/test/dummy/config.ru +4 -0
  22. data/test/dummy/config/application.rb +59 -0
  23. data/test/dummy/config/boot.rb +10 -0
  24. data/test/dummy/config/database.yml +16 -0
  25. data/test/dummy/config/environment.rb +5 -0
  26. data/test/dummy/config/environments/development.rb +37 -0
  27. data/test/dummy/config/environments/production.rb +67 -0
  28. data/test/dummy/config/environments/test.rb +37 -0
  29. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  30. data/test/dummy/config/initializers/inflections.rb +15 -0
  31. data/test/dummy/config/initializers/mime_types.rb +5 -0
  32. data/test/dummy/config/initializers/secret_token.rb +7 -0
  33. data/test/dummy/config/initializers/session_store.rb +8 -0
  34. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  35. data/test/dummy/config/locales/en.yml +5 -0
  36. data/test/dummy/config/routes.rb +58 -0
  37. data/test/dummy/db/development.sqlite3 +0 -0
  38. data/test/dummy/db/test.sqlite3 +0 -0
  39. data/test/dummy/lib/assets/.gitkeep +0 -0
  40. data/test/dummy/public/404.html +26 -0
  41. data/test/dummy/public/422.html +26 -0
  42. data/test/dummy/public/500.html +25 -0
  43. data/test/dummy/public/favicon.ico +0 -0
  44. data/test/dummy/script/rails +6 -0
  45. data/test/functional/pt_osc_adapter_test.rb +16 -0
  46. data/test/functional/pt_osc_migration_functional_test.rb +99 -0
  47. data/test/test_helper.rb +49 -0
  48. data/test/unit/pt_osc_adapter_test.rb +209 -0
  49. data/test/unit/pt_osc_migration_unit_test.rb +39 -0
  50. metadata +281 -0
File without changes
File without changes
File without changes
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/404.html -->
21
+ <div class="dialog">
22
+ <h1>The page you were looking for doesn't exist.</h1>
23
+ <p>You may have mistyped the address or the page may have moved.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/422.html -->
21
+ <div class="dialog">
22
+ <h1>The change you wanted was rejected.</h1>
23
+ <p>Maybe you tried to change something you didn't have access to.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ </div>
24
+ </body>
25
+ </html>
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,16 @@
1
+ require 'test_helper'
2
+ require 'yaml'
3
+
4
+ class PtOscAdapterTest < Test::Unit::TestCase
5
+ class TestConnection < ActiveRecord::Base; end
6
+
7
+ def test_connection
8
+ # Test that we can open a connection with the pt_osc adapter
9
+ spec = test_spec
10
+ spec.delete('database') # We don't care whether the database exists
11
+
12
+ assert_nothing_raised { TestConnection.establish_connection(spec) }
13
+ assert_equal true, TestConnection.connection.in_use
14
+ assert_kind_of ActiveRecord::ConnectionAdapters::PtOscAdapter, TestConnection.connection
15
+ end
16
+ end
@@ -0,0 +1,99 @@
1
+ require 'test_helper'
2
+
3
+ class PtOscMigrationFunctionalTest < ActiveRecord::TestCase
4
+ class TestMigration < ActiveRecord::PtOscMigration; end
5
+
6
+ context 'a migration connected to a pt-osc database' do
7
+ setup do
8
+ ActiveRecord::Base.establish_connection(test_spec)
9
+ @migration = TestMigration.new
10
+ @migration.instance_variable_set(:@connection, ActiveRecord::Base.connection)
11
+ end
12
+
13
+ context 'on an existing table with an existing column' do
14
+ setup do
15
+ @table_name = Faker::Lorem.word
16
+ @column_name = Faker::Lorem.word
17
+ @index_name = Faker::Lorem.words.join('_')
18
+ @index_name_2 = "#{@index_name}_2"
19
+
20
+ ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS `#{@table_name}`;"
21
+ ActiveRecord::Base.connection.execute <<-SQL
22
+ CREATE TABLE `#{@table_name}` (
23
+ `#{@column_name}` varchar(255) DEFAULT NULL,
24
+ KEY `#{@index_name}` (`#{@column_name}`)
25
+ );
26
+ SQL
27
+ end
28
+
29
+ teardown do
30
+ ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS `#{@table_name}`;"
31
+ end
32
+
33
+ context 'a migration with only ALTER statements' do
34
+ setup do
35
+ TestMigration.class_eval <<-EVAL
36
+ def change
37
+ rename_table :#{@table_name}, :#{Faker::Lorem.word}
38
+ add_column :#{@table_name}, :#{Faker::Lorem.word}, :integer
39
+ change_column :#{@table_name}, :#{@column_name}, :varchar, default: 'newthing'
40
+ rename_column :#{@table_name}, :#{@column_name}, :#{Faker::Lorem.word}
41
+ remove_column :#{@table_name}, :#{@column_name}
42
+ add_index :#{@table_name}, :#{@column_name}, name: :#{@index_name_2}
43
+ remove_index :#{@table_name}, name: :#{@index_name}
44
+ end
45
+ EVAL
46
+ end
47
+
48
+ teardown do
49
+ TestMigration.send(:remove_method, :change)
50
+ end
51
+
52
+ context 'ignoring schema lookups' do
53
+ setup do
54
+ # Kind of a hacky way to do this
55
+ ignored_sql = ActiveRecord::SQLCounter.ignored_sql + [
56
+ /^SHOW FULL FIELDS FROM/,
57
+ /^SHOW COLUMNS FROM/,
58
+ /^SHOW KEYS FROM/,
59
+ ]
60
+ ActiveRecord::SQLCounter.any_instance.stubs(:ignore).returns(ignored_sql)
61
+ end
62
+
63
+ teardown do
64
+ ActiveRecord::SQLCounter.any_instance.unstub(:ignore)
65
+ end
66
+
67
+ context 'with suppressed output' do
68
+ setup do
69
+ @migration.stubs(:write)
70
+ @migration.stubs(:announce)
71
+ end
72
+
73
+ teardown do
74
+ @migration.unstub(:write, :announce)
75
+ end
76
+
77
+ should 'not execute any queries immediately' do
78
+ assert_no_queries { @migration.change }
79
+ end
80
+
81
+ context 'with a working pt-online-schema-change' do
82
+ setup do
83
+ Kernel.expects(:system).with(regexp_matches(/^pt-online-schema-change/)).twice.returns(true)
84
+ end
85
+
86
+ teardown do
87
+ Kernel.unstub(:system)
88
+ end
89
+
90
+ should 'not directly execute any queries when migrating' do
91
+ assert_no_queries { @migration.migrate(:up) }
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,49 @@
1
+ # Configure Rails Environment
2
+ ENV['RAILS_ENV'] = 'test'
3
+
4
+ require File.expand_path('../dummy/config/environment.rb', __FILE__)
5
+ require 'test/unit'
6
+ require 'shoulda'
7
+ require 'faker'
8
+ require 'mocha'
9
+
10
+ Rails.backtrace_cleaner.remove_silencers!
11
+
12
+ def test_spec
13
+ test_spec = YAML.load_file(Rails.root.join(*%w(.. config database.yml)))['test']
14
+ test_spec['adapter'] = 'pt_osc'
15
+ test_spec
16
+ end
17
+
18
+ # SQLCounter is part of ActiveRecord but is not distributed with the gem (used for internal tests only)
19
+ # see https://github.com/rails/rails/blob/3-2-stable/activerecord/test/cases/helper.rb#L59
20
+ module ActiveRecord
21
+ class SQLCounter
22
+ cattr_accessor :ignored_sql
23
+ self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/]
24
+
25
+ # FIXME: this needs to be refactored so specific database can add their own
26
+ # ignored SQL. This ignored SQL is for Oracle.
27
+ ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
28
+
29
+ cattr_accessor :log
30
+ self.log = []
31
+
32
+ attr_reader :ignore
33
+
34
+ def initialize(ignore = self.class.ignored_sql)
35
+ @ignore = ignore
36
+ end
37
+
38
+ def call(name, start, finish, message_id, values)
39
+ sql = values[:sql]
40
+
41
+ # FIXME: this seems bad. we should probably have a better way to indicate
42
+ # the query was cached
43
+ return if 'CACHE' == values[:name] || ignore.any? { |x| x =~ sql }
44
+ self.class.log << sql
45
+ end
46
+ end
47
+
48
+ ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
49
+ end
@@ -0,0 +1,209 @@
1
+ require 'test_helper'
2
+
3
+ class PtOscAdapterTest < Test::Unit::TestCase
4
+ class TestConnection < ActiveRecord::Base; end
5
+
6
+ context 'a pt-osc adapter' do
7
+ setup do
8
+ TestConnection.establish_connection(test_spec)
9
+ @adapter = TestConnection.connection
10
+ end
11
+
12
+ context '#rename_table' do
13
+ context 'with no existing commands' do
14
+ setup do
15
+ @adapter.instance_variable_set(:@osc_commands, nil)
16
+ end
17
+
18
+ should 'add a RENAME TO command to the commands hash' do
19
+ table_name = Faker::Lorem.word
20
+ new_table_name = Faker::Lorem.word
21
+ @adapter.rename_table(table_name, new_table_name)
22
+ assert_equal "RENAME TO `#{new_table_name}`", @adapter.send(:get_commands, table_name).first
23
+ end
24
+ end
25
+ end
26
+
27
+ context '#add_column' do
28
+ context 'with no existing commands' do
29
+ setup do
30
+ @adapter.instance_variable_set(:@osc_commands, nil)
31
+ end
32
+
33
+ should 'add an ADD command to the commands hash' do
34
+ table_name = Faker::Lorem.word
35
+ column_name = Faker::Lorem.word
36
+ @adapter.add_column(table_name, column_name, :string, default: 0, null: false)
37
+ assert_equal "ADD `#{column_name}` varchar(255) DEFAULT 0 NOT NULL", @adapter.send(:get_commands, table_name).first
38
+ end
39
+ end
40
+ end
41
+
42
+ context '#change_column' do
43
+ context 'with no existing commands' do
44
+ setup do
45
+ @adapter.instance_variable_set(:@osc_commands, nil)
46
+ end
47
+
48
+ context 'with an existing table and column' do
49
+ setup do
50
+ @table_name = Faker::Lorem.word
51
+ @column_name = Faker::Lorem.word
52
+ @adapter.create_table @table_name, force: true do |t|
53
+ t.string @column_name
54
+ end
55
+ end
56
+
57
+ should 'add a CHANGE command to the commands hash' do
58
+ @adapter.change_column(@table_name, @column_name, :string, default: 0, null: false)
59
+ assert_equal "CHANGE `#{@column_name}` `#{@column_name}` varchar(255) DEFAULT 0 NOT NULL", @adapter.send(:get_commands, @table_name).first
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ context '#rename_column' do
66
+ context 'with no existing commands' do
67
+ setup do
68
+ @adapter.instance_variable_set(:@osc_commands, nil)
69
+ end
70
+
71
+ context 'with an existing table and column' do
72
+ setup do
73
+ @table_name = Faker::Lorem.word
74
+ @column_name = Faker::Lorem.word
75
+ @adapter.create_table @table_name, force: true do |t|
76
+ t.string @column_name, default: nil
77
+ end
78
+ end
79
+
80
+ should 'add a CHANGE command to the commands hash' do
81
+ new_column_name = Faker::Lorem.word
82
+ @adapter.rename_column(@table_name, @column_name, new_column_name)
83
+ assert_equal "CHANGE `#{@column_name}` `#{new_column_name}` varchar(255) DEFAULT NULL", @adapter.send(:get_commands, @table_name).first
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ context '#remove_column' do
90
+ context 'with no existing commands' do
91
+ setup do
92
+ @adapter.instance_variable_set(:@osc_commands, nil)
93
+ end
94
+
95
+ should 'add a DROP COLUMN command to the commands hash' do
96
+ table_name = Faker::Lorem.word
97
+ column_name = Faker::Lorem.word
98
+ @adapter.remove_column(table_name, column_name)
99
+ assert_equal "DROP COLUMN `#{column_name}`", @adapter.send(:get_commands, table_name).first
100
+ end
101
+
102
+ should 'add multiple DROP COLUMN commands to the commands hash' do
103
+ table_name = Faker::Lorem.word
104
+ column_names = 3.times.map { Faker::Lorem.word }
105
+ @adapter.remove_column(table_name, *column_names)
106
+ commands = @adapter.send(:get_commands, table_name)
107
+ column_names.each_with_index do |column_name, index|
108
+ assert_equal "DROP COLUMN `#{column_name}`", commands[index]
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ context '#add_index' do
115
+ context 'with no existing commands' do
116
+ setup do
117
+ @adapter.instance_variable_set(:@osc_commands, nil)
118
+ end
119
+
120
+ context 'with an existing table and columns' do
121
+ setup do
122
+ @table_name = Faker::Lorem.word
123
+ @column_names = Faker::Lorem.words
124
+ @adapter.create_table @table_name, force: true do |t|
125
+ @column_names.each do |column_name|
126
+ t.string(column_name, default: nil)
127
+ end
128
+ end
129
+ end
130
+
131
+ should 'add an ADD INDEX command for one column to the commands hash' do
132
+ index_name = Faker::Lorem.words.join('_')
133
+ @adapter.add_index(@table_name, @column_names.first, name: index_name)
134
+ assert_equal "ADD INDEX `#{index_name}` (`#{@column_names.first}`)", @adapter.send(:get_commands, @table_name).first
135
+ end
136
+
137
+ should 'add an ADD UNIQUE INDEX command for one column to the commands hash' do
138
+ index_name = Faker::Lorem.words.join('_')
139
+ @adapter.add_index(@table_name, @column_names.first, unique: true, name: index_name)
140
+ assert_equal "ADD UNIQUE INDEX `#{index_name}` (`#{@column_names.first}`)", @adapter.send(:get_commands, @table_name).first
141
+ end
142
+
143
+ should 'add an ADD INDEX command for multiple columns to the commands hash' do
144
+ index_name = Faker::Lorem.words.join('_')
145
+ @adapter.add_index(@table_name, @column_names, name: index_name)
146
+ assert_equal "ADD INDEX `#{index_name}` (`#{@column_names.join('`, `')}`)", @adapter.send(:get_commands, @table_name).first
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ context '#remove_index!' do
153
+ context 'with no existing commands' do
154
+ setup do
155
+ @adapter.instance_variable_set(:@osc_commands, nil)
156
+ end
157
+
158
+ should 'add a DROP COLUMN command to the commands hash' do
159
+ table_name = Faker::Lorem.word
160
+ index_name = Faker::Lorem.word
161
+ @adapter.remove_index!(table_name, index_name)
162
+ assert_equal "DROP INDEX `#{index_name}`", @adapter.send(:get_commands, table_name).first
163
+ end
164
+ end
165
+ end
166
+
167
+ context '#add_command' do
168
+ context 'with no existing commands' do
169
+ setup do
170
+ @adapter.instance_variable_set(:@osc_commands, nil)
171
+ end
172
+
173
+ should 'add a command without initializing the array' do
174
+ table_name = Faker::Lorem.word
175
+ @adapter.send(:add_command, table_name, 'foo')
176
+ assert_kind_of Array, @adapter.send(:get_commands, table_name)
177
+ assert_equal 1, @adapter.send(:get_commands, table_name).size
178
+ assert_equal 'foo', @adapter.send(:get_commands, table_name).first
179
+ end
180
+ end
181
+ end
182
+
183
+ context '#get_commands' do
184
+ context 'with no existing commands' do
185
+ setup do
186
+ @adapter.instance_variable_set(:@osc_commands, nil)
187
+ end
188
+
189
+ should "return nil for a table that doesn't exist" do
190
+ table_name = Faker::Lorem.word
191
+ assert_nil @adapter.send(:get_commands, table_name)
192
+ end
193
+ end
194
+ end
195
+
196
+ context '#get_commands_string' do
197
+ context 'with no existing commands' do
198
+ setup do
199
+ @adapter.instance_variable_set(:@osc_commands, nil)
200
+ end
201
+
202
+ should "return an empty string for a table that doesn't exist" do
203
+ table_name = Faker::Lorem.word
204
+ assert_equal '', @adapter.send(:get_commands_string, table_name)
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end