sequel_load_data_infile 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "sequel"
4
+
5
+ # Add dependencies to develop your gem here.
6
+ # Include everything needed to run rake, tests, features, etc.
7
+ group :development do
8
+ gem "mysql", "2.8.1"
9
+ gem "rspec", "~> 2"
10
+ gem "bundler"
11
+ gem "jeweler", "~> 1.8.4"
12
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Roland Swingler
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,29 @@
1
+ = sequel_load_data_infile
2
+
3
+ Provides LOAD DATA INFILE support for mysql datasets.
4
+
5
+ To use:
6
+
7
+ require 'sequel/load_data_infile'
8
+
9
+ DB[:some_table].load_data_infile(filepath, [:column, :names])
10
+
11
+ # Or, for CSV data:
12
+
13
+ DB[:some_table].load_csv_infile(filepath, [:column, :names])
14
+
15
+ == Contributing to sequel_load_data_infile
16
+
17
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
18
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
19
+ * Fork the project.
20
+ * Start a feature/bugfix branch.
21
+ * Commit and push until you are happy with your contribution.
22
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
23
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
24
+
25
+ == Copyright
26
+
27
+ Copyright (c) 2013 Roland Swingler. See LICENSE.txt for
28
+ further details.
29
+
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "sequel_load_data_infile"
18
+ gem.homepage = "http://github.com/knaveofdiamonds/sequel_load_data_infile"
19
+ gem.license = "MIT"
20
+ gem.summary = "Provides LOAD DATA INFILE support for mysql datasets"
21
+ gem.description = "Provides LOAD DATA INFILE support for mysql datasets"
22
+ gem.email = "roland.swingler@gmail.com"
23
+ gem.authors = ["Roland Swingler"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,159 @@
1
+ require 'sequel'
2
+
3
+ module Sequel
4
+ # @api private
5
+ class LoadDataInfileExpression
6
+ attr_reader :path, :table, :columns, :ignore, :character_set
7
+
8
+ def initialize(path, table, columns, opts={})
9
+ @path = path
10
+ @table = table
11
+ @columns = columns
12
+ @ignore = opts[:ignore]
13
+ @update = opts[:update]
14
+ @set = opts[:set] || {}
15
+ @character_set = opts[:character_set] || "utf8"
16
+ if opts[:format] == :csv
17
+ @field_terminator = ","
18
+ @enclosed_by = '"'
19
+ @escaped_by = '"'
20
+ end
21
+ end
22
+
23
+ def replace?
24
+ @update == :replace
25
+ end
26
+
27
+ def ignore?
28
+ @update == :ignore
29
+ end
30
+
31
+ def to_sql(db)
32
+ @db = db
33
+
34
+ [load_fragment,
35
+ replace_fragment,
36
+ table_fragment,
37
+ character_set_fragment,
38
+ field_terminator_fragment,
39
+ field_enclosure_fragment,
40
+ escape_fragment,
41
+ ignore_fragment,
42
+ column_fragment,
43
+ set_fragment].compact.join(" ")
44
+ end
45
+
46
+ private
47
+
48
+ def load_fragment
49
+ "LOAD DATA INFILE '#{path}'"
50
+ end
51
+
52
+ def replace_fragment
53
+ @update.to_s.upcase if replace? || ignore?
54
+ end
55
+
56
+ def table_fragment
57
+ "INTO TABLE `#{table}`"
58
+ end
59
+
60
+ def character_set_fragment
61
+ "CHARACTER SET '#{character_set}'"
62
+ end
63
+
64
+ def field_terminator_fragment
65
+ "FIELDS TERMINATED BY '#{@field_terminator}'" if @field_terminator
66
+ end
67
+
68
+ def field_enclosure_fragment
69
+ "OPTIONALLY ENCLOSED BY '#{@enclosed_by}'" if @enclosed_by
70
+ end
71
+
72
+ def escape_fragment
73
+ "ESCAPED BY '#{@escaped_by}'" if @escaped_by
74
+ end
75
+
76
+ def ignore_fragment
77
+ "IGNORE #{ignore} LINES" if ignore
78
+ end
79
+
80
+ def column_fragment
81
+ "(" + columns.map {|c| format_column(c) }.join(",") + ")"
82
+ end
83
+
84
+ def set_fragment
85
+ unless set_columns.empty?
86
+ "SET " + set_columns.map do |k, v|
87
+ "#{@db.literal(k)} = #{@db.literal(v)}"
88
+ end.join(", ")
89
+ end
90
+ end
91
+
92
+ def format_column(column)
93
+ if binary_columns.include?(column)
94
+ "@#{column}"
95
+ elsif column.to_s[0..0] == "@"
96
+ column
97
+ else
98
+ "`#{column}`"
99
+ end
100
+ end
101
+
102
+ def binary_columns
103
+ @binary_columns ||= @db.schema(@table).
104
+ select {|a| a[1][:type] == :blob }.map {|a| a.first.to_s }
105
+ end
106
+
107
+ def set_columns
108
+ binary_columns.inject({}) do |hash, column|
109
+ hash[column.to_sym] = :unhex.sql_function("@#{column}".lit)
110
+ hash
111
+ end.merge(@set)
112
+ end
113
+ end
114
+
115
+ module LoadDataInfile
116
+ # Load data in file specified at path.
117
+ #
118
+ # Columns is a list of columns to load - column names starting
119
+ # with an @ symbol will be treated as variables.
120
+ #
121
+ # By default, this will generate a REPLACE INTO TABLE
122
+ # statement.
123
+ #
124
+ # Options:
125
+ # :ignore - the number of lines to ignore in the source file
126
+ # :update - nil, :ignore or :replace
127
+ # :set - a hash specifying autopopulation of columns
128
+ # :character_set - the character set of the file, UTF8 default
129
+ # :format - either nil or :csv
130
+ def load_infile(path, columns, options={})
131
+ execute_dui(load_infile_sql(path, columns, options))
132
+ end
133
+
134
+ # Returns the SQL for a LOAD DATA INFILE statement.
135
+ def load_infile_sql(path, columns, options={})
136
+ replacement = opts[:insert_ignore] ? :ignore : :replace
137
+ options = {:update => replacement}.merge(options)
138
+ LoadDataInfileExpression.new(path,
139
+ opts[:from].first,
140
+ columns,
141
+ options).
142
+ to_sql(db)
143
+ end
144
+
145
+ # Loads the CSV data columns in path into this dataset's
146
+ # table.
147
+ #
148
+ # See load_infile for more options.
149
+ def load_csv_infile(path, columns, options={})
150
+ execute_dui(load_csv_infile_sql(path, columns, options))
151
+ end
152
+
153
+ def load_csv_infile_sql(path, columns, options={})
154
+ load_infile_sql(path, columns, options.merge(:format => :csv))
155
+ end
156
+ end
157
+ end
158
+
159
+ Sequel::Dataset.send :include, Sequel::LoadDataInfile
@@ -0,0 +1,4 @@
1
+ adapter: mysql
2
+ username: root
3
+ socket: /tmp/mysql.sock
4
+ database: load_data_infile_test_db
@@ -0,0 +1,71 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Sequel::LoadDataInfileExpression do
4
+ before :each do
5
+ TEST_DB.stub(:schema).and_return([])
6
+ end
7
+
8
+ it "loads the data in the file into the table" do
9
+ described_class.new("bar.csv", :foo, ['bar', 'quux']).
10
+ to_sql(TEST_DB).should include("LOAD DATA INFILE 'bar.csv' INTO TABLE `foo`")
11
+ end
12
+
13
+ it "loads the data with replacment" do
14
+ described_class.new("bar.csv", :foo, ['bar', 'quux'],
15
+ :update => :replace).
16
+ to_sql(TEST_DB).should include("REPLACE INTO TABLE")
17
+ end
18
+
19
+ it "loads the data ignoring rows" do
20
+ described_class.new("bar.csv", :foo, ['bar', 'quux'], :update => :ignore).
21
+ to_sql(TEST_DB).should include("IGNORE INTO TABLE")
22
+ end
23
+
24
+ it "should be in UTF-8 character set by default" do
25
+ described_class.new("bar.csv", :foo, ['bar', 'quux']).
26
+ to_sql(TEST_DB).should include("CHARACTER SET 'utf8'")
27
+ end
28
+
29
+ it "may be in other character sets" do
30
+ described_class.new("bar.csv", :foo, ['bar', 'quux'], :character_set => "ascii").
31
+ to_sql(TEST_DB).should include("CHARACTER SET 'ascii'")
32
+ end
33
+
34
+ it "should load columns" do
35
+ described_class.new("bar.csv", :foo, ['bar', 'quux']).
36
+ to_sql(TEST_DB).should include("(`bar`,`quux`)")
37
+ end
38
+
39
+ it "should load into variables if column begins with @" do
40
+ described_class.new("bar.csv", :foo, ['@bar', 'quux']).
41
+ to_sql(TEST_DB).should include("(@bar,`quux`)")
42
+ end
43
+
44
+ it "can ignore lines" do
45
+ described_class.new("bar.csv", :foo, ['bar', 'quux'], :ignore => 2).
46
+ to_sql(TEST_DB).should include("IGNORE 2 LINES")
47
+ end
48
+
49
+ it "can be in csv format" do
50
+ described_class.new("bar.csv", :foo, ['bar', 'quux'], :format => :csv).
51
+ to_sql(TEST_DB).should include("FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' ESCAPED BY '\"'")
52
+ end
53
+
54
+ it "can set column values" do
55
+ sql = described_class.new("bar.csv", :foo, ['@bar', 'quux'],
56
+ :set => {:bar => :unhex.sql_function("@bar".lit),
57
+ :etl_batch_id => 3}).
58
+ to_sql(TEST_DB)
59
+
60
+
61
+ sql.should include("`etl_batch_id` = 3")
62
+ sql.should include("`bar` = unhex(@bar)")
63
+ end
64
+
65
+ it "unhexes binary columns automatically via set" do
66
+ TEST_DB.stub(:schema).and_return([[:bar, {:type => :blob}]])
67
+ sql = described_class.new("bar.csv", :foo, ['bar', 'quux']).to_sql(TEST_DB)
68
+ sql.should include("(@bar,`quux`)")
69
+ sql.should include("SET `bar` = unhex(@bar)")
70
+ end
71
+ end
@@ -0,0 +1,39 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Sequel::LoadDataInfile do
4
+ before :each do
5
+ dataset = TEST_DB[:foo]
6
+ dataset.db.stub(:schema).and_return([])
7
+ @sql = dataset.load_csv_infile_sql("bar.csv", [:bar, :baz])
8
+ end
9
+
10
+ it "loads the data in the file" do
11
+ @sql.should include("LOAD DATA INFILE 'bar.csv'")
12
+ end
13
+
14
+ it "replaces rows currently in the table" do
15
+ @sql.should include("REPLACE INTO TABLE `foo`")
16
+ end
17
+
18
+ it "should be in the UTF 8 character set" do
19
+ @sql.should include("CHARACTER SET 'utf8'")
20
+ end
21
+
22
+ it "should escape with the \" character" do
23
+ @sql.should include("ESCAPED BY '\"'")
24
+ end
25
+
26
+ it "supports standard csv, with optional quoting" do
27
+ @sql.should include("FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"'")
28
+ end
29
+
30
+ it "loads into the columns specified" do
31
+ @sql.should include("(`bar`,`baz`)")
32
+ end
33
+
34
+ it "can ignore instead of replacing rows" do
35
+ @sql = TEST_DB[:foo].insert_ignore.
36
+ load_csv_infile_sql("bar.csv", [:bar, :baz])
37
+ @sql.should include("IGNORE INTO TABLE `foo`")
38
+ end
39
+ end
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'yaml'
5
+ require 'sequel/load_data_infile'
6
+
7
+ unless defined? TEST_DB
8
+ TEST_DB = Sequel.connect(YAML.load(File.read(File.dirname(__FILE__) + "/db_connections.yml")))
9
+ end
10
+
11
+ # Requires supporting files with custom matchers and macros, etc,
12
+ # in ./support/ and its subdirectories.
13
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
14
+
15
+ RSpec.configure do |config|
16
+
17
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequel_load_data_infile
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Roland Swingler
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-31 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sequel
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: mysql
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - '='
36
+ - !ruby/object:Gem::Version
37
+ version: 2.8.1
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - '='
44
+ - !ruby/object:Gem::Version
45
+ version: 2.8.1
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2'
62
+ - !ruby/object:Gem::Dependency
63
+ name: bundler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: jeweler
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 1.8.4
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 1.8.4
94
+ description: Provides LOAD DATA INFILE support for mysql datasets
95
+ email: roland.swingler@gmail.com
96
+ executables: []
97
+ extensions: []
98
+ extra_rdoc_files:
99
+ - LICENSE.txt
100
+ - README.rdoc
101
+ files:
102
+ - .document
103
+ - .rspec
104
+ - Gemfile
105
+ - LICENSE.txt
106
+ - README.rdoc
107
+ - Rakefile
108
+ - VERSION
109
+ - lib/sequel/load_data_infile.rb
110
+ - spec/db_connections.yml.dist
111
+ - spec/load_data_infile_expression_spec.rb
112
+ - spec/load_data_infile_spec.rb
113
+ - spec/spec_helper.rb
114
+ homepage: http://github.com/knaveofdiamonds/sequel_load_data_infile
115
+ licenses:
116
+ - MIT
117
+ post_install_message:
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ segments:
128
+ - 0
129
+ hash: -2195219335310673832
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 1.8.25
139
+ signing_key:
140
+ specification_version: 3
141
+ summary: Provides LOAD DATA INFILE support for mysql datasets
142
+ test_files: []