icebox 0.0.2 → 0.0.3

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.
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
+