Skip to content

Commit

Permalink
it is alive
Browse files Browse the repository at this point in the history
  • Loading branch information
Emmanuel Oga authored and Emmanuel Oga committed Feb 23, 2010
1 parent 5bca743 commit ebef31c
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 6 deletions.
90 changes: 90 additions & 0 deletions lib/load_data_infile.rb
@@ -0,0 +1,90 @@
require 'erb'

module LoadDataInfile
module MySql

# Deletes all rows in table very fast, but without calling +destroy+ method
# nor any hooks.
def truncate_table
connection.execute("TRUNCATE TABLE #{quoted_table_name}")
end

# Disables key updates for model table
def disable_keys
connection.execute("ALTER TABLE #{quoted_table_name} DISABLE KEYS")
end

# Enables key updates for model table
def enable_keys
connection.execute("ALTER TABLE #{quoted_table_name} ENABLE KEYS")
end

# Disables keys, yields block, enables keys.
def with_keys_disabled
disable_keys
yield
enable_keys
end

def load_data_infile(options = {})
c = Context.new

if options[:low_priority]
c.low_priority_or_concurrent = :LOW_PRIORITY
elsif options[:concurrent]
c.low_priority_or_concurrent = :CONCURRENT
end

c.local = :LOCAL if !options.member?(:local) || options[:local]

c.file_name = options[:path]

c.replace_or_ignore = options[:on_duplicates] if options[:on_duplicates] # REPLACE or IGNORE

c.table_name = options[:table] ? "`#{ options[:table] }`" : quoted_table_name

c.charset = "CHARACTER SET #{options[:charset]}" if options[:charset]

if options[:terminated_by] || options[:enclosed_by] || options[:optionally_enclosed_by] || options[:escaped_by]
c.fields_definitions = " FIELDS " # or COLUMNS
c.fields_definitions << " TERMINATED BY '#{ options[:terminated_by] }' " if options[:terminated_by]
c.fields_definitions << " ENCLOSED BY '#{ options[:enclosed_by] }' " if options[:enclosed_by]
c.fields_definitions << " OPTIONALLY ENCLOSED BY '#{ options[:optionally_enclosed_by] }' " if options[:optionally_enclosed_by]
c.fields_definitions << " ESCAPED BY '#{ options[:escaped_by] }' " if options[:escaped_by]
end

if options[:lines_terminated_by] || options[:lines_starting_by]
c.lines_defitions = " LINES "
c.lines_defitions << " STARTING BY '#{options[:lines_starting_by]}' " if options[:lines_starting_by]
c.lines_defitions << " TERMINATED BY '#{options[:lines_terminated_by]}' " if options[:lines_terminated_by]
end

c.ignores = "IGNORE #{options[:ignore]} LINES" if options[:ignore]

c.columns = " (#{options[:columns].join(", ")}) " if options[:columns]

if options[:mappings] && options[:mappings].length > 0
s = options[:mapping].map{|column, mapping| "#{column} = #{mapping}" }.join(",")
c.mappings = "SET #{s}"
end

connection.execute(ERB.new(LOAD_DATA_INFILE_SQL).result(c.binding).gsub(/^\s*\n/, ""))
end

class Context < OpenStruct
public :binding
end

LOAD_DATA_INFILE_SQL = <<-SQL
LOAD DATA <%= low_priority_or_concurrent %> <%= local %> INFILE '<%= file_name %>'
<%= replace_or_ignore %>
INTO TABLE <%= table_name %>
<%= charset %>
<%= fields_definitions %>
<%= lines_defitions %>
<%= ignores %>
<%= columns %>
<%= mappings %> ;
SQL
end
end
1 change: 1 addition & 0 deletions rails/init.rb
@@ -0,0 +1 @@
ActiveRecord::Base.extend LoadDataInfile::MySql
13 changes: 13 additions & 0 deletions spec/active_record_helper.rb
@@ -0,0 +1,13 @@
ActiveRecord::Base.establish_connection(:adapter => "mysql", :database => "load_data_infile_test")

ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
create_table "things", :force => true do |t|
t.string :field_a, :field_b
t.integer :field_c
end
end

class Thing < ActiveRecord::Base
end
2 changes: 2 additions & 0 deletions spec/fixtures/csv_with_headers.csv
@@ -0,0 +1,2 @@
id,a,b,c
71,Hello,Brother,42
1 change: 1 addition & 0 deletions spec/fixtures/csv_without_headers.csv
@@ -0,0 +1 @@
61,live,from,2400
39 changes: 36 additions & 3 deletions spec/load_data_infile_spec.rb
@@ -1,7 +1,40 @@
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')

describe "LoadDataInfile" do
it "fails" do
fail "hey buddy, you should probably rename this file and start specing for real"
describe LoadDataInfile do
before :each do
Thing.truncate_table
end

it "loads data from a csv file with headers into an ActiveRecord table" do
Thing.with_keys_disabled do
Thing.load_data_infile(
:path => FIXTURE_WITH_HEADERS,
:columns => %w|id field_a field_b field_c|,
:terminated_by => ",",
:ignore => 1
)
end
Thing.all.map(&:attributes).should == [{
"id" => 71,
"field_a" => "Hello",
"field_b" => "Brother",
"field_c" => 42
}]
end

it "loads data from a csv file without headers into an ActiveRecord table" do
Thing.with_keys_disabled do
Thing.load_data_infile(
:path => FIXTURE_WITHOUT_HEADERS,
:terminated_by => ",",
:columns => %w|id field_a field_b field_c|
)
end
Thing.all.map(&:attributes).should == [{
"id" => 61,
"field_a" => "live",
"field_b" => "from",
"field_c" => 2400
}]
end
end
13 changes: 10 additions & 3 deletions spec/spec_helper.rb
@@ -1,9 +1,16 @@
$LOAD_PATH.unshift(File.dirname(__FILE__))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
SPEC_PATH = File.dirname(__FILE__)
$LOAD_PATH.unshift(SPEC_PATH)
$LOAD_PATH.unshift(File.join(SPEC_PATH, '..', 'lib'))
require 'load_data_infile'
require 'spec'
require 'spec/autorun'
require 'rubygems'
require 'active_record'
require 'active_record_helper'
require File.join(SPEC_PATH, "..", "rails", "init.rb")

FIXTURE_WITH_HEADERS = File.join(SPEC_PATH, "fixtures", "csv_with_headers.csv")
FIXTURE_WITHOUT_HEADERS = File.join(SPEC_PATH, "fixtures", "csv_without_headers.csv")

Spec::Runner.configure do |config|

end

0 comments on commit ebef31c

Please sign in to comment.