mysqlnoio 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require 'mysqlnoio'
4
+
5
+ desc "Builds the gem"
6
+ task :build do
7
+ sh "gem build mysqlnoio.gemspec"
8
+ end
9
+
10
+ desc "Publishes the gem to fury.io"
11
+ task :publish => [:build] do |t|
12
+ sh "git tag -f #{MySQLNoIo::VERSION}"
13
+ sh "git push origin #{MySQLNoIo::VERSION} -f"
14
+ sh "curl -F p1=@mysqlnoio-#{MySQLNoIo::VERSION}.gem https://push.fury.io/BEz67VknqnyqmV6p7Huq/"
15
+ end
16
+
17
+ desc "Cleans the project directory up"
18
+ task :clean do
19
+ sh "rm -rf doc/ tmp/"
20
+ sh "rm -f mysqlnoio-*.gem"
21
+ end
22
+
23
+ # Various RSpec Bitties
24
+ base_opts = '--color --order random'
25
+ desc "Run specs"
26
+ RSpec::Core::RakeTask.new(:spec) do |t|
27
+ t.verbose = false
28
+ t.rspec_opts = "#{base_opts} --tag ~integration"
29
+ end
30
+
31
+ desc "Run integration specs"
32
+ RSpec::Core::RakeTask.new(:integration) do |t|
33
+ t.verbose = false
34
+ t.rspec_opts = "#{base_opts} --tag integration"
35
+ end
36
+
37
+ desc "Run all specs"
38
+ RSpec::Core::RakeTask.new(:test) do |t|
39
+ t.verbose = false
40
+ t.rspec_opts = base_opts
41
+ end
42
+
43
+ task :default => :spec
data/Vagrantfile ADDED
@@ -0,0 +1,18 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ $boot = <<SCRIPT
5
+ sudo apt-get install build-essential
6
+
7
+ curl -L https://get.rvm.io | bash -s --autolibs=3 --ruby
8
+ SCRIPT
9
+
10
+ Vagrant.configure("2") do |config|
11
+ config.vm.box = "ubuntu-12.04.2-server-adm54-vmware-fusion"
12
+ config.vm.box_url = "https://s3.amazonaws.com/gsc-vagrant-boxes/ubuntu-12.04.2-server-amd64.box"
13
+
14
+ config.vm.provision :shell do |shell|
15
+ shell.inline = $script
16
+ shell.path = 'vagrant/provision.sh'
17
+ end
18
+ end
data/bin/mysql-noio ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'mysqlnoio'
5
+
6
+ credentials = {}
7
+ wraps = []
8
+
9
+ OptionParser.new do |opts|
10
+ opts.banner = "Usage: mysql-noio [options] <command>"
11
+
12
+ # Determine the MySQL connection parameters
13
+ opts.on("-u", "--user USER", "MySQL Username") do |v|
14
+ credentials[:username] = v
15
+ end
16
+
17
+ opts.on("-p", "--password PASSWORD", "MySQL Password") do |v|
18
+ credentials[:password] = v
19
+ end
20
+
21
+ opts.on("-h", "--host HOST", "MySQL Server Host") do |v|
22
+ credentials[:host] = v
23
+ end
24
+
25
+
26
+ opts.on("-t", "--lock-tables", "Lock tables with a read lock") do |v|
27
+ wraps << :lock
28
+ end
29
+
30
+ opts.on("-s", "--stop-slave", "Stop the slave thread") do |v|
31
+ wraps << :stop
32
+ end
33
+
34
+ opts.on
35
+ end.parse!
36
+
37
+ client = Mysql2::Client.new(credentials)
38
+
39
+ stack = MySQLNoIo::WrapStack.new
40
+ stack.add_wrap MySQLNoIo::Wraps::DeactivatedSlave.new client if wraps.include? :stop
41
+ stack.add_wrap MySQLNoIo::Wraps::GlobalLock.new client if wraps.include? :lock
42
+
43
+ stack.execute do
44
+ spawn *ARGV
45
+ Process.wait
46
+ end
47
+
48
+ exit $?.to_i
49
+
@@ -0,0 +1,12 @@
1
+
2
+ module MySQLNoIo
3
+ # Used in Gemspec and Commander
4
+ NAME = 'mysqlnoio'
5
+
6
+ # Used in Gemspec and Commander
7
+ VERSION = '0.1.0'
8
+
9
+ # Used in Gemspec and Commander
10
+ DESCRIPTION = 'mysqlnoio allows you to execute commands while MySQL is locked.'
11
+ end
12
+
@@ -0,0 +1,11 @@
1
+
2
+ module MySQLNoIo
3
+ # A collection of erros which MySQLNoIo might raise.
4
+ module Errors
5
+
6
+ # represents an argument passed in to a method is invalid.
7
+ class ArgumentError < ::ArgumentError
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,34 @@
1
+
2
+ module MySQLNoIo
3
+
4
+ # A wrap represents a open/close block which always runs a before and an
5
+ # after statement.
6
+ #
7
+ # Passing a block to #execute will execute the block from between the
8
+ # statements.
9
+ #
10
+ # Example:
11
+ # class FooWrap
12
+ # def execute &block
13
+ # puts "Hi"
14
+ #
15
+ # yeild(block)
16
+ # ensure
17
+ # puts "Bye"
18
+ # end
19
+ # end
20
+ #
21
+ # foo = FooWrap.new()
22
+ # foo.execute do
23
+ # puts "Ahh! Safety!"
24
+ # end
25
+ #
26
+ # Will output:
27
+ # Hi
28
+ # Ahh! Safety!
29
+ # Bye
30
+ #
31
+ class Wrap
32
+ end
33
+ end
34
+
@@ -0,0 +1,65 @@
1
+
2
+ module MySQLNoIo
3
+ # A WrapStack collects a series of Wraps, and runs their #execute commands
4
+ # inside one another.
5
+ #
6
+ # Example:
7
+ # stack = MySQLNoIo::WrapStack.new()a
8
+ #
9
+ # wrapFoo = Wrap::Foo.new()
10
+ # wrapBar = Wrap::Bar.new()
11
+ #
12
+ # stack.add_stack wrapFoo
13
+ # stack.add_stack wrapBar
14
+ # stack.execute do
15
+ # puts "hi"
16
+ # end
17
+ #
18
+ # Is identical to:
19
+ # wrapFoo.execute do
20
+ # wrapBar.execute do
21
+ # puts "hi"
22
+ # end
23
+ # end
24
+ #
25
+ class WrapStack
26
+ def initialize
27
+ @wraps = []
28
+ end
29
+
30
+ # Add a layer to wrap around the execution of the command
31
+ #
32
+ # @param [MySQLNoIo::Wrap] the wrapper to add to the stack
33
+ # @raise [MySQLNoIo::Errors::ArgumentError] if the wrapper is not sufficient
34
+ #
35
+ # @return [TrueClass]
36
+ def add_wrap wrap
37
+ unless wrap.respond_to?('execute')
38
+ raise MySQLNoIo::Errors::ArgumentError.new('Wrap must respond to #execute and accept a block.')
39
+ end
40
+
41
+ @wraps << wrap
42
+
43
+ true
44
+ end
45
+
46
+ # Execute your bit of code from within the safety and comfort of the
47
+ # wrappers appended to the stack.
48
+ def execute &block
49
+ # Brace yourself.
50
+ #
51
+ # This next bit of code creates a nested lamda like the WrapStack
52
+ # describes.
53
+ deep_proc = @wraps.reverse.inject(block) do |deep_proc, layer|
54
+ Proc.new do
55
+ layer.execute do
56
+ deep_proc.call
57
+ end
58
+ end
59
+ end
60
+
61
+ deep_proc.call
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,24 @@
1
+ module MySQLNoIo
2
+ module Wraps
3
+ # Just an example Wrap for testing and examples.
4
+ class Blocks
5
+ # The before block will be run prior to the execution.
6
+ def before &block
7
+ @prior = block
8
+ end
9
+
10
+ # The after block will be run post execution.
11
+ def after &block
12
+ @post = block
13
+ end
14
+
15
+ # Call the prior block, then execute, and then call the post-block.
16
+ def execute &block
17
+ @prior.call if @prior.respond_to?(:call)
18
+ block.call
19
+ ensure
20
+ @post.call if @post.respond_to?(:call)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ module MySQLNoIo
2
+ module Wraps
3
+ # Stops the database slave entirely for your command
4
+ class DeactivatedSlave
5
+ def initialize mysql
6
+ @mysql = mysql
7
+ end
8
+
9
+ def execute &block
10
+ @mysql.query('STOP SLAVE;')
11
+ yield
12
+ ensure
13
+ @mysql.query('START SLAVE;')
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,21 @@
1
+ module MySQLNoIo
2
+ module Wraps
3
+ # Allows you to run commands against a MySQL database, while MySQL's
4
+ # tables are locked, and the tables are flushed.
5
+ class GlobalLock
6
+ # Prepare to set up the GlobalLock wrapper, with the mysql2 connection
7
+ def initialize mysql
8
+ @mysql = mysql
9
+ end
10
+
11
+ # Lock the tables, run our command, and then unlock the tables.
12
+ def execute &block
13
+ @mysql.query("FLUSH TABLES WITH READ LOCK;")
14
+ yield
15
+ ensure
16
+ @mysql.query("UNLOCK TABLES;")
17
+ end
18
+ end
19
+ end
20
+ end
21
+
data/lib/mysqlnoio.rb ADDED
@@ -0,0 +1,19 @@
1
+
2
+ require 'mysql2'
3
+
4
+ require 'mysqlnoio/about'
5
+
6
+ require 'mysqlnoio/errors'
7
+
8
+ require 'mysqlnoio/wrap_stack'
9
+ require 'mysqlnoio/wrap'
10
+ require 'mysqlnoio/wraps/blocks'
11
+ require 'mysqlnoio/wraps/global_lock'
12
+ require 'mysqlnoio/wraps/deactivated_slave'
13
+
14
+
15
+ # See: README.md
16
+ module MySQLNoIo
17
+
18
+ end
19
+
data/mysqlnoio.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'mysqlnoio/about'
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.name = MySQLNoIo::NAME
9
+ gem.version = MySQLNoIo::VERSION
10
+ gem.description = MySQLNoIo::DESCRIPTION
11
+ gem.summary = 'Execute shell commands while MySQL IO is stopped.'
12
+ gem.homepage = 'https://zippykid.com'
13
+ gem.authors = ["ZippyKid", "Graham Christensen"]
14
+ gem.email = ["info@zippykid.com"]
15
+ gem.licenses = ["MIT"]
16
+
17
+ gem.files = `git ls-files`.split($/)
18
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
+ gem.require_paths = ["lib"]
21
+
22
+ gem.add_dependency('mysql2')
23
+
24
+ gem.add_development_dependency('rspec')
25
+ gem.add_development_dependency('debugger')
26
+
27
+ # Guard:
28
+ gem.add_development_dependency('guard')
29
+ gem.add_development_dependency('guard-rspec')
30
+ gem.add_development_dependency('guard-shell')
31
+ gem.add_development_dependency('rb-fsevent', '~> 0.9')
32
+ gem.add_development_dependency('rb-inotify', '~> 0.9')
33
+ gem.add_development_dependency('travis-lint')
34
+
35
+ # Docs
36
+ gem.add_development_dependency('yard')
37
+ gem.add_development_dependency('redcarpet')
38
+ end
39
+
@@ -0,0 +1,17 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe "mysql-noio", :integration => true do
4
+ context "called with no parameters" do
5
+ let (:output) { `bundle exec #{project_root}/bin/mysql-noio 2>&1` }
6
+
7
+ it "exits with a zero error code" do
8
+ $?.to_i.should eq(0)
9
+ end
10
+
11
+ it "does not contain something that looks like a stack trace" do
12
+ output.should_not =~ /in `<main>'/
13
+ output.should_not =~ /syntax error/
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe MySQLNoIo::WrapStack do
4
+ let(:stack) { MySQLNoIo::WrapStack.new }
5
+ describe "#add_wrap" do
6
+
7
+ context "An invalid wrap is provided" do
8
+ it "expects the method to have an #execute method" do
9
+ expect {
10
+ stack.add_wrap(nil)
11
+ }.to raise_error(MySQLNoIo::Errors::ArgumentError, /\#execute/)
12
+ end
13
+ end
14
+
15
+ context "A valid wrap is provided" do
16
+ let (:wrap) { double("MySQLNoIo::Wrap", execute: true) }
17
+ it "only requires the #execute method to be callable" do
18
+ stack.add_wrap(wrap).should eq(true)
19
+ end
20
+ end
21
+ end
22
+
23
+ describe "#execute" do
24
+ let (:wrap_foo) do
25
+ wrap = MySQLNoIo::Wraps::Blocks.new
26
+ wrap.before do
27
+ @output << "foo-before\n"
28
+ end
29
+ wrap.after do
30
+ @output << "foo-after\n"
31
+ end
32
+
33
+ wrap
34
+ end
35
+
36
+ let (:wrap_bar) do
37
+ wrap = MySQLNoIo::Wraps::Blocks.new
38
+ wrap.before do
39
+ @output << "bar-before\n"
40
+ end
41
+ wrap.after do
42
+ @output << "bar-after\n"
43
+ end
44
+
45
+ wrap
46
+ end
47
+
48
+ it "Calls the wrappers in order, and then my bits." do
49
+ @output = ""
50
+ stack.add_wrap wrap_foo
51
+ stack.add_wrap wrap_bar
52
+ stack.execute do
53
+ @output << "Cheesy filling!\n"
54
+ end
55
+
56
+ desired_output = "foo-before\n"
57
+ desired_output << "bar-before\n"
58
+ desired_output << "Cheesy filling!\n"
59
+ desired_output << "bar-after\n"
60
+ desired_output << "foo-after\n"
61
+
62
+ @output.should eq(desired_output)
63
+ end
64
+ end
65
+ end
66
+
@@ -0,0 +1,44 @@
1
+
2
+ describe MySQLNoIo::Wraps::Blocks do
3
+ subject { MySQLNoIo::Wraps::Blocks.new() }
4
+ context "accepts a before block" do
5
+ it { should respond_to :before }
6
+ end
7
+
8
+ context "accepts an after block" do
9
+ it { should respond_to :after }
10
+ end
11
+
12
+ describe "#execute" do
13
+ context "executing a block" do
14
+ let (:wrapper) { MySQLNoIo::Wraps::Blocks.new() }
15
+ let (:block_before) { Proc.new do @output << "chocolate" end }
16
+ let (:block_execute) { Proc.new do @output << "icing" end }
17
+ let (:block_after) { Proc.new do @output << "vanilla" end }
18
+
19
+ it "executes the block passed to #execute" do
20
+ @output = ""
21
+ wrapper.execute do block_execute.call end
22
+
23
+ @output.should eq("icing")
24
+ end
25
+
26
+ it "executes the before block ... before" do
27
+ @output = ""
28
+ wrapper.before do block_before.call end
29
+ wrapper.execute do block_execute.call end
30
+
31
+ @output.should eq("chocolateicing")
32
+ end
33
+
34
+ it "executes the after block ... after" do
35
+ @output = ""
36
+ wrapper.after do block_after.call end
37
+ wrapper.execute do block_execute.call end
38
+
39
+ @output.should eq("icingvanilla")
40
+ end
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,25 @@
1
+ describe MySQLNoIo::Wraps::DeactivatedSlave do
2
+ describe "#execute" do
3
+ it "Runs STOP SLAVE and START SLAVE around our command" do
4
+ @cmds = ""
5
+
6
+ mysql = double("MySQL2::Client")
7
+ mysql.should_receive(:query).exactly(2).times do |arg|
8
+ @cmds << "#{arg}\n"
9
+ end
10
+
11
+ wrap = MySQLNoIo::Wraps::DeactivatedSlave.new mysql
12
+ wrap.execute do
13
+ @cmds << "-- my command\n"
14
+ end
15
+
16
+ @cmds.should eq(<<-FOO
17
+ STOP SLAVE;
18
+ -- my command
19
+ START SLAVE;
20
+ FOO
21
+ )
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,25 @@
1
+ describe MySQLNoIo::Wraps::GlobalLock do
2
+ describe "#execute" do
3
+ it "Runs lock and unlock around our command" do
4
+ @cmds = ""
5
+
6
+ mysql = double("MySQL2::Client")
7
+ mysql.should_receive(:query).exactly(2).times do |arg|
8
+ @cmds << "#{arg}\n"
9
+ end
10
+
11
+ wrap = MySQLNoIo::Wraps::GlobalLock.new mysql
12
+ wrap.execute do
13
+ @cmds << "-- my command\n"
14
+ end
15
+
16
+ @cmds.should eq(<<-FOO
17
+ FLUSH TABLES WITH READ LOCK;
18
+ -- my command
19
+ UNLOCK TABLES;
20
+ FOO
21
+ )
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,6 @@
1
+ require 'mysqlnoio'
2
+
3
+ def project_root
4
+ File.realpath(File.dirname(__FILE__) + "/../")
5
+ end
6
+
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+
3
+ apt-get update
4
+ apt-get --yes install libmysqlclient-dev mysql-server-5.5
5
+
6
+ curl -L https://get.rvm.io | bash -s stable --autolibs=3 --ruby
7
+ source /usr/local/rvm/scripts/rvm
8
+ rvm in /vagrant do bundle install
9
+
10
+ cd /vagrant
11
+
12
+ rake
13
+