icebox 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/Gemfile +1 -0
  2. data/Rakefile +2 -2
  3. data/VERSION +1 -1
  4. data/icebox.gemspec +12 -8
  5. data/lib/icebox.rb +70 -19
  6. data/test/test_icebox.rb +80 -19
  7. metadata +77 -60
data/Gemfile CHANGED
@@ -11,5 +11,6 @@ group :development do
11
11
  gem "shoulda", ">= 0"
12
12
  gem "bundler", "~> 1.0.0"
13
13
  gem "jeweler", "~> 1.6.4"
14
+ gem "rdoc", "~> 3.11"
14
15
  gem "rcov", ">= 0"
15
16
  end
data/Rakefile CHANGED
@@ -47,8 +47,8 @@ end
47
47
 
48
48
  task :default => :test
49
49
 
50
- require 'rake/rdoctask'
51
- Rake::RDocTask.new do |rdoc|
50
+ require 'rdoc/task'
51
+ RDoc::Task.new do |rdoc|
52
52
  version = File.exist?('VERSION') ? File.read('VERSION') : ""
53
53
 
54
54
  rdoc.rdoc_dir = 'rdoc'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.0.3
data/icebox.gemspec CHANGED
@@ -4,14 +4,15 @@
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
- s.name = "icebox"
8
- s.version = "0.0.2"
7
+ s.name = %q{icebox}
8
+ s.version = "0.0.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Riley Goodside"]
12
- s.date = "2011-10-05"
13
- s.description = "Ruby library and command-line utility for working with Infobright Community Edition"
14
- s.email = "riley.goodside@gmail.com"
12
+ s.date = %q{2011-10-25}
13
+ s.default_executable = %q{icebox}
14
+ s.description = %q{Ruby library and command-line utility for working with Infobright Community Edition}
15
+ s.email = %q{riley.goodside@gmail.com}
15
16
  s.executables = ["icebox"]
16
17
  s.extra_rdoc_files = [
17
18
  "LICENSE.txt",
@@ -32,11 +33,11 @@ Gem::Specification.new do |s|
32
33
  "test/helper.rb",
33
34
  "test/test_icebox.rb"
34
35
  ]
35
- s.homepage = "http://github.com/goodside/icebox"
36
+ s.homepage = %q{http://github.com/goodside/icebox}
36
37
  s.licenses = ["MIT"]
37
38
  s.require_paths = ["lib"]
38
- s.rubygems_version = "1.8.10"
39
- s.summary = "Multitool for working with Infobright Community Edition"
39
+ s.rubygems_version = %q{1.6.2}
40
+ s.summary = %q{Multitool for working with Infobright Community Edition}
40
41
 
41
42
  if s.respond_to? :specification_version then
42
43
  s.specification_version = 3
@@ -47,6 +48,7 @@ Gem::Specification.new do |s|
47
48
  s.add_development_dependency(%q<shoulda>, [">= 0"])
48
49
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
49
50
  s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
51
+ s.add_development_dependency(%q<rdoc>, ["~> 3.11"])
50
52
  s.add_development_dependency(%q<rcov>, [">= 0"])
51
53
  else
52
54
  s.add_dependency(%q<mysql>, ["~> 2.8.1"])
@@ -54,6 +56,7 @@ Gem::Specification.new do |s|
54
56
  s.add_dependency(%q<shoulda>, [">= 0"])
55
57
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
56
58
  s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
59
+ s.add_dependency(%q<rdoc>, ["~> 3.11"])
57
60
  s.add_dependency(%q<rcov>, [">= 0"])
58
61
  end
59
62
  else
@@ -62,6 +65,7 @@ Gem::Specification.new do |s|
62
65
  s.add_dependency(%q<shoulda>, [">= 0"])
63
66
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
64
67
  s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
68
+ s.add_dependency(%q<rdoc>, ["~> 3.11"])
65
69
  s.add_dependency(%q<rcov>, [">= 0"])
66
70
  end
67
71
  end
data/lib/icebox.rb CHANGED
@@ -1,37 +1,88 @@
1
1
  require 'sequel'
2
+ require 'yaml'
2
3
 
3
4
  module Icebox
5
+ def self.new(*args, &block)
6
+ Icebox.new(*args, &block)
7
+ end
8
+
4
9
  class Icebox
5
- def initialize(db)
6
- @db = db || Sequel.mysql('test', socket: '/tmp/mysql-ib.sock')
10
+ attr_accessor :db
11
+
12
+ def initialize(db = nil)
13
+ # Icebox is configurable using a YAML file in /etc/icebox.conf
14
+ # The root-level key :connections should be an array of option sets to be
15
+ # passed to the Sequel#mysql method. Right now, only the first is used.
16
+
17
+ @db = db
18
+ if @db.nil?
19
+ begin
20
+ opts = YAML.load(File.open("/etc/icebox.conf"))[:connections][0]
21
+ rescue
22
+ opts = {database: 'test', socket: '/tmp/mysql-ib.sock'}
23
+ end
24
+ @db = Sequel.mysql(opts)
25
+ end
26
+ end
27
+
28
+ def load_data_infile(table, path=nil, opts={})
29
+ # If a handle object is given, start writing it out into a new FIFO pipe
30
+ # from within a forked subprocess before we start loading:
31
+ if opts[:handle]
32
+ pipe = "/tmp/icebox_pipe_for_#{table}_at_#{Time.now.to_i}"
33
+ system "mkfifo #{pipe}"
34
+ opts[:fifo] = pipe
35
+ pid = fork do
36
+ File.open(pipe, 'a') do |p|
37
+ p.write opts[:handle].read
38
+ end
39
+ end
40
+ end
41
+
42
+ path = path || opts[:path] || opts[:fifo]
43
+ raise "load_data_infile called with no input source" unless path
44
+
45
+ load_sql = <<-SQL
46
+ #{"SET @bh_pipemode = 'server';" if opts[:fifo]}
47
+ LOAD DATA INFILE '#{path}' INTO TABLE #{table}
48
+ FIELDS TERMINATED BY ',' ENCLOSED BY '"' ESCAPED BY '\\\\';
49
+ SQL
50
+ # Force value retrieval to delay function return until query finishes:
51
+ @db[load_sql].all
52
+ Process.waitpid(pid) if pid # Wait for writer too if it exists
53
+ end
54
+
55
+ def insert_into(table, sql)
56
+ pipe = "/tmp/icebox_pipe_for_#{table}"
57
+ sql.sub! /\s*;\s*/, '' # Chop off semicolon if present
58
+
59
+ # Simultaneously export and re-import CSV through FIFO pipe:
60
+ @db.disconnect # Needed for fork; Reconnects automatically
61
+ pid1 = fork do
62
+ @db << <<-SQL
63
+ SET @bh_pipemode = 'client';
64
+ #{sql} INTO OUTFILE '#{pipe}'
65
+ FIELDS TERMINATED BY ',' ENCLOSED BY '"' ESCAPED BY '\\\\'
66
+ SQL
67
+ end
68
+ pid2 = fork do
69
+ load_data_infile table, pipe, fifo: true
70
+ end
71
+ Process.waitpid(pid1)
72
+ Process.waitpid(pid2)
7
73
  end
8
74
 
9
75
  def create_table_as(table, sql)
10
76
  view = "icebox_view_for_#{table}"
11
- pipe = "/tmp/icebox_pipe_for_#{table}"
12
77
 
13
78
  # Create view from SQL, extract layout, and make new table from it:
14
79
  @db.create_or_replace_view view, sql
15
80
  fields_definition = @db.schema(view).map do |col|
16
81
  "#{col[0]} #{col[1][:db_type]}"
17
82
  end.join(',')
18
- @db.run "CREATE TABLE #{table} (#{fields_definition})"
19
-
20
- # Simultaneously export and re-import CSV through FIFO pipe:
21
- @db.disconnect # Needed for fork; Reconnects automatically
22
- fork do
23
- @db.run <<-SQL
24
- SET @bh_pipemode = 'server';
25
- LOAD DATA INFILE '#{pipe}' INTO TABLE #{table}
26
- FIELDS TERMINATED BY ',' ENCLOSED BY '"' ESCAPED BY '\\\\';
27
- SQL
28
- end
29
- @db.run <<-SQL
30
- SET @bh_pipemode = 'client';
31
- SELECT * FROM #{view} INTO OUTFILE '#{pipe}'
32
- FIELDS TERMINATED BY ',' ENCLOSED BY '"' ESCAPED BY '\\\\'
33
- SQL
83
+ @db << "CREATE TABLE #{table} (#{fields_definition})"
34
84
 
85
+ insert_into table, "SELECT * FROM #{view}"
35
86
  @db.drop_view view
36
87
  end
37
88
  end
data/test/test_icebox.rb CHANGED
@@ -1,29 +1,90 @@
1
1
  require 'helper'
2
2
 
3
3
  class TestIcebox < Test::Unit::TestCase
4
- context "a basic CSV file" do
4
+ context "A clean, default Icebox instance" do
5
5
  setup do
6
- @db = Sequel.mysql('test', socket: '/tmp/mysql-ib.sock')
7
- csv_body = <<-CSV.gsub(/^\s*/, '')
8
- 0,"Every good boy deserves fudge."
9
- 1,"A stitch in time saves nine."
10
- 2,""
11
- CSV
12
- File.open('/tmp/input_table.csv', 'w') { |f| f.write csv_body }
13
- @db.run <<-SQL
14
- DROP TABLE IF EXISTS input_table;
15
- CREATE TABLE input_table (number INTEGER, line TEXT);
16
- LOAD DATA INFILE '/tmp/input_table.csv' INTO TABLE input_table
17
- FIELDS TERMINATED BY ',' ENCLOSED BY '\\"';
6
+ @box = Icebox.new
7
+ end
8
+ should "have an internal Sequel MySQL DB accessible via #db" do
9
+ assert_equal @box.db.class, Sequel::MySQL::Database
10
+ end
11
+ end
12
+
13
+ context "Given a CSV file," do
14
+ setup do
15
+ File.open('/tmp/test_table.csv', 'w') do |f|
16
+ f.write <<-CSV.gsub(/^\s*/, '')
17
+ -127,-127,-32767,-8388608,-2147483647,-9223372036854775806,-3.402823466E+38,-1.7976931348623157E+308,-8675309.8675309,"Negative"
18
+ 127,127,32767,8388608,2147483647,9223372036854775806,3.402823466E+38,1.7976931348623157E+308,8675309.8675309,"Positive"
19
+ 0,0,0,0,0,0,0.0,0.0,0.0,"Zero"
20
+ CSV
21
+ end
22
+ @box = Icebox.new
23
+ @db = @box.db
24
+ @db << <<-SQL
25
+ CREATE TABLE test_table (
26
+ ti TINYINT,
27
+ b BOOLEAN,
28
+ si SMALLINT,
29
+ mi MEDIUMINT,
30
+ i INTEGER,
31
+ bi BIGINT,
32
+ f FLOAT,
33
+ dp DOUBLE PRECISION,
34
+ dc DEC(14,7),
35
+ tx TEXT
36
+ )
18
37
  SQL
19
38
  end
20
39
 
21
- should "copy a table with create_table_as" do
22
- box = Icebox::Icebox.new(@db)
23
- @db.run "DROP TABLE IF EXISTS output_table"
24
- box.create_table_as "output_table", "SELECT * FROM input_table"
25
- result = @db["SELECT COUNT(*) AS ct FROM output_table"].first[:ct]
26
- assert_equal result, 3
40
+ should "load data from CSV and copy it with #create_table_as" do
41
+ @box.load_data_infile 'test_table', '/tmp/test_table.csv'
42
+ @box.create_table_as 'test_ctas_output', 'SELECT * FROM test_table'
43
+
44
+ t = @db[:test_ctas_output]
45
+ assert_equal 3, t.count
46
+ assert_equal -127, t.order(:ti).get(:ti)
47
+ assert_equal "Negative", t.order(:si).get(:tx)
48
+ assert_equal BigDecimal.new("8675309.8675309"), t.order(:dc.desc).get(:dc)
49
+ end
50
+
51
+ should "append loaded CSV data to an existing table" do
52
+ @box.load_data_infile 'test_table', '/tmp/test_table.csv'
53
+ count_1 = @db[:test_table].count
54
+ @box.load_data_infile 'test_table', '/tmp/test_table.csv'
55
+ count_2 = @db[:test_table].count
56
+
57
+ assert_equal 3, count_1
58
+ assert_equal 6, count_2
59
+ end
60
+
61
+ should "append a query result onto existing data via #insert_into" do
62
+ @box.load_data_infile 'test_table', '/tmp/test_table.csv'
63
+ count_1 = @db[:test_table].count
64
+ @box.insert_into 'test_table', 'SELECT * FROM test_table'
65
+ count_2 = @db[:test_table].count
66
+
67
+ assert_equal 3, count_1
68
+ assert_equal 6, count_2
69
+ end
70
+
71
+ should "not have race conditions when #insert_into is called rapidly" do
72
+ @box.load_data_infile 'test_table', '/tmp/test_table.csv'
73
+ count_1 = @db[:test_table].count
74
+ 5.times do
75
+ @box.insert_into 'test_table', 'SELECT * FROM test_table'
76
+ end
77
+ count_2 = @db[:test_table].count
78
+
79
+ assert_equal 3, count_1
80
+ assert_equal 3 * (2**5), count_2
81
+ end
82
+
83
+ teardown do
84
+ File.delete '/tmp/test_table.csv'
85
+ @db.drop_table 'test_table'
86
+ @db.drop_table 'test_ctas_output' rescue Sequel
27
87
  end
28
88
  end
89
+
29
90
  end
metadata CHANGED
@@ -1,92 +1,105 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: icebox
3
- version: !ruby/object:Gem::Version
4
- version: 0.0.2
3
+ version: !ruby/object:Gem::Version
5
4
  prerelease:
5
+ version: 0.0.3
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Riley Goodside
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-05 00:00:00.000000000Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
12
+
13
+ date: 2011-10-25 00:00:00 -04:00
14
+ default_executable: icebox
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
15
17
  name: mysql
16
- requirement: &17081840 !ruby/object:Gem::Requirement
18
+ requirement: &id001 !ruby/object:Gem::Requirement
17
19
  none: false
18
- requirements:
20
+ requirements:
19
21
  - - ~>
20
- - !ruby/object:Gem::Version
22
+ - !ruby/object:Gem::Version
21
23
  version: 2.8.1
22
24
  type: :development
23
25
  prerelease: false
24
- version_requirements: *17081840
25
- - !ruby/object:Gem::Dependency
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
26
28
  name: sequel
27
- requirement: &17081320 !ruby/object:Gem::Requirement
29
+ requirement: &id002 !ruby/object:Gem::Requirement
28
30
  none: false
29
- requirements:
31
+ requirements:
30
32
  - - ~>
31
- - !ruby/object:Gem::Version
33
+ - !ruby/object:Gem::Version
32
34
  version: 3.27.0
33
35
  type: :development
34
36
  prerelease: false
35
- version_requirements: *17081320
36
- - !ruby/object:Gem::Dependency
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
37
39
  name: shoulda
38
- requirement: &17080800 !ruby/object:Gem::Requirement
40
+ requirement: &id003 !ruby/object:Gem::Requirement
39
41
  none: false
40
- requirements:
41
- - - ! '>='
42
- - !ruby/object:Gem::Version
43
- version: '0'
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
44
46
  type: :development
45
47
  prerelease: false
46
- version_requirements: *17080800
47
- - !ruby/object:Gem::Dependency
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
48
50
  name: bundler
49
- requirement: &17080280 !ruby/object:Gem::Requirement
51
+ requirement: &id004 !ruby/object:Gem::Requirement
50
52
  none: false
51
- requirements:
53
+ requirements:
52
54
  - - ~>
53
- - !ruby/object:Gem::Version
55
+ - !ruby/object:Gem::Version
54
56
  version: 1.0.0
55
57
  type: :development
56
58
  prerelease: false
57
- version_requirements: *17080280
58
- - !ruby/object:Gem::Dependency
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
59
61
  name: jeweler
60
- requirement: &17079760 !ruby/object:Gem::Requirement
62
+ requirement: &id005 !ruby/object:Gem::Requirement
61
63
  none: false
62
- requirements:
64
+ requirements:
63
65
  - - ~>
64
- - !ruby/object:Gem::Version
66
+ - !ruby/object:Gem::Version
65
67
  version: 1.6.4
66
68
  type: :development
67
69
  prerelease: false
68
- version_requirements: *17079760
69
- - !ruby/object:Gem::Dependency
70
+ version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: rdoc
73
+ requirement: &id006 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ~>
77
+ - !ruby/object:Gem::Version
78
+ version: "3.11"
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: *id006
82
+ - !ruby/object:Gem::Dependency
70
83
  name: rcov
71
- requirement: &17079220 !ruby/object:Gem::Requirement
84
+ requirement: &id007 !ruby/object:Gem::Requirement
72
85
  none: false
73
- requirements:
74
- - - ! '>='
75
- - !ruby/object:Gem::Version
76
- version: '0'
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: "0"
77
90
  type: :development
78
91
  prerelease: false
79
- version_requirements: *17079220
80
- description: Ruby library and command-line utility for working with Infobright Community
81
- Edition
92
+ version_requirements: *id007
93
+ description: Ruby library and command-line utility for working with Infobright Community Edition
82
94
  email: riley.goodside@gmail.com
83
- executables:
95
+ executables:
84
96
  - icebox
85
97
  extensions: []
86
- extra_rdoc_files:
98
+
99
+ extra_rdoc_files:
87
100
  - LICENSE.txt
88
101
  - README.rdoc
89
- files:
102
+ files:
90
103
  - .document
91
104
  - Gemfile
92
105
  - LICENSE.txt
@@ -100,32 +113,36 @@ files:
100
113
  - tasks/scratch.rb
101
114
  - test/helper.rb
102
115
  - test/test_icebox.rb
116
+ has_rdoc: true
103
117
  homepage: http://github.com/goodside/icebox
104
- licenses:
118
+ licenses:
105
119
  - MIT
106
120
  post_install_message:
107
121
  rdoc_options: []
108
- require_paths:
122
+
123
+ require_paths:
109
124
  - lib
110
- required_ruby_version: !ruby/object:Gem::Requirement
125
+ required_ruby_version: !ruby/object:Gem::Requirement
111
126
  none: false
112
- requirements:
113
- - - ! '>='
114
- - !ruby/object:Gem::Version
115
- version: '0'
116
- segments:
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ hash: -170128870566158022
131
+ segments:
117
132
  - 0
118
- hash: 2168865579570218248
119
- required_rubygems_version: !ruby/object:Gem::Requirement
133
+ version: "0"
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
135
  none: false
121
- requirements:
122
- - - ! '>='
123
- - !ruby/object:Gem::Version
124
- version: '0'
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: "0"
125
140
  requirements: []
141
+
126
142
  rubyforge_project:
127
- rubygems_version: 1.8.10
143
+ rubygems_version: 1.6.2
128
144
  signing_key:
129
145
  specification_version: 3
130
146
  summary: Multitool for working with Infobright Community Edition
131
147
  test_files: []
148
+