flattendb 0.0.8 → 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/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