dkastner-taps 0.3.11

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Ricardo Chimal, Jr
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,51 @@
1
+ = Taps -- simple database import/export app
2
+
3
+ A simple database agnostic import/export app to transfer data to/from a remote database.
4
+
5
+ == Usage: Server
6
+
7
+ Here's how you start a taps server
8
+
9
+ $ taps server postgres://localdbuser:localdbpass@localhost/dbname httpuser httppassword
10
+
11
+ You can also specify an encoding in the database url
12
+
13
+ $ taps server mysql://localdbuser:localdbpass@localhost/dbname?encoding=latin1 httpuser httppassword
14
+
15
+ == Usage: Client
16
+
17
+ When you want to pull down a database from a taps server
18
+
19
+ $ taps pull postgres://dbuser:dbpassword@localhost/dbname http://httpuser:httppassword@example.com:5000
20
+
21
+ or when you want to push a local database to a taps server
22
+
23
+ $ taps push postgres://dbuser:dbpassword@localhost/dbname http://httpuser:httppassword@example.com:5000
24
+
25
+ or when you want to transfer a list of tables
26
+
27
+ $ taps push postgres://dbuser:dbpassword@localhost/dbname http://httpuser:httppassword@example.com:5000 --tables logs,tags
28
+
29
+ or when you want to transfer tables that start with a word
30
+
31
+ $ taps push postgres://dbuser:dbpassword@localhost/dbname http://httpuser:httppassword@example.com:5000 --filter '^log_'
32
+
33
+ == Known Issues
34
+
35
+ * Foreign Keys get lost in the schema transfer
36
+ * Tables without primary keys will be incredibly slow to transfer. This is due to it being inefficient having large offset values in queries.
37
+ * Multiple schemas are currently not supported
38
+
39
+ == Meta
40
+
41
+ Maintained by Ricardo Chimal, Jr. (ricardo at heroku dot com)
42
+
43
+ Written by Ricardo Chimal, Jr. (ricardo at heroku dot com) and Adam Wiggins (adam at heroku dot com)
44
+
45
+ Early research and inspiration by Blake Mizerany
46
+
47
+ Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
48
+
49
+ http://github.com/ricardochimal/taps
50
+
51
+ Special Thanks to Sequel for making this tool possible http://sequel.rubyforge.org/
data/Rakefile ADDED
@@ -0,0 +1,75 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |s|
4
+ s.name = "dkastner-taps"
5
+ s.summary = %Q{simple database import/export app}
6
+ s.email = 'derek@brighterplanet.com'
7
+ s.homepage = "http://github.com/dkastner/taps"
8
+ s.description = "A simple database agnostic import/export app to transfer data to/from a remote database."
9
+ s.authors = ["Ricardo Chimal, Jr.", 'Derek Kastner']
10
+
11
+ s.rubygems_version = %q{1.3.5}
12
+
13
+ s.add_dependency 'json_pure', '>= 1.2.0', '< 1.5.0'
14
+ s.add_dependency 'sinatra', '~> 1.0.0'
15
+ s.add_dependency 'rest-client', '~> 1.4.0'
16
+ s.add_dependency 'sequel', '~> 3.13.0'
17
+ s.add_dependency 'sqlite3-ruby', '~> 1.2'
18
+ s.add_dependency 'rack', '>= 1.0.1'
19
+ s.add_development_dependency 'jeweler'
20
+ s.add_development_dependency 'bacon'
21
+
22
+ s.rubyforge_project = "taps"
23
+
24
+ s.files = FileList['spec/*.rb'] + FileList['lib/**/*.rb'] + ['README.rdoc', 'LICENSE', 'VERSION.yml', 'Rakefile'] + FileList['bin/*']
25
+ s.executables = ['taps', 'schema']
26
+ end
27
+ Jeweler::GemcutterTasks.new
28
+ rescue LoadError => e
29
+ if e.message =~ /jeweler/
30
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
31
+ else
32
+ puts e.message + ' -- while loading jeweler.'
33
+ end
34
+ end
35
+
36
+ begin
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ rdoc.rdoc_dir = 'rdoc'
40
+ rdoc.title = 'taps'
41
+ rdoc.options << '--line-numbers' << '--inline-source'
42
+ rdoc.rdoc_files.include('README*')
43
+ rdoc.rdoc_files.include('lib/**/*.rb')
44
+ end
45
+ rescue LoadError
46
+ puts "Rdoc is not available"
47
+ end
48
+
49
+ begin
50
+ require 'rcov/rcovtask'
51
+ Rcov::RcovTask.new do |t|
52
+ t.libs << 'spec'
53
+ t.test_files = FileList['spec/*_spec.rb']
54
+ t.verbose = true
55
+ end
56
+ rescue LoadError
57
+ puts "RCov is not available. In order to run rcov, you must: sudo gem install rcov"
58
+ end
59
+
60
+ desc "Run all specs; requires the bacon gem"
61
+ task :spec do
62
+ if `which bacon`.empty?
63
+ puts "bacon is not available. In order to run the specs, you must: sudo gem install bacon."
64
+ else
65
+ system "bacon #{File.dirname(__FILE__)}/spec/*_spec.rb"
66
+ end
67
+ end
68
+
69
+ desc "copy/paste env vars for dev testing"
70
+ task :env do
71
+ puts "export RUBYLIB='#{File.dirname(__FILE__) + '/lib'}'"
72
+ puts "export RUBYOPT='-rrubygems'"
73
+ end
74
+
75
+ task :default => :spec
data/TODO ADDED
@@ -0,0 +1 @@
1
+ * Detect incompatible Marshal versions
data/VERSION.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :patch: 11
3
+ :build:
4
+ :major: 0
5
+ :minor: 3
data/bin/schema ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ gem 'sequel', '~> 3.13.0'
5
+
6
+ $:.unshift File.dirname(__FILE__) + '/../lib'
7
+
8
+ require 'taps/schema'
9
+
10
+ cmd = ARGV.shift.strip rescue ''
11
+ database_url = ARGV.shift.strip rescue ''
12
+
13
+ def show_usage_and_exit
14
+ puts <<EOTXT
15
+ schema console <database_url>
16
+ schema dump <database_url>
17
+ schema dump_table <database_url> <table>
18
+ schema indexes <database_url>
19
+ schema indexes_individual <database_url>
20
+ schema reset_db_sequences <database_url>
21
+ schema load <database_url> <schema_file>
22
+ schema load_indexes <database_url> <indexes_file>
23
+ EOTXT
24
+ exit(1)
25
+ end
26
+
27
+ case cmd
28
+ when 'dump'
29
+ puts Taps::Schema.dump(database_url)
30
+ when 'dump_table'
31
+ table = ARGV.shift.strip
32
+ puts Taps::Schema.dump_table(database_url, table)
33
+ when 'indexes'
34
+ puts Taps::Schema.indexes(database_url)
35
+ when 'indexes_individual'
36
+ puts Taps::Schema.indexes_individual(database_url)
37
+ when 'load_indexes'
38
+ filename = ARGV.shift.strip rescue ''
39
+ indexes = File.read(filename) rescue show_usage_and_exit
40
+ Taps::Schema.load_indexes(database_url, indexes)
41
+ when 'load'
42
+ filename = ARGV.shift.strip rescue ''
43
+ schema = File.read(filename) rescue show_usage_and_exit
44
+ Taps::Schema.load(database_url, schema)
45
+ when 'reset_db_sequences'
46
+ Taps::Schema.reset_db_sequences(database_url)
47
+ when 'console'
48
+ $db = Sequel.connect(database_url)
49
+ require 'irb'
50
+ require 'irb/completion'
51
+ IRB.start
52
+ else
53
+ show_usage_and_exit
54
+ end
data/bin/schema.cmd ADDED
@@ -0,0 +1,6 @@
1
+ @ECHO OFF
2
+ IF NOT "%~f0" == "~f0" GOTO :WinNT
3
+ @"ruby.exe" "./schema" %1 %2 %3 %4 %5 %6 %7 %8 %9
4
+ GOTO :EOF
5
+ :WinNT
6
+ @"ruby.exe" "%~dpn0" %*
data/bin/taps ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.dirname(__FILE__) + '/../lib'
4
+ require 'taps/cli'
5
+
6
+ Taps::Cli.new(ARGV.dup).run
data/lib/taps/cli.rb ADDED
@@ -0,0 +1,188 @@
1
+ require 'optparse'
2
+ require 'tempfile'
3
+ require 'json/pure'
4
+ require 'taps/monkey'
5
+ require 'taps/config'
6
+ require 'taps/log'
7
+
8
+ Taps::Config.taps_database_url = ENV['TAPS_DATABASE_URL'] || "sqlite://#{Tempfile.new('taps.db').path}"
9
+
10
+ module Taps
11
+ class Cli
12
+ attr_accessor :argv
13
+
14
+ def initialize(argv)
15
+ @argv = argv
16
+ end
17
+
18
+ def run
19
+ method = (argv.shift || 'help').to_sym
20
+ if [:pull, :push, :server, :version].include? method
21
+ send(method)
22
+ else
23
+ help
24
+ end
25
+ end
26
+
27
+ def pull
28
+ opts = clientoptparse(:pull)
29
+ Taps.log.level = Logger::DEBUG if opts[:debug]
30
+ if opts[:resume_filename]
31
+ clientresumexfer(:pull, opts)
32
+ else
33
+ clientxfer(:pull, opts)
34
+ end
35
+ end
36
+
37
+ def push
38
+ opts = clientoptparse(:push)
39
+ Taps.log.level = Logger::DEBUG if opts[:debug]
40
+ if opts[:resume_filename]
41
+ clientresumexfer(:push, opts)
42
+ else
43
+ clientxfer(:push, opts)
44
+ end
45
+ end
46
+
47
+ def server
48
+ opts = serveroptparse
49
+ Taps.log.level = Logger::DEBUG if opts[:debug]
50
+ Taps::Config.database_url = opts[:database_url]
51
+ Taps::Config.login = opts[:login]
52
+ Taps::Config.password = opts[:password]
53
+
54
+ Taps::Config.verify_database_url
55
+ require 'taps/server'
56
+ Taps::Server.run!({
57
+ :port => opts[:port],
58
+ :environment => :production,
59
+ :logging => true,
60
+ :dump_errors => true,
61
+ })
62
+ end
63
+
64
+ def version
65
+ puts Taps.version
66
+ end
67
+
68
+ def help
69
+ puts <<EOHELP
70
+ Options
71
+ =======
72
+ server Start a taps database import/export server
73
+ pull Pull a database from a taps server
74
+ push Push a database to a taps server
75
+ version Taps version
76
+
77
+ Add '-h' to any command to see their usage
78
+ EOHELP
79
+ end
80
+
81
+ def serveroptparse
82
+ opts={:port => 5000, :database_url => nil, :login => nil, :password => nil, :debug => false}
83
+ OptionParser.new do |o|
84
+ o.banner = "Usage: #{File.basename($0)} server [OPTIONS] <local_database_url> <login> <password>"
85
+ o.define_head "Start a taps database import/export server"
86
+
87
+ o.on("-p", "--port=N", "Server Port") { |v| opts[:port] = v.to_i if v.to_i > 0 }
88
+ o.on("-d", "--debug", "Enable Debug Messages") { |v| opts[:debug] = true }
89
+ o.parse!(argv)
90
+
91
+ opts[:database_url] = argv.shift
92
+ opts[:login] = argv.shift
93
+ opts[:password] = argv.shift
94
+
95
+ if opts[:database_url].nil?
96
+ $stderr.puts "Missing Database URL"
97
+ puts o
98
+ exit 1
99
+ end
100
+ if opts[:login].nil?
101
+ $stderr.puts "Missing Login"
102
+ puts o
103
+ exit 1
104
+ end
105
+ if opts[:password].nil?
106
+ $stderr.puts "Missing Password"
107
+ puts o
108
+ exit 1
109
+ end
110
+ end
111
+ opts
112
+ end
113
+
114
+ def clientoptparse(cmd)
115
+ opts={:default_chunksize => 1000, :database_url => nil, :remote_url => nil, :debug => false, :resume_filename => nil, :disable_compresion => false, :indexes_first => false}
116
+ OptionParser.new do |o|
117
+ o.banner = "Usage: #{File.basename($0)} #{cmd} [OPTIONS] <local_database_url> <remote_url>"
118
+
119
+ case cmd
120
+ when :pull
121
+ o.define_head "Pull a database from a taps server"
122
+ when :push
123
+ o.define_head "Push a database to a taps server"
124
+ end
125
+
126
+ o.on("-i", "--indexes-first", "Transfer indexes first before data") { |v| opts[:indexes_first] = true }
127
+ o.on("-r", "--resume=file", "Resume a Taps Session from a stored file") { |v| opts[:resume_filename] = v }
128
+ o.on("-c", "--chunksize=N", "Initial Chunksize") { |v| opts[:default_chunksize] = (v.to_i < 10 ? 10 : v.to_i) }
129
+ o.on("-g", "--disable-compression", "Disable Compression") { |v| opts[:disable_compression] = true }
130
+ o.on("-f", "--filter=regex", "Regex Filter for tables") { |v| opts[:table_filter] = v }
131
+ o.on("-t", "--tables=A,B,C", Array, "Shortcut to filter on a list of tables") do |v|
132
+ r_tables = v.collect { |t| "^#{t}$" }.join("|")
133
+ opts[:table_filter] = "(#{r_tables})"
134
+ end
135
+ o.on("-d", "--debug", "Enable Debug Messages") { |v| opts[:debug] = true }
136
+ o.parse!(argv)
137
+
138
+ opts[:database_url] = argv.shift
139
+ opts[:remote_url] = argv.shift
140
+
141
+ if opts[:database_url].nil?
142
+ $stderr.puts "Missing Database URL"
143
+ puts o
144
+ exit 1
145
+ end
146
+ if opts[:remote_url].nil?
147
+ $stderr.puts "Missing Remote Taps URL"
148
+ puts o
149
+ exit 1
150
+ end
151
+ end
152
+
153
+ opts
154
+ end
155
+
156
+ def clientxfer(method, opts)
157
+ database_url = opts.delete(:database_url)
158
+ remote_url = opts.delete(:remote_url)
159
+
160
+ Taps::Config.verify_database_url(database_url)
161
+
162
+ require 'taps/operation'
163
+
164
+ Taps::Operation.factory(method, database_url, remote_url, opts).run
165
+ end
166
+
167
+ def clientresumexfer(method, opts)
168
+ session = JSON.parse(File.read(opts.delete(:resume_filename)))
169
+ session.symbolize_recursively!
170
+
171
+ database_url = opts.delete(:database_url)
172
+ remote_url = opts.delete(:remote_url) || session.delete(:remote_url)
173
+
174
+ Taps::Config.verify_database_url(database_url)
175
+
176
+ require 'taps/operation'
177
+
178
+ newsession = session.merge({
179
+ :default_chunksize => opts[:default_chunksize],
180
+ :disable_compression => opts[:disable_compression],
181
+ :resume => true,
182
+ })
183
+
184
+ Taps::Operation.factory(method, database_url, remote_url, newsession).run
185
+ end
186
+
187
+ end
188
+ end
@@ -0,0 +1,47 @@
1
+ require 'sequel'
2
+ require 'sqlite3'
3
+ require 'yaml'
4
+
5
+ Sequel.datetime_class = DateTime
6
+
7
+ module Taps
8
+ def self.version_yml
9
+ @@version_yml ||= YAML.load(File.read(File.dirname(__FILE__) + '/../../VERSION.yml'))
10
+ end
11
+
12
+ def self.version
13
+ version = "#{version_yml[:major]}.#{version_yml[:minor]}.#{version_yml[:patch]}"
14
+ version += ".#{version_yml[:build]}" if version_yml[:build]
15
+ version
16
+ end
17
+
18
+ def self.compatible_version
19
+ "#{version_yml[:major]}.#{version_yml[:minor]}"
20
+ end
21
+
22
+ def self.exiting=(val)
23
+ @@exiting = val
24
+ end
25
+
26
+ def exiting?
27
+ (@@exiting ||= false) == true
28
+ end
29
+
30
+ class Config
31
+ class << self
32
+ attr_accessor :taps_database_url
33
+ attr_accessor :login, :password, :database_url, :remote_url
34
+ attr_accessor :chunksize
35
+
36
+ def verify_database_url(db_url=nil)
37
+ db_url ||= self.database_url
38
+ db = Sequel.connect(db_url)
39
+ db.tables
40
+ db.disconnect
41
+ rescue Object => e
42
+ puts "Failed to connect to database:\n #{e.class} -> #{e}"
43
+ exit 1
44
+ end
45
+ end
46
+ end
47
+ end