dkastner-taps 0.3.11

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/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