flattendb 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  == VERSION
4
4
 
5
- This documentation refers to flattendb version 0.0.8
5
+ This documentation refers to flattendb version 0.1.0
6
6
 
7
7
 
8
8
  == DESCRIPTION
data/Rakefile CHANGED
@@ -14,7 +14,7 @@ begin
14
14
  :summary => %q{Flatten relational databases.},
15
15
  :author => %q{Jens Wille},
16
16
  :email => %q{jens.wille@uni-koeln.de},
17
- :dependencies => %w[highline libxml-ruby builder ruby-nuggets]
17
+ :dependencies => %w[libxml-ruby builder ruby-nuggets] << ['athena', '>= 0.1.5']
18
18
  }
19
19
  }}
20
20
  rescue LoadError => err
data/bin/flattendb CHANGED
@@ -28,168 +28,10 @@
28
28
  ###############################################################################
29
29
  #++
30
30
 
31
- require 'optparse'
32
-
33
- require 'rubygems'
34
- require 'highline/import'
35
- require 'nuggets/array/flatten_once'
36
-
37
- $: << File.join(File.dirname(__FILE__), '..', 'lib')
38
-
39
- require 'flattendb'
40
31
  require 'flattendb/cli'
41
32
 
42
- include FlattenDB::CLI
43
-
44
- USAGE = "Usage: #{$0} [-h|--help] [options]"
45
- abort USAGE if ARGV.empty?
46
-
47
- $global_options = {
48
- :type => :mysql,
49
- :infiles => [],
50
- :outfile => nil,
51
- :confile => nil,
52
- :keep => false,
53
- :mysql => {
54
- :intype => :xml
55
- },
56
- :mdb => {
57
- :intype => :mdb
58
- }
59
- }
60
-
61
- $opts_by_type = {
62
- :mysql => {
63
- :title => 'MySQL',
64
- :opts => lambda { |opts|
65
- opts.on('-x', '--mysql-xml', "Input file is of type XML [This is the default]") {
66
- $global_options[:mysql][:intype] = :xml
67
- }
68
- opts.on('-s', '--sql', "Input file is of type SQL") {
69
- $global_options[:mysql][:intype] = :sql
70
- }
71
- }
72
- },
73
- :mdb => {
74
- :title => 'MS Access',
75
- :opts => lambda { |opts|
76
- opts.on('-m', '--mdb', "Input file is of type MDB [This is the default]") {
77
- $global_options[:mdb][:intype] = :mdb
78
- }
79
- opts.on('-y', '--mdb-xml', "Input file is of type XML") {
80
- $global_options[:mdb][:intype] = :xml
81
- }
82
- }
83
- }
84
- }
85
-
86
- def type_options(type, opts, with_heading = true)
87
- if type == :all
88
- $opts_by_type.keys.sort_by { |t| t.to_s }.each { |t|
89
- type_options(t, opts, with_heading)
90
- }
91
- else
92
- opt = $opts_by_type[type.to_sym]
93
-
94
- if with_heading
95
- opts.separator ''
96
- opts.separator " - [#{type}] #{opt[:title]}"
97
- end
98
-
99
- opt[:opts][opts]
100
- end
101
- end
102
-
103
- OptionParser.new { |opts|
104
- opts.banner = USAGE
105
-
106
- opts.separator ''
107
- opts.separator 'Options:'
108
-
109
- if $type
110
- opts.separator " [-t, --type] TYPE OVERRIDE IN EFFECT (#{$type})"
111
- else
112
- opts.on('-t', '--type TYPE', "Type of database at hand [Default: #{$global_options[:type]}]") { |t|
113
- $global_options[:type] = t.downcase.to_sym
114
- }
115
- end
116
-
117
- opts.separator ''
118
-
119
- opts.on('-i', '--input-file FILE', "Input file; depending on the database type, this option", "may be given multiple times [REQUIRED]") { |f|
120
- $global_options[:infiles] << f
121
- }
122
-
123
- opts.on('-o', '--output-file FILE', "Output file (flat XML) [REQUIRED]") { |f|
124
- $global_options[:outfile] = f
125
- }
126
-
127
- opts.on('-c', '--config-file FILE', "Configuration file (YAML) [REQUIRED]") { |f|
128
- $global_options[:confile] = f
129
- }
130
-
131
- opts.separator ''
132
-
133
- opts.on('-k', '--keep', "Keep any intermediate files (generated XML dumps, etc.)") {
134
- $global_options[:keep] = true
135
- }
136
-
137
- opts.separator ''
138
- opts.separator 'Database-specific Options:'
139
-
140
- if $type
141
- type_options($type, opts, false)
142
- else
143
- type_options(:all, opts)
144
- end
145
-
146
- opts.separator ''
147
- opts.separator 'Generic options:'
148
-
149
- opts.on('-h', '--help', 'Print this help message and exit') {
150
- abort opts.to_s
151
- }
152
-
153
- opts.on('--version', 'Print program version and exit') {
154
- abort "#{File.basename($0)} v#{FlattenDB::VERSION}"
155
- }
156
- }.parse!
157
-
158
- $type ||= $global_options[:type]
159
- $options = $global_options[$type] || {}
160
-
161
- # Load corresponding module
162
- begin
163
- require "flattendb/types/#{$type}"
164
- rescue LoadError
165
- abort "Database type '#{$type}' is not supported at the moment."
166
- end
167
-
168
- abort "No output file specified" unless $global_options[:outfile]
169
-
170
- if confile = $global_options[:confile]
171
- abort "Configuration file not found: #{confile}" unless File.readable?(confile)
172
- else
173
- abort "No configuration file specified"
174
- end
175
-
176
- $global_options[:infiles].each { |infile|
177
- abort "Input file not found: #{infile}" unless File.readable?(infile)
178
- }
179
-
180
- $infiles = $global_options[:infiles]
181
- $outfile = $global_options[:outfile]
182
- $confile = $global_options[:confile]
183
-
184
- # Load type-specific script
185
33
  begin
186
- load File.join(File.dirname(__FILE__), "flattendb.#{$type}")
187
- rescue LoadError
188
- # silently ignore
189
- end
190
-
191
- FlattenDB[$type].new($infile || $infiles, $outfile, $confile).flatten!.to_xml
192
-
193
- unless $global_options[:keep]
194
- File.delete($dump_file) if $dump_file
34
+ FlattenDB::CLI.execute($flattendb_type, ARGV, STDIN, STDOUT, STDERR)
35
+ rescue ArgumentError => err
36
+ abort err
195
37
  end
data/bin/flattendb.mdb CHANGED
@@ -1,55 +1,7 @@
1
1
  #! /usr/bin/ruby
2
2
 
3
- #--
4
- ###############################################################################
5
- # #
6
- # flattendb -- Flatten relational databases #
7
- # #
8
- # Copyright (C) 2007-2011 University of Cologne, #
9
- # Albertus-Magnus-Platz, #
10
- # 50923 Cologne, Germany #
11
- # #
12
- # Authors: #
13
- # Jens Wille <jens.wille@uni-koeln.de> #
14
- # #
15
- # flattendb is free software; you can redistribute it and/or modify it under #
16
- # the terms of the GNU Affero General Public License as published by the Free #
17
- # Software Foundation; either version 3 of the License, or (at your option) #
18
- # any later version. #
19
- # #
20
- # flattendb is distributed in the hope that it will be useful, but WITHOUT #
21
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
22
- # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License #
23
- # for more details. #
24
- # #
25
- # You should have received a copy of the GNU Affero General Public License #
26
- # along with flattendb. If not, see <http://www.gnu.org/licenses/>. #
27
- # #
28
- ###############################################################################
29
- #++
30
-
31
- unless $type
32
- $type = :mdb
33
- load File.join(File.dirname(__FILE__), 'flattendb')
34
- else
35
- case $options[:intype]
36
- when :xml
37
- $infiles.each { |infile|
38
- unless IO.read(infile, 6) == '<?xml '
39
- abort "Input file doesn't seem to be a valid XML file, XML declaration missing: #{infile}"
40
- end
41
- }
42
- when :mdb
43
- tables_cmd = 'mdb-tables'
44
- export_cmd = 'mdb-export'
45
-
46
- require_commands(tables_cmd, export_cmd, :pkg => 'mdbtools')
47
- require_libraries('fastercsv')
48
-
49
- $infiles.map! { |infile|
50
- $dump_file
51
- }
52
- end
53
- end
3
+ ext = File.extname(__FILE__)
4
+ $flattendb_type = ext.sub(/\A\./, '')
5
+ load __FILE__.sub(/#{Regexp.escape(ext)}\z/, '')
54
6
 
55
7
  # vim:ft=ruby
data/bin/flattendb.mysql CHANGED
@@ -1,92 +1,7 @@
1
1
  #! /usr/bin/ruby
2
2
 
3
- #--
4
- ###############################################################################
5
- # #
6
- # flattendb -- Flatten relational databases #
7
- # #
8
- # Copyright (C) 2007-2011 University of Cologne, #
9
- # Albertus-Magnus-Platz, #
10
- # 50923 Cologne, Germany #
11
- # #
12
- # Authors: #
13
- # Jens Wille <jens.wille@uni-koeln.de> #
14
- # #
15
- # flattendb is free software; you can redistribute it and/or modify it under #
16
- # the terms of the GNU Affero General Public License as published by the Free #
17
- # Software Foundation; either version 3 of the License, or (at your option) #
18
- # any later version. #
19
- # #
20
- # flattendb is distributed in the hope that it will be useful, but WITHOUT #
21
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
22
- # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License #
23
- # for more details. #
24
- # #
25
- # You should have received a copy of the GNU Affero General Public License #
26
- # along with flattendb. If not, see <http://www.gnu.org/licenses/>. #
27
- # #
28
- ###############################################################################
29
- #++
30
-
31
- unless $type
32
- $type = :mysql
33
- load File.join(File.dirname(__FILE__), 'flattendb')
34
- else
35
- $infile = $infiles.first
36
-
37
- case $options[:intype]
38
- when :xml
39
- unless IO.read($infile, 6) == '<?xml '
40
- abort "Input file doesn't seem to be a valid XML file, XML declaration missing"
41
- end
42
- when :sql
43
- $dump_file = $infile.sub(/\.(?:sql|dump)(?:\.gz)?\z/i, '') << '.xml'
44
- abort "Dump file and output file are the same: #{$dump_file} = #{$outfile}" \
45
- if File.expand_path($dump_file) == File.expand_path($outfile)
46
-
47
- mysql_cmd = 'mysql'
48
- dump_cmd = 'mysqldump'
49
-
50
- require_commands(mysql_cmd, dump_cmd, :pkg => 'a suitable MySQL client')
51
- require_libraries('mysql')
52
-
53
- mysql_user = ask('Please enter the MySQL user name: ') \
54
- { |q| q.default = ENV['USER'] }
55
- mysql_pass = ask('Please enter the MySQL password for that user: ') \
56
- { |q| q.echo = false }
57
-
58
- is_root = mysql_user == 'root'
59
-
60
- temp_db = ENV['FLATTEN_DB'] || 'flattendb_temp'
61
- temp_db = "#{temp_db}_%d_%d" % [Time.now, $$] if is_root
62
-
63
- temp_user = ENV['FLATTEN_USER'] || 'flattendb_user'
64
- temp_pass = ENV['FLATTEN_PASS'] || 'flattendb_pass'
65
-
66
- input = $infile =~ /\.gz\z/i ? "<(zcat #{$infile})" : "< #{$infile}"
67
- mysql_args = "--one-database -u#{temp_user} -p#{temp_pass} #{temp_db} #{input}"
68
- dump_args = "--xml -u#{temp_user} -p#{temp_pass} #{temp_db} > #{$dump_file}"
69
-
70
- begin
71
- mysql = Mysql.real_connect('localhost', mysql_user, mysql_pass)
72
-
73
- mysql.query("CREATE DATABASE #{temp_db}")
74
- mysql.query("GRANT ALL ON #{temp_db}.* TO '#{temp_user}'@'localhost' IDENTIFIED BY '#{temp_pass}'") if is_root
75
-
76
- system("#{mysql_cmd} #{mysql_args} && #{dump_cmd} #{dump_args}")
77
- rescue Mysql::Error => err
78
- abort "ERROR #{err.errno} (#{err.sqlstate}): #{err.error}"
79
- ensure
80
- if mysql
81
- mysql.query("REVOKE ALL ON #{temp_db}.* FROM '#{temp_user}'@'localhost'") if is_root
82
- mysql.query("DROP DATABASE IF EXISTS #{temp_db}")
83
-
84
- mysql.close
85
- end
86
- end
87
-
88
- $infile = $dump_file
89
- end
90
- end
3
+ ext = File.extname(__FILE__)
4
+ $flattendb_type = ext.sub(/\A\./, '')
5
+ load __FILE__.sub(/#{Regexp.escape(ext)}\z/, '')
91
6
 
92
7
  # vim:ft=ruby
@@ -0,0 +1,120 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <ndr>
3
+ <row>
4
+ <Bla>12</Bla>
5
+ <Blub></Blub>
6
+ <ObjID>1</ObjID>
7
+ <attr1>
8
+ <ObjID>1</ObjID>
9
+ <Val>3</Val>
10
+ <attr1ID>1</attr1ID>
11
+ </attr1>
12
+ <attr2>
13
+ <ObjID>1</ObjID>
14
+ <Val>4</Val>
15
+ <attr2ID>3</attr2ID>
16
+ </attr2>
17
+ <barobject>
18
+ <Bar>1002</Bar>
19
+ <ObjID>1</ObjID>
20
+ <attr4>
21
+ <Val>0</Val>
22
+ <attr4ID>2</attr4ID>
23
+ </attr4>
24
+ <attr4ID>2</attr4ID>
25
+ </barobject>
26
+ <fooobject>
27
+ <Foo>112</Foo>
28
+ <ObjID>1</ObjID>
29
+ <attr1>
30
+ <ObjID>1</ObjID>
31
+ <Val>3</Val>
32
+ <attr1ID>1</attr1ID>
33
+ </attr1>
34
+ <attr1ID>1</attr1ID>
35
+ <attr3>
36
+ <Val>8</Val>
37
+ <attr3ID>2</attr3ID>
38
+ </attr3>
39
+ <attr3ID>2</attr3ID>
40
+ </fooobject>
41
+ </row>
42
+ <row>
43
+ <Bla>12</Bla>
44
+ <Blub>hi</Blub>
45
+ <ObjID>2</ObjID>
46
+ <attr1>
47
+ <ObjID>2</ObjID>
48
+ <Val>2</Val>
49
+ <attr1ID>2</attr1ID>
50
+ </attr1>
51
+ <attr2>
52
+ <ObjID>2</ObjID>
53
+ <Val>5</Val>
54
+ <attr2ID>2</attr2ID>
55
+ </attr2>
56
+ <barobject>
57
+ <Bar>1200</Bar>
58
+ <ObjID>2</ObjID>
59
+ <attr4>
60
+ <Val>0</Val>
61
+ <attr4ID>1</attr4ID>
62
+ </attr4>
63
+ <attr4ID>1</attr4ID>
64
+ </barobject>
65
+ <fooobject>
66
+ <Foo>122</Foo>
67
+ <ObjID>2</ObjID>
68
+ <attr1>
69
+ <ObjID>2</ObjID>
70
+ <Val>2</Val>
71
+ <attr1ID>2</attr1ID>
72
+ </attr1>
73
+ <attr1ID>2</attr1ID>
74
+ <attr3>
75
+ <Val>0</Val>
76
+ <attr3ID>1</attr3ID>
77
+ </attr3>
78
+ <attr3ID>1</attr3ID>
79
+ </fooobject>
80
+ </row>
81
+ <row>
82
+ <Bla>1</Bla>
83
+ <Blub>ho</Blub>
84
+ <ObjID>3</ObjID>
85
+ <attr1>
86
+ <ObjID>3</ObjID>
87
+ <Val>1</Val>
88
+ <attr1ID>3</attr1ID>
89
+ </attr1>
90
+ <attr2>
91
+ <ObjID>3</ObjID>
92
+ <Val>6</Val>
93
+ <attr2ID>1</attr2ID>
94
+ </attr2>
95
+ <barobject>
96
+ <Bar>1000</Bar>
97
+ <ObjID>3</ObjID>
98
+ <attr4>
99
+ <Val>9</Val>
100
+ <attr4ID>3</attr4ID>
101
+ </attr4>
102
+ <attr4ID>3</attr4ID>
103
+ </barobject>
104
+ <fooobject>
105
+ <Foo>111</Foo>
106
+ <ObjID>3</ObjID>
107
+ <attr1>
108
+ <ObjID>3</ObjID>
109
+ <Val>1</Val>
110
+ <attr1ID>3</attr1ID>
111
+ </attr1>
112
+ <attr1ID>3</attr1ID>
113
+ <attr3>
114
+ <Val>7</Val>
115
+ <attr3ID>3</attr3ID>
116
+ </attr3>
117
+ <attr3ID>3</attr3ID>
118
+ </fooobject>
119
+ </row>
120
+ </ndr>
@@ -0,0 +1,137 @@
1
+ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
2
+ /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
3
+ /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
4
+ /*!40101 SET NAMES utf8 */;
5
+ /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
6
+ /*!40103 SET TIME_ZONE='+00:00' */;
7
+ /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
8
+ /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
9
+ /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
10
+ /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
11
+
12
+ DROP TABLE IF EXISTS `object`;
13
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
14
+ /*!40101 SET character_set_client = utf8 */;
15
+ CREATE TABLE `object` (
16
+ `ObjID` int(11) NOT NULL,
17
+ `Bla` int(11) NOT NULL,
18
+ `Blub` varchar(50) DEFAULT NULL,
19
+ PRIMARY KEY (`ObjID`)
20
+ ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
21
+ /*!40101 SET character_set_client = @saved_cs_client */;
22
+
23
+ LOCK TABLES `object` WRITE;
24
+ /*!40000 ALTER TABLE `object` DISABLE KEYS */;
25
+ INSERT INTO `object` VALUES (1,12,NULL),(2,12,'hi'),(3,1,'ho');
26
+ /*!40000 ALTER TABLE `object` ENABLE KEYS */;
27
+ UNLOCK TABLES;
28
+
29
+ DROP TABLE IF EXISTS `fooobject`;
30
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
31
+ /*!40101 SET character_set_client = utf8 */;
32
+ CREATE TABLE `fooobject` (
33
+ `ObjID` int(11) NOT NULL,
34
+ `attr1ID` int(11) NOT NULL,
35
+ `attr3ID` int(11) NOT NULL,
36
+ `Foo` int(11) NOT NULL,
37
+ PRIMARY KEY (`ObjID`)
38
+ ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
39
+ /*!40101 SET character_set_client = @saved_cs_client */;
40
+
41
+ LOCK TABLES `fooobject` WRITE;
42
+ /*!40000 ALTER TABLE `fooobject` DISABLE KEYS */;
43
+ INSERT INTO `fooobject` VALUES (1,1,2,112),(2,2,1,122),(3,3,3,111);
44
+ /*!40000 ALTER TABLE `fooobject` ENABLE KEYS */;
45
+ UNLOCK TABLES;
46
+
47
+ DROP TABLE IF EXISTS `barobject`;
48
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
49
+ /*!40101 SET character_set_client = utf8 */;
50
+ CREATE TABLE `barobject` (
51
+ `ObjID` int(11) NOT NULL,
52
+ `attr4ID` int(11) NOT NULL,
53
+ `Bar` int(11) NOT NULL,
54
+ PRIMARY KEY (`ObjID`)
55
+ ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
56
+ /*!40101 SET character_set_client = @saved_cs_client */;
57
+
58
+ LOCK TABLES `barobject` WRITE;
59
+ /*!40000 ALTER TABLE `barobject` DISABLE KEYS */;
60
+ INSERT INTO `barobject` VALUES (1,2,1002),(2,1,1200),(3,3,1000);
61
+ /*!40000 ALTER TABLE `barobject` ENABLE KEYS */;
62
+ UNLOCK TABLES;
63
+
64
+ DROP TABLE IF EXISTS `attr1`;
65
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
66
+ /*!40101 SET character_set_client = utf8 */;
67
+ CREATE TABLE `attr1` (
68
+ `ObjID` int(11) NOT NULL,
69
+ `attr1ID` int(11) NOT NULL,
70
+ `Val` int(11) NOT NULL,
71
+ PRIMARY KEY (`attr1ID`)
72
+ ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
73
+ /*!40101 SET character_set_client = @saved_cs_client */;
74
+
75
+ LOCK TABLES `attr1` WRITE;
76
+ /*!40000 ALTER TABLE `attr1` DISABLE KEYS */;
77
+ INSERT INTO `attr1` VALUES (1,1,3),(2,2,2),(3,3,1);
78
+ /*!40000 ALTER TABLE `attr1` ENABLE KEYS */;
79
+ UNLOCK TABLES;
80
+
81
+ DROP TABLE IF EXISTS `attr2`;
82
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
83
+ /*!40101 SET character_set_client = utf8 */;
84
+ CREATE TABLE `attr2` (
85
+ `ObjID` int(11) NOT NULL,
86
+ `attr2ID` int(11) NOT NULL,
87
+ `Val` int(11) NOT NULL,
88
+ PRIMARY KEY (`attr2ID`)
89
+ ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
90
+ /*!40101 SET character_set_client = @saved_cs_client */;
91
+
92
+ LOCK TABLES `attr2` WRITE;
93
+ /*!40000 ALTER TABLE `attr2` DISABLE KEYS */;
94
+ INSERT INTO `attr2` VALUES (1,3,4),(2,2,5),(3,1,6);
95
+ /*!40000 ALTER TABLE `attr2` ENABLE KEYS */;
96
+ UNLOCK TABLES;
97
+
98
+ DROP TABLE IF EXISTS `attr3`;
99
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
100
+ /*!40101 SET character_set_client = utf8 */;
101
+ CREATE TABLE `attr3` (
102
+ `attr3ID` int(11) NOT NULL,
103
+ `Val` int(11) NOT NULL,
104
+ PRIMARY KEY (`attr3ID`)
105
+ ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
106
+ /*!40101 SET character_set_client = @saved_cs_client */;
107
+
108
+ LOCK TABLES `attr3` WRITE;
109
+ /*!40000 ALTER TABLE `attr3` DISABLE KEYS */;
110
+ INSERT INTO `attr3` VALUES (1,0),(2,8),(3,7);
111
+ /*!40000 ALTER TABLE `attr3` ENABLE KEYS */;
112
+ UNLOCK TABLES;
113
+
114
+ DROP TABLE IF EXISTS `attr4`;
115
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
116
+ /*!40101 SET character_set_client = utf8 */;
117
+ CREATE TABLE `attr4` (
118
+ `attr4ID` int(11) NOT NULL,
119
+ `Val` int(11) NOT NULL,
120
+ PRIMARY KEY (`attr4ID`)
121
+ ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
122
+ /*!40101 SET character_set_client = @saved_cs_client */;
123
+
124
+ LOCK TABLES `attr4` WRITE;
125
+ /*!40000 ALTER TABLE `attr4` DISABLE KEYS */;
126
+ INSERT INTO `attr4` VALUES (1,0),(2,0),(3,9);
127
+ /*!40000 ALTER TABLE `attr4` ENABLE KEYS */;
128
+ UNLOCK TABLES;
129
+ /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
130
+
131
+ /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
132
+ /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
133
+ /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
134
+ /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
135
+ /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
136
+ /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
137
+ /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
@@ -27,9 +27,8 @@
27
27
  #++
28
28
 
29
29
  require 'yaml'
30
-
31
- require 'rubygems'
32
30
  require 'builder'
31
+ require 'flattendb'
33
32
 
34
33
  module FlattenDB
35
34
 
@@ -49,8 +48,12 @@ module FlattenDB
49
48
 
50
49
  class << self
51
50
 
51
+ def to_flat!(*args)
52
+ new(*args).flatten!.to_xml
53
+ end
54
+
52
55
  def types
53
- Base.instance_variable_get :@types
56
+ Base.instance_variable_get(:@types)
54
57
  end
55
58
 
56
59
  def [](type)
@@ -67,40 +70,13 @@ module FlattenDB
67
70
 
68
71
  attr_reader :root, :config, :input, :output
69
72
 
70
- def initialize(infiles, outfile, config)
71
- config = case config
72
- when Hash
73
- config
74
- when String
75
- # assume file name
76
- YAML.load_file(config)
77
- else
78
- raise ArgumentError, "invalid config argument of type '#{config.class}'"
79
- end
73
+ def initialize(options)
74
+ config = options.select { |k, _| k.is_a?(String) }
80
75
  raise ArgumentError, "can't have more than one primary (root) table" if config.size > 1
81
76
 
82
77
  (@root, @config), _ = *config # get "first" (and only) hash element
83
78
 
84
- @input = [*infiles].map { |infile|
85
- case infile
86
- when String
87
- infile
88
- when File
89
- infile.path
90
- else
91
- raise ArgumentError, "invalid infile argument of type '#{infile.class}'"
92
- end
93
- }
94
-
95
- @output = case outfile
96
- when IO
97
- outfile
98
- when String
99
- # assume file name
100
- File.open(outfile, 'w')
101
- else
102
- raise ArgumentError, "invalid outfile argument of type '#{outfile.class}'"
103
- end
79
+ @input, @output = options.values_at(:input, :output)
104
80
  end
105
81
 
106
82
  def flatten!(*args)
data/lib/flattendb/cli.rb CHANGED
@@ -26,52 +26,212 @@
26
26
  ###############################################################################
27
27
  #++
28
28
 
29
+ require 'optparse'
30
+ require 'yaml'
31
+ require 'zlib'
32
+ require 'flattendb'
33
+
29
34
  module FlattenDB
30
35
 
31
- module CLI
36
+ class CLI
32
37
 
33
- def require_libraries(*libraries)
34
- parse_arguments(libraries, :gem).each { |lib, gem|
35
- begin
36
- require lib
37
- rescue LoadError
38
- abort_with_msg('Ruby library not found: %s', lib, 'Please install gem %s first', gem)
39
- end
38
+ USAGE = "Usage: #{$0} [-h|--help] [options]"
39
+
40
+ DEFAULTS = {
41
+ :input => '-',
42
+ :inputs => [],
43
+ :output => '-',
44
+ :config => 'config.yaml'
45
+ }
46
+
47
+ TYPES = {
48
+ :mysql => {
49
+ :title => 'MySQL',
50
+ :opts => lambda { |opts, options|
51
+ opts.on('-x', '--xml', 'Input file is of type XML [This is the default]') {
52
+ options[:type] = :xml
53
+ }
54
+ opts.on('-s', '--sql', 'Input file is of type SQL') {
55
+ options[:type] = :sql
56
+ }
57
+ }
58
+ },
59
+ :mdb => {
60
+ :title => 'MS Access',
61
+ :opts => lambda { |opts, options|
62
+ opts.separator(" NOTE: Repeat '-i' for each .mdb file")
63
+ }
40
64
  }
65
+ }
66
+
67
+ def self.execute(type = nil, *args)
68
+ new(type).execute(*args)
41
69
  end
42
70
 
43
- def require_commands(*commands)
44
- parse_arguments(commands, :pkg).each { |cmd, pkg|
45
- catch :cmd_found do
46
- ENV['PATH'].split(':').each { |path|
47
- throw :cmd_found if File.executable?(File.join(path, cmd))
48
- }
71
+ attr_reader :type, :options, :config, :defaults
72
+ attr_reader :stdin, :stdout, :stderr
49
73
 
50
- abort_with_msg("Command not found: #{cmd}", "Please install #{pkg} first", pkg || cmd)
51
- end
52
- }
74
+ def initialize(type = nil, defaults = DEFAULTS)
75
+ @defaults = defaults
76
+
77
+ reset(type)
78
+
79
+ # prevent backtrace on ^C
80
+ trap(:INT) { exit 130 }
53
81
  end
54
82
 
55
- def abort_with_msg(msg1, arg1, msg2, arg2)
56
- msg = msg1 % arg1
57
- msg += " (#{msg2})" % (arg2 || arg1)
83
+ def execute(arguments = [], *inouterr)
84
+ reset(type, *inouterr)
85
+
86
+ parse_options(arguments, defaults)
58
87
 
59
- abort msg
88
+ if type
89
+ require "flattendb/types/#{type}"
90
+ else
91
+ abort 'Database type is required!'
92
+ end
93
+
94
+ options[:input] = if type == :mdb
95
+ if options[:inputs].empty?
96
+ if arguments.empty?
97
+ options[:inputs] << options[:input]
98
+ else
99
+ options[:inputs].concat(arguments)
100
+ arguments.clear
101
+ end
102
+ end
103
+
104
+ options[:inputs].map! { |file| open_file_or_std(file) }
105
+ else
106
+ open_file_or_std(
107
+ options[:inputs].last || arguments.shift || options[:input]
108
+ )
109
+ end
110
+
111
+ abort USAGE unless arguments.empty?
112
+
113
+ options[:output] = open_file_or_std(options[:output], true)
114
+
115
+ FlattenDB[type].to_flat!(options)
116
+ ensure
117
+ options[:output].close if options[:output].is_a?(Zlib::GzipWriter)
118
+ end
119
+
120
+ def reset(type = nil, stdin = STDIN, stdout = STDOUT, stderr = STDERR)
121
+ @stdin, @stdout, @stderr = stdin, stdout, stderr
122
+ self.type, @options, @config = type, {}, {}
60
123
  end
61
124
 
62
125
  private
63
126
 
64
- def parse_arguments(arguments, option)
65
- options = arguments.last.is_a?(Hash) ? arguments.pop : {}
66
- special = options.delete(option)
127
+ def type=(type)
128
+ if type
129
+ @type = type.to_s.downcase.to_sym
130
+ abort "Database type not supported: #{type}" unless TYPES.has_key?(@type)
131
+ else
132
+ @type = nil
133
+ end
134
+ end
135
+
136
+ def open_file_or_std(file, write = false)
137
+ if file == '-'
138
+ write ? stdout : stdin
139
+ else
140
+ gz = file =~ /\.gz\z/i
141
+
142
+ if write
143
+ gz ? Zlib::GzipWriter.open(file) : File.open(file, 'w')
144
+ else
145
+ abort "No such file: #{file}" unless File.readable?(file)
146
+ (gz ? Zlib::GzipReader : File).open(file)
147
+ end
148
+ end
149
+ end
150
+
151
+ def warn(msg, output = stderr)
152
+ output.puts(msg)
153
+ end
154
+
155
+ def abort(msg = nil, status = 1, output = stderr)
156
+ warn(msg, output) if msg
157
+ exit(status)
158
+ end
159
+
160
+ def parse_options(arguments, defaults)
161
+ option_parser(defaults).parse!(arguments)
162
+
163
+ config_file = options[:config] || defaults[:config]
164
+ @config = YAML.load_file(config_file) if File.readable?(config_file)
165
+
166
+ [config, defaults].each { |hash| hash.each { |key, value| options[key] ||= value } }
167
+ end
168
+
169
+ def option_parser(defaults)
170
+ sorted_types = TYPES.keys.sort_by { |t| t.to_s }
171
+
172
+ OptionParser.new { |opts|
173
+ opts.banner = USAGE
174
+
175
+ if type
176
+ opts.separator ''
177
+ opts.separator "TYPE = #{type} (#{TYPES[type][:title]})"
178
+ end
179
+
180
+ opts.separator ''
181
+ opts.separator 'Options:'
182
+
183
+ unless type
184
+ opts.on('-t', '--type TYPE', 'Type of database [REQUIRED]') { |type|
185
+ self.type = type
186
+ }
187
+
188
+ opts.separator ''
189
+ end
190
+
191
+ opts.on('-i', '--input FILE', 'Input file(s) [Default: STDIN]') { |input|
192
+ (options[:inputs] ||= []) << input
193
+ }
194
+
195
+ opts.on('-o', '--output FILE', 'Output file (flat XML) [Default: STDOUT]') { |output|
196
+ options[:output] = output
197
+ }
198
+
199
+ opts.on('-c', '--config FILE', "Configuration file (YAML) [Default: #{defaults[:config]}#{' (currently not present)' unless File.readable?(defaults[:config])}]") { |config|
200
+ options[:config] = config
201
+ }
202
+
203
+ opts.separator ''
204
+ opts.separator 'Database-specific options:'
205
+
206
+ type ? type_options(opts) : sorted_types.each { |t| type_options(opts, true, t) }
67
207
 
68
- arguments.map { |arg|
69
- [arg, special]
70
- } + options.map { |args, spx|
71
- [*args].map { |arg|
72
- [arg, spx]
208
+ opts.separator ''
209
+ opts.separator 'Generic options:'
210
+
211
+ opts.on('-h', '--help', 'Print this help message and exit') {
212
+ abort opts.to_s
73
213
  }
74
- }.flatten_once
214
+
215
+ opts.on('--version', 'Print program version and exit') {
216
+ abort "#{File.basename($0)} v#{FlattenDB::VERSION}"
217
+ }
218
+
219
+ unless type
220
+ opts.separator ''
221
+ opts.separator "Supported database types: #{sorted_types.map { |t| "#{t} (#{TYPES[t][:title]})" }.join(', ')}."
222
+ end
223
+ }
224
+ end
225
+
226
+ def type_options(opts, heading = false, type = type)
227
+ cfg = TYPES[type]
228
+
229
+ if heading
230
+ opts.separator ''
231
+ opts.separator " - [#{type}] #{cfg[:title]}"
232
+ end
233
+
234
+ cfg[:opts][opts, options]
75
235
  end
76
236
 
77
237
  end
@@ -26,16 +26,28 @@
26
26
  ###############################################################################
27
27
  #++
28
28
 
29
- require 'flattendb/base'
29
+ require 'fastercsv'
30
+ require 'nuggets/file/which'
31
+ require 'flattendb'
30
32
 
31
33
  module FlattenDB
32
34
 
33
35
  class MDB < Base
34
36
 
35
- JOIN_KEY = '@key'
36
-
37
- def initialize(infiles, outfile, config)
37
+ def initialize(options)
38
38
  super
39
+ parse
40
+ end
41
+
42
+ def parse
43
+ tables_cmd, export_cmd = 'mdb-tables', 'mdb-export'
44
+
45
+ [tables_cmd, export_cmd].each { |cmd|
46
+ next if File.which(cmd)
47
+ abort "Command not found: #{cmd}! Please install `mdbtools' first."
48
+ }
49
+
50
+ # ...
39
51
  end
40
52
 
41
53
  def flatten!(options = {}, builder_options = {})
@@ -27,8 +27,8 @@
27
27
  #++
28
28
 
29
29
  require 'libxml'
30
-
31
- require 'flattendb/base'
30
+ require 'athena'
31
+ require 'flattendb'
32
32
 
33
33
  module FlattenDB
34
34
 
@@ -36,22 +36,21 @@ module FlattenDB
36
36
 
37
37
  JOIN_KEY = '@key'
38
38
 
39
- attr_reader :document, :database, :name, :tables, :builder
39
+ attr_reader :type, :name, :tables, :builder
40
40
 
41
- def initialize(infile, outfile, config)
41
+ def initialize(options)
42
42
  super
43
43
 
44
- @document = LibXML::XML::Document.file(@input.first)
45
- @database = @document.root.find_first('database[@name]')
46
- @name = @database[:name]
47
- @tables = {}
44
+ @type = options[:type] || :xml
45
+ @name, @tables = parse
46
+ end
48
47
 
49
- parse
48
+ def parse(tables = {})
49
+ [send("parse_#{type}", tables) || 'root', tables]
50
50
  end
51
51
 
52
52
  def flatten!(options = {}, builder_options = {})
53
53
  flatten_tables!(tables, root, config)
54
-
55
54
  self
56
55
  end
57
56
 
@@ -76,9 +75,12 @@ module FlattenDB
76
75
 
77
76
  private
78
77
 
79
- def parse
78
+ def parse_xml(tables)
79
+ document = LibXML::XML::Document.io(input)
80
+ database = document.root.find_first('database[@name]')
81
+
80
82
  database.find('table_data[@name]').each { |table|
81
- rows = []
83
+ rows = tables[table[:name]] ||= []
82
84
 
83
85
  table.find('row').each { |row|
84
86
  fields = {}
@@ -89,9 +91,44 @@ module FlattenDB
89
91
 
90
92
  rows << fields
91
93
  }
94
+ }
92
95
 
93
- tables[table[:name]] = rows
96
+ database[:name]
97
+ end
98
+
99
+ def parse_sql(tables)
100
+ columns, table, name = Hash.new { |h, k| h[k] = [] }, nil, nil
101
+ parser = Athena::Formats::MySQL::SQLParser.new
102
+
103
+ input.each { |line|
104
+ case line
105
+ when /\AUSE\s+`(.+?)`/i
106
+ raise 'dump file contains more than one database' if name
107
+ name = $1
108
+ when /\ACREATE\s+TABLE\s+`(.+?)`/i
109
+ table = $1
110
+ when /\A\s+`(.+?)`/i
111
+ columns[table] << $1 if table
112
+ when /\A\).*;\Z/
113
+ table = nil
114
+ when /\AINSERT\s+INTO\s+`(.+?)`\s+VALUES\s*(.*);\Z/i
115
+ _columns = columns[_table = $1]
116
+ next if _columns.empty?
117
+
118
+ parser.parse($2) { |row|
119
+ fields = {}
120
+
121
+ row.each_with_index { |value, index|
122
+ column = _columns[index] or next
123
+ fields[column] = value.to_s
124
+ }
125
+
126
+ (tables[_table] ||= []) << fields
127
+ }
128
+ end
94
129
  }
130
+
131
+ name
95
132
  end
96
133
 
97
134
  def flatten_tables!(tables, primary_table, config)
@@ -144,7 +181,7 @@ module FlattenDB
144
181
  builder.tag!(table) {
145
182
  rows.each { |row|
146
183
  row_to_xml('row', row, builder)
147
- }
184
+ } if rows
148
185
  }
149
186
  end
150
187
 
@@ -3,8 +3,8 @@ module FlattenDB
3
3
  module Version
4
4
 
5
5
  MAJOR = 0
6
- MINOR = 0
7
- TINY = 8
6
+ MINOR = 1
7
+ TINY = 0
8
8
 
9
9
  class << self
10
10
 
data/lib/flattendb.rb CHANGED
@@ -27,10 +27,13 @@
27
27
  #++
28
28
 
29
29
  require 'flattendb/version'
30
- require 'flattendb/base'
31
30
 
32
31
  module FlattenDB
33
32
 
33
+ autoload :Base, 'flattendb/base'
34
+ autoload :MDB, 'flattendb/types/mdb'
35
+ autoload :MySQL, 'flattendb/types/mysql'
36
+
34
37
  extend self
35
38
 
36
39
  def [](type)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flattendb
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 8
10
- version: 0.0.8
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jens Wille
@@ -15,10 +15,10 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-07-04 00:00:00 Z
18
+ date: 2011-07-12 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
- name: highline
21
+ name: libxml-ruby
22
22
  prerelease: false
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
24
  none: false
@@ -32,7 +32,7 @@ dependencies:
32
32
  type: :runtime
33
33
  version_requirements: *id001
34
34
  - !ruby/object:Gem::Dependency
35
- name: libxml-ruby
35
+ name: builder
36
36
  prerelease: false
37
37
  requirement: &id002 !ruby/object:Gem::Requirement
38
38
  none: false
@@ -46,7 +46,7 @@ dependencies:
46
46
  type: :runtime
47
47
  version_requirements: *id002
48
48
  - !ruby/object:Gem::Dependency
49
- name: builder
49
+ name: ruby-nuggets
50
50
  prerelease: false
51
51
  requirement: &id003 !ruby/object:Gem::Requirement
52
52
  none: false
@@ -60,17 +60,19 @@ dependencies:
60
60
  type: :runtime
61
61
  version_requirements: *id003
62
62
  - !ruby/object:Gem::Dependency
63
- name: ruby-nuggets
63
+ name: athena
64
64
  prerelease: false
65
65
  requirement: &id004 !ruby/object:Gem::Requirement
66
66
  none: false
67
67
  requirements:
68
68
  - - ">="
69
69
  - !ruby/object:Gem::Version
70
- hash: 3
70
+ hash: 17
71
71
  segments:
72
72
  - 0
73
- version: "0"
73
+ - 1
74
+ - 5
75
+ version: 0.1.5
74
76
  type: :runtime
75
77
  version_requirements: *id004
76
78
  description: Flatten relational databases.
@@ -100,6 +102,7 @@ files:
100
102
  - Rakefile
101
103
  - COPYING
102
104
  - example/mysql-sample.flat.xml
105
+ - example/mysql-sample.flat-sql.xml
103
106
  - example/mysql-sample2flat.yaml
104
107
  - example/mysql-sample.xml
105
108
  - example/mysql-sample.sql
@@ -111,7 +114,7 @@ rdoc_options:
111
114
  - --charset
112
115
  - UTF-8
113
116
  - --title
114
- - flattendb Application documentation (v0.0.8)
117
+ - flattendb Application documentation (v0.1.0)
115
118
  - --main
116
119
  - README
117
120
  - --line-numbers