postgres-copy 1.1.2 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 3cc595142d45d6053c54bac79e0855c4d8136856
4
- data.tar.gz: 548bbff5d3505a7c9721d0d69f20313072770dd3
2
+ SHA256:
3
+ metadata.gz: ad654ff2a933f1e41f23e6347f0b20daa3e16455caea9a6107ce4d50c39598f8
4
+ data.tar.gz: e972e684f928c0afdd7fdd096ade3d28d293604ef20f42a959097234f37f7317
5
5
  SHA512:
6
- metadata.gz: e15905d7bcd481f38153decfc9350d0220642c62b35ea7d285293b3ccf2f50fd8cf4e87d1e4bc8c49bcb120e15b6a422b78f05313e3587ca94e0b578561fd28a
7
- data.tar.gz: 1da873e2f16c582afb38eef9d4dba009067446df3c6835228ec31c1cf2253a8690dd3e716f162a05032b6f0d0becf0d8defcca575cf29c101755c9f651c88935
6
+ metadata.gz: e57d3d4d0bd7a0bef175dad625c77013f2616222ef9ef086c449cdcbd8f58e4bbde77625e6f70ab55149683becac6e15d8c2cbf84c09c707f1a39cbef0417b05
7
+ data.tar.gz: b8768a3cf765aa7d6c01eb5138439e20c1140f6b2e772e96d169635832fe14a2780e438732503f719eba7f0b9bcf0e6cf6da4c7241d3d42e734d4433f65799e6
@@ -0,0 +1,48 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
15
+
16
+ jobs:
17
+ test:
18
+ services:
19
+ # Label used to access the service container
20
+ postgres:
21
+ # Docker Hub image
22
+ image: postgres
23
+ # Provide the password for postgres
24
+ env:
25
+ POSTGRES_PASSWORD: postgres
26
+ ports:
27
+ - 5432:5432
28
+ # Set health checks to wait until postgres has started
29
+ options: >-
30
+ --health-cmd pg_isready
31
+ --health-interval 10s
32
+ --health-timeout 5s
33
+ --health-retries 5
34
+
35
+ runs-on: ubuntu-latest
36
+
37
+ steps:
38
+ - uses: actions/checkout@v2
39
+ - name: Set up Ruby
40
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
41
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
42
+ uses: ruby/setup-ruby@v1
43
+ with:
44
+ ruby-version: 2.7
45
+ - name: Install dependencies
46
+ run: bundle install
47
+ - name: Run tests
48
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -23,3 +23,7 @@ pkg
23
23
 
24
24
  ## PROJECT::SPECIFIC
25
25
  .project
26
+
27
+ # RVM files
28
+ .ruby-version
29
+ .ruby-gemset
@@ -1,70 +1,73 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- postgres-copy (1.1.2)
5
- activerecord (>= 4.0)
4
+ postgres-copy (1.5.0)
5
+ activerecord (>= 5.1)
6
6
  pg (>= 0.17)
7
7
  responders
8
8
 
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- actionpack (5.0.1)
13
- actionview (= 5.0.1)
14
- activesupport (= 5.0.1)
15
- rack (~> 2.0)
16
- rack-test (~> 0.6.3)
12
+ actionpack (6.0.3.2)
13
+ actionview (= 6.0.3.2)
14
+ activesupport (= 6.0.3.2)
15
+ rack (~> 2.0, >= 2.0.8)
16
+ rack-test (>= 0.6.3)
17
17
  rails-dom-testing (~> 2.0)
18
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
19
- actionview (5.0.1)
20
- activesupport (= 5.0.1)
18
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
19
+ actionview (6.0.3.2)
20
+ activesupport (= 6.0.3.2)
21
21
  builder (~> 3.1)
22
- erubis (~> 2.7.0)
22
+ erubi (~> 1.4)
23
23
  rails-dom-testing (~> 2.0)
24
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
25
- activemodel (5.0.1)
26
- activesupport (= 5.0.1)
27
- activerecord (5.0.1)
28
- activemodel (= 5.0.1)
29
- activesupport (= 5.0.1)
30
- arel (~> 7.0)
31
- activesupport (5.0.1)
24
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
25
+ activemodel (6.0.3.2)
26
+ activesupport (= 6.0.3.2)
27
+ activerecord (6.0.3.2)
28
+ activemodel (= 6.0.3.2)
29
+ activesupport (= 6.0.3.2)
30
+ activesupport (6.0.3.2)
32
31
  concurrent-ruby (~> 1.0, >= 1.0.2)
33
- i18n (~> 0.7)
32
+ i18n (>= 0.7, < 2)
34
33
  minitest (~> 5.1)
35
34
  tzinfo (~> 1.1)
36
- arel (7.1.4)
37
- builder (3.2.3)
38
- concurrent-ruby (1.0.4)
39
- diff-lcs (1.3)
40
- erubis (2.7.0)
41
- i18n (0.8.1)
42
- loofah (2.0.3)
35
+ zeitwerk (~> 2.2, >= 2.2.2)
36
+ builder (3.2.4)
37
+ concurrent-ruby (1.1.6)
38
+ crass (1.0.6)
39
+ diff-lcs (1.4.4)
40
+ erubi (1.9.0)
41
+ i18n (1.8.3)
42
+ concurrent-ruby (~> 1.0)
43
+ loofah (2.6.0)
44
+ crass (~> 1.0.2)
43
45
  nokogiri (>= 1.5.9)
44
- method_source (0.8.2)
45
- mini_portile2 (2.1.0)
46
- minitest (5.10.1)
47
- nokogiri (1.7.0.1)
48
- mini_portile2 (~> 2.1.0)
49
- pg (0.19.0)
50
- rack (2.0.1)
51
- rack-test (0.6.3)
52
- rack (>= 1.0)
53
- rails-dom-testing (2.0.2)
54
- activesupport (>= 4.2.0, < 6.0)
55
- nokogiri (~> 1.6)
56
- rails-html-sanitizer (1.0.3)
57
- loofah (~> 2.0)
58
- railties (5.0.1)
59
- actionpack (= 5.0.1)
60
- activesupport (= 5.0.1)
46
+ method_source (1.0.0)
47
+ mini_portile2 (2.4.0)
48
+ minitest (5.14.1)
49
+ nokogiri (1.10.10)
50
+ mini_portile2 (~> 2.4.0)
51
+ pg (1.2.3)
52
+ rack (2.2.3)
53
+ rack-test (1.1.0)
54
+ rack (>= 1.0, < 3)
55
+ rails-dom-testing (2.0.3)
56
+ activesupport (>= 4.2.0)
57
+ nokogiri (>= 1.6)
58
+ rails-html-sanitizer (1.3.0)
59
+ loofah (~> 2.3)
60
+ railties (6.0.3.2)
61
+ actionpack (= 6.0.3.2)
62
+ activesupport (= 6.0.3.2)
61
63
  method_source
62
64
  rake (>= 0.8.7)
63
- thor (>= 0.18.1, < 2.0)
65
+ thor (>= 0.20.3, < 2.0)
64
66
  rake (11.2.2)
65
- rdoc (5.0.0)
66
- responders (2.3.0)
67
- railties (>= 4.2.0, < 5.1)
67
+ rdoc (6.2.1)
68
+ responders (3.0.1)
69
+ actionpack (>= 5.0)
70
+ railties (>= 5.0)
68
71
  rspec (2.99.0)
69
72
  rspec-core (~> 2.99.0)
70
73
  rspec-expectations (~> 2.99.0)
@@ -73,10 +76,11 @@ GEM
73
76
  rspec-expectations (2.99.2)
74
77
  diff-lcs (>= 1.1.3, < 2.0)
75
78
  rspec-mocks (2.99.4)
76
- thor (0.19.4)
79
+ thor (1.0.1)
77
80
  thread_safe (0.3.6)
78
- tzinfo (1.2.2)
81
+ tzinfo (1.2.7)
79
82
  thread_safe (~> 0.1)
83
+ zeitwerk (2.4.0)
80
84
 
81
85
  PLATFORMS
82
86
  ruby
@@ -89,4 +93,4 @@ DEPENDENCIES
89
93
  rspec (~> 2.12)
90
94
 
91
95
  BUNDLED WITH
92
- 1.14.3
96
+ 2.1.2
data/README.md CHANGED
@@ -1,11 +1,13 @@
1
- # postgres-copy [![Build Status](https://travis-ci.org/diogob/postgres-copy.svg?branch=master)](https://travis-ci.org/diogob/postgres-copy) [![Code Climate](https://codeclimate.com/github/diogob/postgres-copy.svg)](https://codeclimate.com/github/diogob/postgres-copy)
1
+ # postgres-copy
2
+
3
+ ![Ruby](https://github.com/diogob/postgres-copy/workflows/Ruby/badge.svg)
2
4
 
3
5
  This Gem will enable your AR models to use the PostgreSQL COPY command to import/export data in CSV format.
4
6
  If you need to tranfer data between a PostgreSQL database and CSV files, the PostgreSQL native CSV parser
5
7
  will give you a greater performance than using the ruby CSV+INSERT commands.
6
8
  I have not found time to make accurate benchmarks, but in the use scenario where I have developed the gem
7
9
  I have had a four-fold performance gain.
8
- This gem was written having the Rails framework in mind, I think it could work only with active-record,
10
+ This gem was written having the Rails framework in mind, I think it could work only with active-record,
9
11
  but I will assume in this README that you are using Rails.
10
12
 
11
13
  ## Install
@@ -32,16 +34,17 @@ class User < ActiveRecord::Base
32
34
  end
33
35
  ```
34
36
 
35
- This will add the aditiontal class methods to your model:
37
+ This will add the additional class methods to your model:
36
38
 
37
- * copy_to
39
+ * copy_to
38
40
  * copy_to_string
41
+ * copy_to_enumerator
39
42
  * copy_from
40
43
 
41
44
  ### Using copy_to and copy_to_string
42
45
 
43
46
  You can go to the rails console and try some cool things first.
44
- The first and most basic use case, let's copy the enteire content of a database table to a CSV file on the database server disk.
47
+ The first and most basic use case, let's copy the entire content of a database table to a CSV file on the database server disk.
45
48
  Assuming we have a users table and a User AR model:
46
49
 
47
50
  ```ruby
@@ -54,8 +57,8 @@ This will execute in the database the command:
54
57
  COPY (SELECT "users".* FROM "users" ) TO '/tmp/users.csv' WITH DELIMITER ',' CSV HEADER
55
58
  ```
56
59
 
57
- Remark that the file will be created in the database server disk.
58
- But what if you want to write the lines in a file on the server that is running Rails, instead of the database?
60
+ Remark that the file will be created in the database server disk.
61
+ But what if you want to write the lines in a file on the server that is running Rails, instead of the database?
59
62
  In this case you can pass a block and retrieve the generated lines and then write them to a file:
60
63
 
61
64
  ```ruby
@@ -66,13 +69,23 @@ File.open('/tmp/users.csv', 'w') do |f|
66
69
  end
67
70
  ```
68
71
 
72
+ Instead of yielding each line, you could return an enumerator with all users:
73
+ ```ruby
74
+ enumerator = User.copy_to_enumerator
75
+ ```
76
+
77
+ And for better performance when rendering the result of the enumerator, you can return an enumerator with blocks of 100 lines joined:
78
+ ```ruby
79
+ enumerator = User.copy_to_enumerator(:buffer_lines => 100)
80
+ ```
81
+
69
82
  Or, if you have enough memory, you can read all table contents to a string using .copy_to_string
70
83
 
71
84
  ```ruby
72
85
  puts User.copy_to_string
73
86
  ```
74
87
 
75
- Another insteresting feature of copy_to is that it uses the scoped relation, it means that you can use ARel
88
+ Another interesting feature of copy_to is that it uses the scoped relation, it means that you can use ARel
76
89
  operations to generate different CSV files according to your needs.
77
90
  Assuming we want to generate a file only with the names of users 1, 2 and 3:
78
91
 
@@ -86,6 +99,18 @@ Which will generate the following SQL command:
86
99
  COPY (SELECT name FROM "users" WHERE "users"."id" IN (1, 2, 3)) TO '/tmp/users.csv' WITH DELIMITER ',' CSV HEADER
87
100
  ```
88
101
 
102
+ Alternatively, you can supply customized raw SQL query to copy_to instead of scoped relation:
103
+
104
+ ```ruby
105
+ User.copy_to("/tmp/users.csv", query: 'SELECT count(*) as Total FROM users')
106
+ ```
107
+
108
+ Which will generate the following SQL command:
109
+
110
+ ```sql
111
+ COPY (SELECT count(*) as Total FROM users) TO '/tmp/users.csv' WITH DELIMITER ',' CSV HEADER
112
+ ```
113
+
89
114
  The COPY command also supports exporting the data in binary format.
90
115
 
91
116
  ```ruby
@@ -133,7 +158,7 @@ User.copy_from "/tmp/users.csv" do |row|
133
158
  end
134
159
  ```
135
160
 
136
- The above extample will always change the value of the first column to "fixed string" before storing it into the database.
161
+ The above example will always change the value of the first column to "fixed string" before storing it into the database.
137
162
  For each iteration of the block row receives an array with the same order as the columns in the CSV file.
138
163
 
139
164
 
@@ -143,6 +168,17 @@ To specify NULL value you can pass the null option parameter.
143
168
  User.copy_from "/tmp/users.csv", :null => 'null'
144
169
  ```
145
170
 
171
+ Match the specified columns' values against the null string, even if it has been quoted, and if a match is found set the value to NULL (Postgres 9.4+ only).
172
+
173
+ ```ruby
174
+ User.copy_from "/tmp/users.csv", :null => '', :force_null => [:name, :city]
175
+ ```
176
+
177
+ To copy from tsv file , you can set format `:tsv`
178
+
179
+ ```ruby
180
+ User.copy_from "/tmp/users.tsv", :format => :tsv
181
+ ```
146
182
 
147
183
  To copy a binary formatted data file or IO object you can specify the format as binary
148
184
 
@@ -16,12 +16,13 @@ module PostgresCopy
16
16
  else
17
17
  "DELIMITER '#{options[:delimiter]}' CSV #{options[:header] ? 'HEADER' : ''}"
18
18
  end
19
+ options_query = options.delete(:query) || self.all.to_sql
19
20
 
20
21
  if path
21
22
  raise "You have to choose between exporting to a file or receiving the lines inside a block" if block_given?
22
- connection.execute "COPY (#{self.all.to_sql}) TO #{sanitize(path)} WITH #{options_string}"
23
+ connection.execute "COPY (#{options_query}) TO '#{sanitize_sql(path)}' WITH #{options_string}"
23
24
  else
24
- connection.raw_connection.copy_data "COPY (#{self.all.to_sql}) TO STDOUT WITH #{options_string}" do
25
+ connection.raw_connection.copy_data "COPY (#{options_query}) TO STDOUT WITH #{options_string}" do
25
26
  while line = connection.raw_connection.get_copy_data do
26
27
  yield(line) if block_given?
27
28
  end
@@ -30,6 +31,32 @@ module PostgresCopy
30
31
  return self
31
32
  end
32
33
 
34
+ # Create an enumerator with each line from the CSV.
35
+ # Note that using this directly in a controller response
36
+ # will perform very poorly as each line will get put
37
+ # into its own chunk. Joining every (eg) 100 rows together
38
+ # is much, much faster.
39
+ def copy_to_enumerator(options={})
40
+ buffer_lines = options.delete(:buffer_lines)
41
+ # Somehow, self loses its scope once inside the Enumerator
42
+ scope = self.current_scope || self
43
+ result = Enumerator.new do |y|
44
+ scope.copy_to(nil, options) do |line|
45
+ y << line
46
+ end
47
+ end
48
+
49
+ if buffer_lines.to_i > 0
50
+ Enumerator.new do |y|
51
+ result.each_slice(buffer_lines.to_i) do |slice|
52
+ y << slice.join
53
+ end
54
+ end
55
+ else
56
+ result
57
+ end
58
+ end
59
+
33
60
  # Copy all data to a single string
34
61
  def copy_to_string options = {}
35
62
  data = ''
@@ -46,12 +73,15 @@ module PostgresCopy
46
73
  # * For further details on usage take a look at the README.md
47
74
  def copy_from path_or_io, options = {}
48
75
  options = {:delimiter => ",", :format => :csv, :header => true, :quote => '"'}.merge(options)
76
+ options[:delimiter] = "\t" if options[:format] == :tsv
49
77
  options_string = if options[:format] == :binary
50
78
  "BINARY"
51
79
  else
52
80
  quote = options[:quote] == "'" ? "''" : options[:quote]
53
- null = options.key?(:null) ? "NULL '#{options[:null]}'" : ''
54
- "DELIMITER '#{options[:delimiter]}' QUOTE '#{quote}' #{null} CSV"
81
+ null = options.key?(:null) ? "NULL '#{options[:null]}'" : nil
82
+ force_null = options.key?(:force_null) ? "FORCE_NULL(#{options[:force_null].join(',')})" : nil
83
+ delimiter = options[:format] == :tsv ? "E'\t'" : "'#{options[:delimiter]}'"
84
+ "WITH (" + ["DELIMITER #{delimiter}", "QUOTE '#{quote}'", null, force_null, "FORMAT CSV"].compact.join(', ') + ")"
55
85
  end
56
86
  io = path_or_io.instance_of?(String) ? File.open(path_or_io, 'r') : path_or_io
57
87
 
@@ -83,15 +113,31 @@ module PostgresCopy
83
113
  rescue EOFError
84
114
  end
85
115
  else
116
+ line_buffer = ''
117
+
86
118
  while line = io.gets do
87
119
  next if line.strip.size == 0
88
- if block_given?
89
- row = CSV.parse_line(line.strip, {:col_sep => options[:delimiter]})
90
- yield(row)
91
- next if row.all?{|f| f.nil? }
92
- line = CSV.generate_line(row, {:col_sep => options[:delimiter]})
120
+
121
+ line_buffer += line
122
+
123
+ # If line is incomplete, get the next line until it terminates
124
+ if line_buffer =~ /\n$/ || line_buffer =~ /\Z/
125
+ if block_given?
126
+ begin
127
+ row = CSV.parse_line(line_buffer.strip, {:col_sep => options[:delimiter]})
128
+ yield(row)
129
+ next if row.all?{|f| f.nil? }
130
+ line_buffer = CSV.generate_line(row, {:col_sep => options[:delimiter]})
131
+ rescue CSV::MalformedCSVError
132
+ next
133
+ end
134
+ end
135
+
136
+ connection.raw_connection.put_copy_data(line_buffer)
137
+
138
+ # Clear the buffer
139
+ line_buffer = ''
93
140
  end
94
- connection.raw_connection.put_copy_data line
95
141
  end
96
142
  end
97
143
  end
@@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib)
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "postgres-copy"
8
- s.version = "1.1.2"
8
+ s.version = "1.5.0"
9
9
  s.platform = Gem::Platform::RUBY
10
10
  s.required_ruby_version = ">= 1.9.3"
11
11
  s.authors = ["Diogo Biazus"]
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.summary = "Put COPY command functionality in ActiveRecord's model class"
22
22
 
23
23
  s.add_dependency "pg", ">= 0.17"
24
- s.add_dependency "activerecord", '>= 4.0'
24
+ s.add_dependency "activerecord", '>= 5.1'
25
25
  s.add_dependency "responders"
26
26
  s.add_development_dependency "bundler"
27
27
  s.add_development_dependency "rdoc"
@@ -100,20 +100,20 @@ describe "COPY FROM" do
100
100
  ReservedWordModel.copy_from File.expand_path('spec/fixtures/reserved_words.csv'), :delimiter => "\t"
101
101
  ReservedWordModel.order(:id).map{|r| r.attributes}.should == [{"group"=>"group name", "id"=>1, "select"=>"test select"}]
102
102
  end
103
-
103
+
104
104
  it "should import even last columns have empty values" do
105
105
  TestExtendedModel.copy_from File.expand_path('spec/fixtures/comma_with_header_empty_values_at_the_end.csv')
106
- TestExtendedModel.order(:id).map{|r| r.attributes}.should ==
106
+ TestExtendedModel.order(:id).map{|r| r.attributes}.should ==
107
107
  [{"id"=>1, "data"=>"test data 1", "more_data"=>nil, "other_data"=>nil, "final_data"=>nil},
108
108
  {"id"=>2, "data"=>"test data 2", "more_data"=>"9", "other_data"=>nil, "final_data"=>nil},
109
109
  {"id"=>3, "data"=>"test data 2", "more_data"=>"9", "other_data"=>nil, "final_data"=>"0"}]
110
110
  end
111
-
111
+
112
112
  it "should import even last columns have empty values with block" do
113
113
  TestExtendedModel.copy_from File.expand_path('spec/fixtures/comma_with_header_empty_values_at_the_end.csv') do |row|
114
114
  row[4]="666"
115
115
  end
116
- TestExtendedModel.order(:id).map{|r| r.attributes}.should ==
116
+ TestExtendedModel.order(:id).map{|r| r.attributes}.should ==
117
117
  [{"id"=>1, "data"=>"test data 1", "more_data"=>nil, "other_data"=>nil, "final_data"=>"666"},
118
118
  {"id"=>2, "data"=>"test data 2", "more_data"=>"9", "other_data"=>nil, "final_data"=>"666"},
119
119
  {"id"=>3, "data"=>"test data 2", "more_data"=>"9", "other_data"=>nil, "final_data"=>"666"}]
@@ -150,4 +150,27 @@ describe "COPY FROM" do
150
150
  TestModel.copy_from File.open(File.expand_path('spec/fixtures/special_null_with_header.csv'), 'r'), :null => 'NULL'
151
151
  TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => nil}]
152
152
  end
153
+
154
+ it "should import with a carriage return in the value" do
155
+ TestModel.copy_from File.expand_path('spec/fixtures/comma_with_carriage_returns.csv')
156
+ TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => "test\ndata 1"}]
157
+ end
158
+
159
+ it "should import custom force null expressions from path" do
160
+ TestModel.copy_from File.expand_path('spec/fixtures/comma_with_empty_string.csv'), :null => '', :force_null => [:data]
161
+ TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => nil}]
162
+ end
163
+
164
+ it "should import tsv from path" do
165
+ TestModel.copy_from File.expand_path('spec/fixtures/tab_with_header.tsv'), :format => :tsv
166
+ TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}]
167
+ end
168
+
169
+ it "should import 2 lines from tsv and allow changes in block" do
170
+ TestModel.copy_from(File.open(File.expand_path('spec/fixtures/tab_with_two_lines.tsv'), 'r'), :format => :tsv) do |row|
171
+ row[1] = 'changed this data'
172
+ end
173
+ TestModel.order(:id).first.attributes.should == {'id' => 1, 'data' => 'changed this data'}
174
+ TestModel.count.should == 2
175
+ end
153
176
  end
@@ -19,15 +19,4 @@ describe "COPY TO BINARY" do
19
19
  it{ should == File.open('spec/fixtures/2_col_binary_data.dat', 'r:ASCII-8BIT').read }
20
20
  end
21
21
  end
22
-
23
- describe "should allow binary output to file" do
24
- it "should copy to disk if block is not given and a path is passed" do
25
- TestModel.copy_to '/tmp/export.dat', :format => :binary
26
- str = File.open('/tmp/export.dat', 'r:ASCII-8BIT').read
27
-
28
- str.should == File.open('spec/fixtures/2_col_binary_data.dat', 'r:ASCII-8BIT').read
29
-
30
- end
31
- end
32
-
33
22
  end
@@ -1,11 +1,11 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  describe "COPY TO" do
4
- before(:all) do
4
+ before(:each) do
5
5
  ActiveRecord::Base.connection.execute %{
6
6
  TRUNCATE TABLE test_models;
7
7
  SELECT setval('test_models_id_seq', 1, false);
8
- }
8
+ }
9
9
  TestModel.create :data => 'test data 1'
10
10
  end
11
11
 
@@ -21,6 +21,40 @@ describe "COPY TO" do
21
21
  end
22
22
  end
23
23
 
24
+ describe ".copy_to_enumerator" do
25
+ before(:each) do
26
+ TestModel.create :data => 'test data 2'
27
+ TestModel.create :data => 'test data 3'
28
+ TestModel.create :data => 'test data 4'
29
+ end
30
+
31
+ context "with no options" do
32
+ subject{ TestModel.copy_to_enumerator.to_a }
33
+ it{ should == File.open('spec/fixtures/comma_with_header_multi.csv', 'r').read.lines }
34
+ end
35
+
36
+ context "with tab as delimiter" do
37
+ subject{ TestModel.copy_to_enumerator(:delimiter => "\t").to_a }
38
+ it{ should == File.open('spec/fixtures/tab_with_header_multi.csv', 'r').read.lines }
39
+ end
40
+
41
+ context "with many records" do
42
+ context "enumerating in batches" do
43
+ subject{ TestModel.copy_to_enumerator(:buffer_lines => 2).to_a }
44
+ it do
45
+ expected = []
46
+ File.open('spec/fixtures/comma_with_header_multi.csv', 'r').read.lines.each_slice(2){|s| expected << s.join }
47
+ should == expected
48
+ end
49
+ end
50
+
51
+ context "excluding some records via a scope" do
52
+ subject{ TestModel.where("data not like '%3'").copy_to_enumerator.to_a }
53
+ it{ should == File.open('spec/fixtures/comma_with_header_and_scope.csv', 'r').read.lines }
54
+ end
55
+ end
56
+ end
57
+
24
58
  describe ".copy_to" do
25
59
  it "should copy and pass data to block if block is given and no path is passed" do
26
60
  File.open('spec/fixtures/comma_with_header.csv', 'r') do |f|
@@ -30,20 +64,18 @@ describe "COPY TO" do
30
64
  end
31
65
  end
32
66
 
33
- it "should copy to disk if block is not given and a path is passed" do
34
- TestModel.copy_to '/tmp/export.csv'
35
- File.open('spec/fixtures/comma_with_header.csv', 'r') do |fixture|
36
- File.open('/tmp/export.csv', 'r') do |result|
37
- result.read.should == fixture.read
38
- end
39
- end
40
- end
41
-
42
67
  it "should raise exception if I pass a path and a block simultaneously" do
43
68
  lambda do
44
69
  TestModel.copy_to('/tmp/bogus_path') do |row|
45
70
  end
46
71
  end.should raise_error
47
72
  end
73
+
74
+ it "accepts custom sql query to run instead on the current relation" do
75
+ TestModel.copy_to(nil, query: 'SELECT count(*) as "Total" FROM test_models') do |row|
76
+ expect(row).to eq("Total\n")
77
+ break
78
+ end
79
+ end
48
80
  end
49
81
  end
@@ -0,0 +1,3 @@
1
+ id,data
2
+ 1,"test
3
+ data 1"
@@ -0,0 +1,2 @@
1
+ id,data
2
+ 1,""
@@ -0,0 +1,4 @@
1
+ id,data
2
+ 1,test data 1
3
+ 2,test data 2
4
+ 4,test data 4
@@ -0,0 +1,5 @@
1
+ id,data
2
+ 1,test data 1
3
+ 2,test data 2
4
+ 3,test data 3
5
+ 4,test data 4
@@ -0,0 +1,2 @@
1
+ id data
2
+ 1 test data 1
@@ -0,0 +1,5 @@
1
+ id data
2
+ 1 test data 1
3
+ 2 test data 2
4
+ 3 test data 3
5
+ 4 test data 4
@@ -0,0 +1,3 @@
1
+ id data
2
+ 1 test data 1
3
+ 2 test data 2
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: postgres-copy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Diogo Biazus
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-09 00:00:00.000000000 Z
11
+ date: 2020-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '4.0'
33
+ version: '5.1'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '4.0'
40
+ version: '5.1'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: responders
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -116,15 +116,14 @@ extensions: []
116
116
  extra_rdoc_files: []
117
117
  files:
118
118
  - ".document"
119
+ - ".github/workflows/ruby.yml"
119
120
  - ".gitignore"
120
121
  - ".rspec"
121
- - ".travis.yml"
122
122
  - Gemfile
123
123
  - Gemfile.lock
124
124
  - LICENSE
125
125
  - README.md
126
126
  - Rakefile
127
- - VERSION
128
127
  - lib/postgres-copy.rb
129
128
  - lib/postgres-copy/acts_as_copy_target.rb
130
129
  - lib/postgres-copy/csv_responder.rb
@@ -135,8 +134,12 @@ files:
135
134
  - spec/copy_to_spec.rb
136
135
  - spec/fixtures/2_col_binary_data.dat
137
136
  - spec/fixtures/comma_inside_field.csv
137
+ - spec/fixtures/comma_with_carriage_returns.csv
138
+ - spec/fixtures/comma_with_empty_string.csv
138
139
  - spec/fixtures/comma_with_header.csv
140
+ - spec/fixtures/comma_with_header_and_scope.csv
139
141
  - spec/fixtures/comma_with_header_empty_values_at_the_end.csv
142
+ - spec/fixtures/comma_with_header_multi.csv
140
143
  - spec/fixtures/comma_without_header.csv
141
144
  - spec/fixtures/extra_field.rb
142
145
  - spec/fixtures/reserved_word_model.rb
@@ -150,7 +153,10 @@ files:
150
153
  - spec/fixtures/tab_with_error.csv
151
154
  - spec/fixtures/tab_with_extra_line.csv
152
155
  - spec/fixtures/tab_with_header.csv
156
+ - spec/fixtures/tab_with_header.tsv
157
+ - spec/fixtures/tab_with_header_multi.csv
153
158
  - spec/fixtures/tab_with_two_lines.csv
159
+ - spec/fixtures/tab_with_two_lines.tsv
154
160
  - spec/fixtures/test_extended_model.rb
155
161
  - spec/fixtures/test_model.rb
156
162
  - spec/spec.opts
@@ -173,35 +179,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
179
  - !ruby/object:Gem::Version
174
180
  version: '0'
175
181
  requirements: []
176
- rubyforge_project:
177
- rubygems_version: 2.5.1
182
+ rubygems_version: 3.1.2
178
183
  signing_key:
179
184
  specification_version: 4
180
185
  summary: Put COPY command functionality in ActiveRecord's model class
181
- test_files:
182
- - spec/copy_from_binary_spec.rb
183
- - spec/copy_from_spec.rb
184
- - spec/copy_to_binary_spec.rb
185
- - spec/copy_to_spec.rb
186
- - spec/fixtures/2_col_binary_data.dat
187
- - spec/fixtures/comma_inside_field.csv
188
- - spec/fixtures/comma_with_header.csv
189
- - spec/fixtures/comma_with_header_empty_values_at_the_end.csv
190
- - spec/fixtures/comma_without_header.csv
191
- - spec/fixtures/extra_field.rb
192
- - spec/fixtures/reserved_word_model.rb
193
- - spec/fixtures/reserved_words.csv
194
- - spec/fixtures/semicolon_with_different_header.csv
195
- - spec/fixtures/semicolon_with_header.csv
196
- - spec/fixtures/semicolon_with_quote.csv
197
- - spec/fixtures/special_null_with_header.csv
198
- - spec/fixtures/tab_only_data.csv
199
- - spec/fixtures/tab_with_different_header.csv
200
- - spec/fixtures/tab_with_error.csv
201
- - spec/fixtures/tab_with_extra_line.csv
202
- - spec/fixtures/tab_with_header.csv
203
- - spec/fixtures/tab_with_two_lines.csv
204
- - spec/fixtures/test_extended_model.rb
205
- - spec/fixtures/test_model.rb
206
- - spec/spec.opts
207
- - spec/spec_helper.rb
186
+ test_files: []
@@ -1,7 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.3.1
4
-
5
- branches:
6
- only:
7
- - master
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.5.6