xmysql2psql 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 68e6a0393134773b9525654b5b9f943634a3616c
4
+ data.tar.gz: ac5b3c5ec00fbc003a276d0bb41c5e407d67a57e
5
+ SHA512:
6
+ metadata.gz: cffc0de72ddc657ae62f74200a3bd6a96037fec2bcee6d5606dadacdd937c20beaa26987709405f65d90e8c25a0120f5b6f1e870a282a66e6759f49802da2a8a
7
+ data.tar.gz: f224a0393a3da4496902176d2e8bfff9b84ac9c3bb54f610549c99f9e1a2af447a77b474c6312a877c279232eb08ab8c404f47d6db81be5d00fbc56d511ec0bd
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009-2010 name <email>
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,123 @@
1
+ = XMYSQL2PSQL - MySQL to PostgreSQL Migration Tool
2
+
3
+ Tool for converting mysql database to postgresql.
4
+ It can create postgresql dump from mysql database or directly load data from mysql to
5
+ postgresql (at about 100 000 records per minute).
6
+
7
+ It can translate now most data types and indexes, but if you experience some problems, feel free
8
+ to contact me, I'll help you.
9
+
10
+ == Installation
11
+ Xmysql2psql is packaged as a gem. Install as usual (use sudo if required).
12
+
13
+ $ gem install mysql2psql
14
+ NB: the gem hasn't been formally released on http://rubygems.org/ yet. For now you need to clone the git repo at http://github.com/tardate/mysql2postgres and 'rake install'
15
+
16
+ After installation, the "mysql2psql" command will be available.
17
+ When run, it will generate a default configuration file in the current directory if a config
18
+ file is not already found. The default configuration filename is mysql2psql.yml.
19
+
20
+ $ xmysql2psql
21
+
22
+ You can use an alternate config file by passing the filename/path as a parameter:
23
+
24
+ $ xmysql2psql ../another/world.yml
25
+
26
+ == Running Migrations
27
+ See the configuration file for documentation about settings. Key choices include:
28
+ * whether to dump to a file, or migrate direct to PostgreSQL
29
+ * migrate only the schema definition, only the data, or both schema and data
30
+
31
+
32
+ After editing the configuration file and launching tool, you will see something like..
33
+
34
+ Creating table friendships...
35
+ Created table friendships
36
+ Loading friendships...
37
+ 620000 of 638779 rows loaded. [ETA: 2010/01/21 21:32 (00h:00m:01s)]
38
+ 638779 rows loaded in 1min 3s
39
+ Indexing table friendships...
40
+ Indexed table friendships
41
+ Table creation 0 min, loading 1 min, indexing 0 min, total 1 min
42
+
43
+
44
+ == Database driver dependencies
45
+ Xmysql2psql uses the 'mysql2' and 'pg' gems for database connectivity.
46
+ Xmysql2psql gem installation should automatically install these too.
47
+
48
+ If you encounter any issues with db connectivity, verify that the 'mysql' and 'pg' gems are installed and working correctly first.
49
+ The 'mysql' gem in particular may need a hint on where to find the mysql headers and libraries:
50
+
51
+ $ [sudo] gem install mysql -- --with-mysql-dir=/usr/local/mysql
52
+
53
+ == Getting the source
54
+ Xmysql2psql's source is located at http://github.com/kmizu/xmysql2psql
55
+
56
+ == Running tests
57
+ If you fork/clone the project, you will want to run the test suite (and add to it if you are developing new features or fixing bugs).
58
+
59
+ A suite of tests are provided and are run using rake (rake -T for more info)
60
+ rake test:units
61
+ rake test:integration
62
+ rake test
63
+
64
+ Rake without parameters (or "rake test") will run both the unit and integration tests.
65
+
66
+ Unit tests are small standalone tests of the xmysql2psql codebase that have no external dependencies (like a database)
67
+
68
+ Integration tests require suitable mysql and postgres databases to be setup in advance.
69
+ Running the integration tests *will* rewrite data and schema for the "xmysql2psql_test" databases in mysql and postgres. Don't use this database for anything else!
70
+
71
+ mysql on localhost:3306
72
+ - database created called "xmysql2psql_test"
73
+ - user setup for "xmysql2psql" with no password
74
+ - e.g.
75
+ mysqladmin -uroot -p create xmysql2psql_test
76
+ mysql -uroot -p -e "grant all on xmysql2psql_test.* to 'mysql2psql'@'localhost';"
77
+ # verify connecction:
78
+ mysql -uxmysql2psql -e "select database(),'yes' as connected" mysql2psql_test
79
+
80
+ postgres on localhost:5432
81
+ - database created called "xmysql2psql_test"
82
+ - role (user) access setup for "xmysql2psql" with no password
83
+ - e.g.
84
+ psql postgres -c "create role xmysql2psql with login"
85
+ psql postgres -c "create database xmysql2psql_test with owner xmysql2psql encoding='UTF8'"
86
+ # verify connection:
87
+ psql xmysql2psql_test -U xmysql2psql -c "\c"
88
+
89
+ == Notes, Limitations, Outstanding Issues..
90
+
91
+ Todo:
92
+ - more tests
93
+ - release gem
94
+ - a windows cmd shim
95
+
96
+ === note from mgkimsal
97
+ I'm still having trouble with bit(1)/boolean fields
98
+ workaround I've found is to put output in file
99
+ then in VIM on file, search/replace the true/false binary fields with t/f
100
+ specifically
101
+
102
+ (reversed on 3/23 - should be ^A gets f)
103
+ :%s/^@/t/g
104
+ :%s/^A/f/g
105
+ keystrokes are ctrl-v ctrl-shift-@ to get the 'true' binary field
106
+ keystrokes are ctrl-v ctrl-shift-A to get the 'false' binary field
107
+
108
+ == Contributors
109
+ Project founded by Max Lapshin <max@maxidoors.ru>
110
+
111
+ Other contributors (in git log order):
112
+ - Anton Ageev <anton@ageev.name>
113
+ - Samuel Tribehou <cracoucax@gmail.com>
114
+ - Marco Nenciarini <marco.nenciarini@devise.it>
115
+ - James Nobis <jnobis@jnobis.controldocs.com>
116
+ - quel <github@quelrod.net>
117
+ - Holger Amann <keeney@fehu.org>
118
+ - Maxim Dobriakov <closer.main@gmail.com>
119
+ - Michael Kimsal <mgkimsal@gmail.com>
120
+ - Jacob Coby <jcoby@portallabs.com>
121
+ - Neszt Tibor <neszt@tvnetwork.hu>
122
+ - Miroslav Kratochvil <exa.exa@gmail.com>
123
+ - Paul Gallagher <gallagher.paul@gmail.com>
data/Rakefile ADDED
@@ -0,0 +1,79 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ $LOAD_PATH.unshift('lib')
5
+ require 'xmysql2psql/version'
6
+
7
+ begin
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |gem|
10
+ gem.name = "xmysql2psql"
11
+ gem.version = Xmysql2psql::Version::STRING
12
+ gem.summary = %Q{Tool for converting mysql database to postgresql}
13
+ gem.description = %Q{It can create postgresql dump from mysql database or directly load data from mysql to
14
+ postgresql (at about 100 000 records per minute). Translates most data types and indexes.}
15
+ gem.email = "mizukota@gmail.com"
16
+ gem.homepage = "http://github.com/kmizu/xmysql2psql"
17
+ gem.authors = [
18
+ "Kota Mizushima <mizukota@gmail.com>"
19
+ ]
20
+ gem.add_dependency "mysql", "= 2.8.1"
21
+ gem.add_dependency "pg", ">= 0.15.0"
22
+ gem.add_development_dependency "test-unit", ">= 2.1.1"
23
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
24
+ end
25
+ Jeweler::GemcutterTasks.new
26
+ rescue LoadError
27
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
28
+ end
29
+
30
+ require 'rake/tasklib'
31
+ require 'rake/testtask'
32
+ require 'rdoc/task'
33
+ namespace :test do
34
+ Rake::TestTask.new(:units) do |test|
35
+ test.libs << 'lib' << 'test/lib'
36
+ test.pattern = 'test/units/*test.rb'
37
+ test.verbose = true
38
+ end
39
+
40
+ Rake::TestTask.new(:integration) do |test|
41
+ test.libs << 'lib' << 'test/lib'
42
+ test.pattern = 'test/integration/*test.rb'
43
+ test.verbose = true
44
+ end
45
+ end
46
+
47
+ desc "Run all tests"
48
+ task :test do
49
+ Rake::Task['test:units'].invoke
50
+ Rake::Task['test:integration'].invoke
51
+ end
52
+
53
+ begin
54
+ require 'rcov/rcovtask'
55
+ Rcov::RcovTask.new do |test|
56
+ test.libs << 'test'
57
+ test.pattern = 'test/**/*test.rb'
58
+ test.verbose = true
59
+ end
60
+ rescue LoadError
61
+ task :rcov do
62
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
63
+ end
64
+ end
65
+
66
+
67
+ task :default => :test
68
+
69
+ require 'rake/tasklib'
70
+ require 'rake/testtask'
71
+ require 'rdoc/task'
72
+ Rake::RDocTask.new do |rdoc|
73
+ version = Xmysql2psql::Version::STRING
74
+
75
+ rdoc.rdoc_dir = 'rdoc'
76
+ rdoc.title = "xmysql2psql #{version}"
77
+ rdoc.rdoc_files.include('README*')
78
+ rdoc.rdoc_files.include('lib/**/*.rb')
79
+ end
data/bin/xmysql2psql ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require 'rubygems'
5
+ require 'xmysql2psql'
6
+
7
+ exit Xmysql2psql.new(ARGV).convert
@@ -0,0 +1,100 @@
1
+ require 'xmysql2psql/config_base'
2
+
3
+ class Xmysql2psql
4
+
5
+ class Config < ConfigBase
6
+
7
+ def initialize(configfilepath, generate_default_if_not_found = true)
8
+ unless File.exists?(configfilepath)
9
+ reset_configfile(configfilepath) if generate_default_if_not_found
10
+ if File.exists?(configfilepath)
11
+ raise Xmysql2psql::ConfigurationFileInitialized.new("\n
12
+ No configuration file found.
13
+ A new file has been initialized at: #{configfilepath}
14
+ Please review the configuration and retry..\n\n\n")
15
+ else
16
+ raise Xmysql2psql::ConfigurationFileNotFound.new("cannot load config file #{configfilepath}")
17
+ end
18
+ end
19
+ super(configfilepath)
20
+ end
21
+
22
+ def reset_configfile(filepath)
23
+ file = File.new(filepath,'w')
24
+ self.class.template.each_line do | line|
25
+ file.puts line
26
+ end
27
+ file.close
28
+ end
29
+
30
+ def self.template(to_filename = nil, include_tables = [], exclude_tables = [], supress_data = false, supress_ddl = false, force_truncate = false)
31
+ configtext = <<EOS
32
+ mysql:
33
+ hostname: localhost
34
+ port: 3306
35
+ socket: /tmp/mysql.sock
36
+ username: xmysql2psql
37
+ password:
38
+ database: xmysql2psql_test
39
+
40
+ destination:
41
+ # if file is given, output goes to file, else postgres
42
+ file: #{ to_filename ? to_filename : ''}
43
+ postgres:
44
+ hostname: localhost
45
+ port: 5432
46
+ username: xmysql2psql
47
+ password:
48
+ database: xmysql2psql_test
49
+
50
+ # if tables is given, only the listed tables will be converted. leave empty to convert all tables.
51
+ #tables:
52
+ #- table1
53
+ #- table2
54
+ EOS
55
+ if include_tables.length>0
56
+ configtext += "\ntables:\n"
57
+ include_tables.each do |t|
58
+ configtext += "- #{t}\n"
59
+ end
60
+ end
61
+ configtext += <<EOS
62
+ # if exclude_tables is given, exclude the listed tables from the conversion.
63
+ #exclude_tables:
64
+ #- table3
65
+ #- table4
66
+
67
+ EOS
68
+ if exclude_tables.length>0
69
+ configtext += "\nexclude_tables:\n"
70
+ exclude_tables.each do |t|
71
+ configtext += "- #{t}\n"
72
+ end
73
+ end
74
+ if !supress_data.nil?
75
+ configtext += <<EOS
76
+
77
+ # if supress_data is true, only the schema definition will be exported/migrated, and not the data
78
+ supress_data: #{supress_data}
79
+ EOS
80
+ end
81
+ if !supress_ddl.nil?
82
+ configtext += <<EOS
83
+
84
+ # if supress_ddl is true, only the data will be exported/imported, and not the schema
85
+ supress_ddl: #{supress_ddl}
86
+ EOS
87
+ end
88
+ if !force_truncate.nil?
89
+ configtext += <<EOS
90
+
91
+ # if force_truncate is true, forces a table truncate before table loading
92
+ force_truncate: #{force_truncate}
93
+ EOS
94
+ end
95
+ configtext
96
+ end
97
+
98
+ end
99
+
100
+ end
@@ -0,0 +1,39 @@
1
+ require 'yaml'
2
+ require 'xmysql2psql/errors'
3
+
4
+ class Xmysql2psql
5
+
6
+ class ConfigBase
7
+ attr_reader :config, :filepath
8
+
9
+ def initialize(configfilepath)
10
+ @filepath=configfilepath
11
+ @config = YAML::load(File.read(filepath))
12
+ end
13
+ def [](key)
14
+ self.send( key )
15
+ end
16
+ def method_missing(name, *args)
17
+ token=name.to_s
18
+ default = args.length>0 ? args[0] : ''
19
+ must_be_defined = default == :none
20
+ case token
21
+ when /mysql/i
22
+ key=token.sub( /^mysql/, '' )
23
+ value=config["mysql"][key]
24
+ when /pg/i
25
+ key=token.sub( /^pg/, '' )
26
+ value=config["destination"]["postgres"][key]
27
+ when /dest/i
28
+ key=token.sub( /^dest/, '' )
29
+ value=config["destination"][key]
30
+ when /only_tables/i
31
+ value=config["tables"]
32
+ else
33
+ value=config[token]
34
+ end
35
+ value.nil? ? ( must_be_defined ? (raise Xmysql2psql::UninitializedValueError.new("no value and no default for #{name}")) : default ) : value
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,55 @@
1
+ class Xmysql2psql
2
+
3
+ class Converter
4
+ attr_reader :reader, :writer, :options
5
+ attr_reader :exclude_tables, :only_tables, :supress_data, :supress_ddl, :force_truncate
6
+
7
+ def initialize(reader, writer, options)
8
+ @reader = reader
9
+ @writer = writer
10
+ @options = options
11
+ @exclude_tables = options.exclude_tables([])
12
+ @only_tables = options.only_tables(nil)
13
+ @supress_data = options.supress_data(false)
14
+ @supress_ddl = options.supress_ddl(false)
15
+ @force_truncate = options.force_truncate(false)
16
+ end
17
+
18
+ def convert
19
+ _time1 = Time.now
20
+
21
+ tables = reader.tables.
22
+ reject {|table| @exclude_tables.include?(table.name)}.
23
+ select {|table| @only_tables ? @only_tables.include?(table.name) : true}
24
+
25
+
26
+ tables.each do |table|
27
+ writer.write_table(table)
28
+ end unless @supress_ddl
29
+
30
+ _time2 = Time.now
31
+ tables.each do |table|
32
+ writer.truncate(table) if force_truncate && supress_ddl
33
+ writer.write_contents(table, reader)
34
+ end unless @supress_data
35
+
36
+ _time3 = Time.now
37
+ tables.each do |table|
38
+ writer.write_indexes(table)
39
+ end unless @supress_ddl
40
+ tables.each do |table|
41
+ writer.write_constraints(table)
42
+ end unless @supress_ddl
43
+
44
+
45
+ writer.close
46
+ _time4 = Time.now
47
+ puts "Table creation #{((_time2 - _time1) / 60).round} min, loading #{((_time3 - _time2) / 60).round} min, indexing #{((_time4 - _time3) / 60).round} min, total #{((_time4 - _time1) / 60).round} min"
48
+ return 0
49
+ rescue => e
50
+ $stderr.puts "Xmysql2psql: conversion failed: #{e.to_s}"
51
+ return -1
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,16 @@
1
+
2
+ class Xmysql2psql
3
+
4
+ class GeneralError < StandardError
5
+ end
6
+
7
+ class ConfigurationError < StandardError
8
+ end
9
+ class UninitializedValueError < ConfigurationError
10
+ end
11
+ class ConfigurationFileNotFound < ConfigurationError
12
+ end
13
+ class ConfigurationFileInitialized < ConfigurationError
14
+ end
15
+
16
+ end
@@ -0,0 +1,190 @@
1
+ require 'mysql'
2
+
3
+ class Xmysql2psql
4
+
5
+ class MysqlReader
6
+ class Field
7
+ end
8
+
9
+ class Table
10
+ attr_reader :name
11
+
12
+ def initialize(reader, name)
13
+ @reader = reader
14
+ @name = name
15
+ end
16
+
17
+ @@types = %w(tiny enum decimal short long float double null timestamp longlong int24 date time datetime year set blob string var_string char).inject({}) do |list, type|
18
+ list[eval("::Mysql::Field::TYPE_#{type.upcase}")] = type
19
+ list
20
+ end
21
+
22
+ @@types[246] = "decimal"
23
+
24
+ def columns
25
+ @columns ||= load_columns
26
+ end
27
+
28
+ def convert_type(type)
29
+ case type
30
+ when /int.* unsigned/
31
+ "bigint"
32
+ when /bigint/
33
+ "bigint"
34
+ when "bit(1)"
35
+ "boolean"
36
+ when "tinyint(1)"
37
+ "boolean"
38
+ when /tinyint/
39
+ "tinyint"
40
+ when /int/
41
+ "integer"
42
+ when /varchar/
43
+ "varchar"
44
+ when /char/
45
+ "char"
46
+ when /(float|decimal)/
47
+ "decimal"
48
+ when /double/
49
+ "double precision"
50
+ else
51
+ type
52
+ end
53
+ end
54
+
55
+ def load_columns
56
+ @reader.reconnect
57
+ result = @reader.mysql.list_fields(name)
58
+ mysql_flags = ::Mysql::Field.constants.select {|c| c =~ /FLAG/}
59
+ fields = []
60
+ @reader.mysql.query("EXPLAIN `#{name}`") do |res|
61
+ while field = res.fetch_row do
62
+ length = field[1][/\((\d+)\)/, 1] if field[1] =~ /\((\d+)\)/
63
+ length = field[1][/\((\d+),(\d+)\)/, 1] if field[1] =~ /\((\d+),(\d+)\)/
64
+ desc = {
65
+ :name => field[0],
66
+ :table_name => name,
67
+ :type => convert_type(field[1]),
68
+ :length => length && length.to_i,
69
+ :decimals => field[1][/\((\d+),(\d+)\)/, 2],
70
+ :null => field[2] == "YES",
71
+ :primary_key => field[3] == "PRI",
72
+ :auto_increment => field[5] == "auto_increment"
73
+ }
74
+ desc[:default] = field[4] unless field[4].nil?
75
+ fields << desc
76
+ end
77
+ end
78
+
79
+ fields.select {|field| field[:auto_increment]}.each do |field|
80
+ @reader.mysql.query("SELECT max(`#{field[:name]}`) FROM `#{name}`") do |res|
81
+ field[:maxval] = res.fetch_row[0].to_i
82
+ end
83
+ end
84
+ fields
85
+ end
86
+
87
+
88
+ def indexes
89
+ load_indexes unless @indexes
90
+ @indexes
91
+ end
92
+
93
+ def foreign_keys
94
+ load_indexes unless @foreign_keys
95
+ @foreign_keys
96
+ end
97
+
98
+ def load_indexes
99
+ @indexes = []
100
+ @foreign_keys = []
101
+
102
+ @reader.mysql.query("SHOW CREATE TABLE `#{name}`") do |result|
103
+ explain = result.fetch_row[1]
104
+ explain.split(/\n/).each do |line|
105
+ next unless line =~ / KEY /
106
+ index = {}
107
+ if match_data = /CONSTRAINT `(\w+)` FOREIGN KEY \(`(\w+)`\) REFERENCES `(\w+)` \(`(\w+)`\)/.match(line)
108
+ index[:name] = match_data[1]
109
+ index[:column] = match_data[2]
110
+ index[:ref_table] = match_data[3]
111
+ index[:ref_column] = match_data[4]
112
+ @foreign_keys << index
113
+ elsif match_data = /KEY `(\w+)` \((.*)\)/.match(line)
114
+ index[:name] = match_data[1]
115
+ index[:columns] = match_data[2].split(",").map {|col| col[/`(\w+)`/, 1]}
116
+ index[:unique] = true if line =~ /UNIQUE/
117
+ @indexes << index
118
+ elsif match_data = /PRIMARY KEY .*\((.*)\)/.match(line)
119
+ index[:primary] = true
120
+ index[:columns] = match_data[1].split(",").map {|col| col.strip.gsub(/`/, "")}
121
+ @indexes << index
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ def count_rows
128
+ @reader.mysql.query("SELECT COUNT(*) FROM `#{name}`") do |res|
129
+ return res.fetch_row[0].to_i
130
+ end
131
+ end
132
+
133
+ def has_id?
134
+ !!columns.find {|col| col[:name] == "id"}
135
+ end
136
+
137
+ def count_for_pager
138
+ query = has_id? ? 'MAX(id)' : 'COUNT(*)'
139
+ @reader.mysql.query("SELECT #{query} FROM `#{name}`") do |res|
140
+ return res.fetch_row[0].to_i
141
+ end
142
+ end
143
+
144
+ def query_for_pager
145
+ query = has_id? ? 'WHERE id >= ? AND id < ?' : 'LIMIT ?,?'
146
+ "SELECT #{columns.map{|c| "`"+c[:name]+"`"}.join(", ")} FROM `#{name}` #{query}"
147
+ end
148
+ end
149
+
150
+ def connect
151
+ @mysql = ::Mysql.connect(@host, @user, @passwd, @db, @port, @sock, @flag)
152
+ @mysql.query("SET NAMES utf8")
153
+ end
154
+
155
+ def reconnect
156
+ @mysql.close rescue false
157
+ connect
158
+ end
159
+
160
+ def initialize(options)
161
+ @host, @user, @passwd, @db, @port, @sock, @flag =
162
+ options.mysqlhostname('localhost'), options.mysqlusername,
163
+ options.mysqlpassword, options.mysqldatabase,
164
+ options.mysqlport, options.mysqlsocket, Mysql::CLIENT_COMPRESS
165
+ connect
166
+ end
167
+
168
+ attr_reader :mysql
169
+
170
+ def tables
171
+ @tables ||= @mysql.list_tables.map {|table| Table.new(self, table)}
172
+ end
173
+
174
+ def paginated_read(table, page_size)
175
+ count = table.count_for_pager
176
+ return if count < 1
177
+ statement = @mysql.prepare(table.query_for_pager)
178
+ counter = 0
179
+ 0.upto((count + page_size)/page_size) do |i|
180
+ statement.execute(i*page_size, table.has_id? ? (i+1)*page_size : page_size)
181
+ while row = statement.fetch
182
+ counter += 1
183
+ yield(row, counter)
184
+ end
185
+ end
186
+ counter
187
+ end
188
+ end
189
+
190
+ end