salesforce_bulk_query-edge 0.2.1
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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +3 -0
- data/.travis.yml +26 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +168 -0
- data/Rakefile +20 -0
- data/env_setup-example.sh +13 -0
- data/lib/salesforce_bulk_query.rb +108 -0
- data/lib/salesforce_bulk_query/batch.rb +153 -0
- data/lib/salesforce_bulk_query/connection.rb +140 -0
- data/lib/salesforce_bulk_query/job.rb +199 -0
- data/lib/salesforce_bulk_query/logger.rb +44 -0
- data/lib/salesforce_bulk_query/query.rb +192 -0
- data/lib/salesforce_bulk_query/utils.rb +16 -0
- data/lib/salesforce_bulk_query/version.rb +3 -0
- data/new-version.sh +22 -0
- data/salesforce_bulk_query.gemspec +34 -0
- data/spec/salesforce_bulk_query_spec.rb +227 -0
- data/spec/spec_helper.rb +9 -0
- metadata +207 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module SalesforceBulkQuery
|
4
|
+
class Utils
|
5
|
+
# record count if they want to
|
6
|
+
def self.line_count(f)
|
7
|
+
i = 0
|
8
|
+
CSV.foreach(f, :headers => true,:encoding => "UTF-8") {|_| i += 1}
|
9
|
+
i
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.header(f)
|
13
|
+
File.open(f, &:readline).split(',').map{ |c| c.strip.delete('"') }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/new-version.sh
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
# get the new version
|
4
|
+
VERSION=`bundle exec ruby <<-EORUBY
|
5
|
+
|
6
|
+
require 'salesforce_bulk_query'
|
7
|
+
puts SalesforceBulkQuery::VERSION
|
8
|
+
|
9
|
+
EORUBY`
|
10
|
+
|
11
|
+
# create tag and push it
|
12
|
+
TAG="v$VERSION"
|
13
|
+
git tag $TAG
|
14
|
+
git push origin $TAG
|
15
|
+
|
16
|
+
# build and push the gem
|
17
|
+
gem build salesforce_bulk_query.gemspec
|
18
|
+
gem push "salesforce_bulk_query-$VERSION.gem"
|
19
|
+
|
20
|
+
# update the gem after a few secs
|
21
|
+
sleep 30
|
22
|
+
gem update salesforce_bulk_query
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib/", __FILE__)
|
3
|
+
require "salesforce_bulk_query/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'salesforce_bulk_query-edge'
|
7
|
+
s.version = SalesforceBulkQuery::VERSION
|
8
|
+
s.authors = ['Petr Cvengros']
|
9
|
+
s.email = ['petr.cvengros@gooddata.com']
|
10
|
+
|
11
|
+
s.required_ruby_version = '>= 1.9'
|
12
|
+
|
13
|
+
s.homepage = 'https://github.com/cvengros/salesforce_bulk_query'
|
14
|
+
s.summary = %q{Downloading data from Salesforce Bulk API made easy and scalable.}
|
15
|
+
s.description = %q{A library for downloading data from Salesforce Bulk API. We only focus on querying, other operations of the API aren't supported. Designed to handle a lot of data.}
|
16
|
+
s.license = 'BSD'
|
17
|
+
|
18
|
+
s.add_dependency 'json', '<= 2'
|
19
|
+
s.add_dependency 'xml-simple', '~> 1.1'
|
20
|
+
|
21
|
+
s.add_development_dependency 'multi_json', '~> 1.9'
|
22
|
+
s.add_development_dependency 'restforce', '~>1.4'
|
23
|
+
s.add_development_dependency 'rspec', '~>2.14'
|
24
|
+
s.add_development_dependency 'pry', '~>0.9'
|
25
|
+
s.add_development_dependency 'pry-stack_explorer', '~>0.4' if RUBY_PLATFORM != 'java'
|
26
|
+
s.add_development_dependency 'rake', '~> 10.3'
|
27
|
+
s.add_development_dependency 'coveralls', '~> 0.7'
|
28
|
+
s.add_development_dependency 'webmock', '~> 1.20'
|
29
|
+
|
30
|
+
s.files = `git ls-files`.split($/)
|
31
|
+
s.require_paths = ['lib']
|
32
|
+
|
33
|
+
s.rubygems_version = "1.3.7"
|
34
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'csv'
|
3
|
+
require 'tmpdir'
|
4
|
+
require 'logger'
|
5
|
+
require 'set'
|
6
|
+
require 'salesforce_bulk_query'
|
7
|
+
require 'restforce'
|
8
|
+
require 'webmock/rspec'
|
9
|
+
|
10
|
+
class Helper
|
11
|
+
DEFAULT_API_VERSION = '30.0'
|
12
|
+
def self.create_default_restforce
|
13
|
+
Restforce.new(
|
14
|
+
:username => ENV['USERNAME'],
|
15
|
+
:password => ENV['PASSWORD'],
|
16
|
+
:security_token => ENV['TOKEN'],
|
17
|
+
:client_id => ENV['CLIENT_ID'],
|
18
|
+
:client_secret => ENV['CLIENT_SECRET'],
|
19
|
+
:api_version => api_version
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.api_version
|
24
|
+
ENV['API_VERSION'] || DEFAULT_API_VERSION
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.create_default_api(restforce)
|
28
|
+
# switch off the normal logging
|
29
|
+
Restforce.log = false
|
30
|
+
|
31
|
+
SalesforceBulkQuery::Api.new(restforce,
|
32
|
+
:api_version => ENV['API_VERSION'] || DEFAULT_API_VERSION,
|
33
|
+
:logger => ENV['LOGGING'] ? Logger.new(STDOUT): nil
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# test co nejak nafakuje tu situaci v twc
|
39
|
+
describe SalesforceBulkQuery do
|
40
|
+
before :all do
|
41
|
+
WebMock.allow_net_connect!
|
42
|
+
|
43
|
+
@client = Helper.create_default_restforce
|
44
|
+
@api = Helper.create_default_api(@client)
|
45
|
+
@entity = ENV['ENTITY'] || 'Opportunity'
|
46
|
+
@field_list = (ENV['FIELD_LIST'] || "Id,CreatedDate").split(',')
|
47
|
+
@api_version = Helper.api_version
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "instance_url" do
|
51
|
+
it "gives you some reasonable url" do
|
52
|
+
url = @api.instance_url
|
53
|
+
url.should_not be_empty
|
54
|
+
url.should match(/salesforce\.com\//)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "query" do
|
59
|
+
context "if you give it an invalid SOQL" do
|
60
|
+
it "fails with argument error" do
|
61
|
+
expect{@api.query(@entity, "SELECT Id, SomethingInvalid FROM #{@entity}")}.to raise_error(ArgumentError)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
context "when you give it no options" do
|
65
|
+
it "downloads the data to a few files", :constraint => 'slow' do
|
66
|
+
result = @api.query(@entity, "SELECT #{@field_list.join(', ')} FROM #{@entity}", :count_lines => true)
|
67
|
+
filenames = result[:filenames]
|
68
|
+
filenames.should have_at_least(2).items
|
69
|
+
result[:jobs_done].should_not be_empty
|
70
|
+
|
71
|
+
# no duplicate filenames
|
72
|
+
expect(Set.new(filenames).length).to eq(filenames.length)
|
73
|
+
|
74
|
+
filenames.each do |filename|
|
75
|
+
File.size?(filename).should be_true
|
76
|
+
|
77
|
+
lines = CSV.read(filename)
|
78
|
+
|
79
|
+
if lines.length > 1
|
80
|
+
# first line should be the header
|
81
|
+
lines[0].should eql(@field_list)
|
82
|
+
|
83
|
+
# first id shouldn't be emtpy
|
84
|
+
lines[1][0].should_not be_empty
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
context "when we want to mock things" do
|
90
|
+
before(:each) do
|
91
|
+
WebMock.allow_net_connect!
|
92
|
+
end
|
93
|
+
after(:each) do
|
94
|
+
WebMock.allow_net_connect!
|
95
|
+
end
|
96
|
+
it "catches the timeout error for query" do
|
97
|
+
# stub the timeout on query
|
98
|
+
host = URI.parse(@api.instance_url).host
|
99
|
+
query_url = "#{host}/services/data/v#{@api_version}/query"
|
100
|
+
query_regexp = Regexp.new(query_url)
|
101
|
+
# 4 timeouts (first get the oldest record), then fake a
|
102
|
+
# 0 count query response
|
103
|
+
stub_request(:get, query_regexp).to_timeout.times(4).then.to_return(
|
104
|
+
:body => "{\"totalSize\":0,\"done\":true,\"records\":[]}",
|
105
|
+
:headers => {
|
106
|
+
"date"=>"Wed, 04 Feb 2015 01:18:45 GMT",
|
107
|
+
"set-cookie"=>"BrowserId=hahaha;Path=/;Domain=.salesforce.com;Expires=never",
|
108
|
+
"expires"=>"Thu, 01 Jan 1970 00:00:00 GMT",
|
109
|
+
"sforce-limit-info"=>"api-usage=6666/15000",
|
110
|
+
"content-type"=>"application/json;charset=UTF-8",
|
111
|
+
"transfer-encoding"=>"chunked"}
|
112
|
+
)
|
113
|
+
|
114
|
+
# do the actual request
|
115
|
+
WebMock.allow_net_connect!
|
116
|
+
result = @api.query(
|
117
|
+
@entity,
|
118
|
+
"SELECT #{@field_list.join(', ')} FROM #{@entity}",
|
119
|
+
:count_lines => true,
|
120
|
+
:single_batch => true
|
121
|
+
)
|
122
|
+
|
123
|
+
# check it
|
124
|
+
expect(result[:succeeded]).to be_true
|
125
|
+
expect(result[:unfinished_subqueries]).to be_empty
|
126
|
+
expect(result[:filenames]).not_to be_empty
|
127
|
+
expect(result[:jobs_done]).not_to be_empty
|
128
|
+
end
|
129
|
+
end
|
130
|
+
context "when you give it all the options" do
|
131
|
+
it "downloads a single file" do
|
132
|
+
tmp = Dir.mktmpdir
|
133
|
+
frm = "2000-01-01"
|
134
|
+
from = "#{frm}T00:00:00.000Z"
|
135
|
+
t = "2020-01-01"
|
136
|
+
to = "#{t}T00:00:00.000Z"
|
137
|
+
field = 'SystemModstamp'
|
138
|
+
result = @api.query(
|
139
|
+
"Account",
|
140
|
+
"SELECT Id, Name, Industry, Type FROM Account",
|
141
|
+
:check_interval => 30,
|
142
|
+
:directory_path => tmp,
|
143
|
+
:date_from => from,
|
144
|
+
:date_to => to,
|
145
|
+
:single_batch => true,
|
146
|
+
:count_lines => true,
|
147
|
+
:date_field => field
|
148
|
+
)
|
149
|
+
|
150
|
+
result[:filenames].should have(1).items
|
151
|
+
result[:jobs_done].should_not be_empty
|
152
|
+
|
153
|
+
filename = result[:filenames][0]
|
154
|
+
|
155
|
+
File.size?(filename).should be_true
|
156
|
+
lines = CSV.read(filename)
|
157
|
+
|
158
|
+
# first line should be the header
|
159
|
+
lines[0].should eql(["Id", "Name", "Industry", "Type"])
|
160
|
+
|
161
|
+
# first id shouldn't be emtpy
|
162
|
+
lines[1][0].should_not be_empty
|
163
|
+
|
164
|
+
filename.should match(tmp)
|
165
|
+
filename.should match(frm)
|
166
|
+
filename.should match(t)
|
167
|
+
filename.should match(field)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
context "when you give it a bad date_field" do
|
171
|
+
it "fails with argument error with no from date" do
|
172
|
+
expect{@api.query(@entity, "SELECT Id, CreatedDate FROM #{@entity}", :date_field => 'SomethingInvalid')}.to raise_error(ArgumentError)
|
173
|
+
end
|
174
|
+
it "fails with argument error with given from date" do
|
175
|
+
from = "2000-01-01T00:00:00.000Z"
|
176
|
+
expect{
|
177
|
+
@api.query(
|
178
|
+
@entity,
|
179
|
+
"SELECT Id, CreatedDate FROM #{@entity}",
|
180
|
+
:date_field => 'SomethingInvalid',
|
181
|
+
:date_from => from
|
182
|
+
)
|
183
|
+
}.to raise_error(ArgumentError)
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
context "when you give it a short time limit" do
|
188
|
+
it "downloads some stuff is unfinished" do
|
189
|
+
result = @api.query(
|
190
|
+
"Opportunity",
|
191
|
+
"SELECT Id, Name, CreatedDate FROM Opportunity",
|
192
|
+
:time_limit => 15
|
193
|
+
)
|
194
|
+
# one of them should be non-empty
|
195
|
+
expect((! result[:unfinished_subqueries].empty?) || (! result[:filenames].empty?)).to eq true
|
196
|
+
end
|
197
|
+
end
|
198
|
+
context "when you pass a short job time limit" do
|
199
|
+
it "creates quite a few jobs quickly", :skip => true do
|
200
|
+
# development only
|
201
|
+
result = @api.query(
|
202
|
+
@entity,
|
203
|
+
"SELECT Id, CreatedDate FROM #{@entity}",
|
204
|
+
:count_lines => true,
|
205
|
+
:job_time_limit => 60
|
206
|
+
)
|
207
|
+
require 'pry'; binding.pry
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
describe "start_query" do
|
213
|
+
it "starts a query that finishes some time later" do
|
214
|
+
query = @api.start_query("Opportunity", "SELECT Id, Name, CreatedDate FROM Opportunity", :single_batch => true)
|
215
|
+
|
216
|
+
# get a cofee
|
217
|
+
sleep(60*2)
|
218
|
+
|
219
|
+
# check the status
|
220
|
+
result = query.get_available_results
|
221
|
+
expect(result[:succeeded]).to eq true
|
222
|
+
result[:filenames].should have_at_least(1).items
|
223
|
+
result[:jobs_done].should_not be_empty
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: salesforce_bulk_query-edge
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Petr Cvengros
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-07-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "<="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "<="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: xml-simple
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: multi_json
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.9'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.9'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: restforce
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.4'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.14'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.14'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.9'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.9'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry-stack_explorer
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.4'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.4'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '10.3'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '10.3'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: coveralls
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.7'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.7'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: webmock
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '1.20'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '1.20'
|
153
|
+
description: A library for downloading data from Salesforce Bulk API. We only focus
|
154
|
+
on querying, other operations of the API aren't supported. Designed to handle a
|
155
|
+
lot of data.
|
156
|
+
email:
|
157
|
+
- petr.cvengros@gooddata.com
|
158
|
+
executables: []
|
159
|
+
extensions: []
|
160
|
+
extra_rdoc_files: []
|
161
|
+
files:
|
162
|
+
- ".gitignore"
|
163
|
+
- ".rspec"
|
164
|
+
- ".travis.yml"
|
165
|
+
- Gemfile
|
166
|
+
- LICENSE
|
167
|
+
- README.md
|
168
|
+
- Rakefile
|
169
|
+
- env_setup-example.sh
|
170
|
+
- lib/salesforce_bulk_query.rb
|
171
|
+
- lib/salesforce_bulk_query/batch.rb
|
172
|
+
- lib/salesforce_bulk_query/connection.rb
|
173
|
+
- lib/salesforce_bulk_query/job.rb
|
174
|
+
- lib/salesforce_bulk_query/logger.rb
|
175
|
+
- lib/salesforce_bulk_query/query.rb
|
176
|
+
- lib/salesforce_bulk_query/utils.rb
|
177
|
+
- lib/salesforce_bulk_query/version.rb
|
178
|
+
- new-version.sh
|
179
|
+
- salesforce_bulk_query.gemspec
|
180
|
+
- spec/salesforce_bulk_query_spec.rb
|
181
|
+
- spec/spec_helper.rb
|
182
|
+
homepage: https://github.com/cvengros/salesforce_bulk_query
|
183
|
+
licenses:
|
184
|
+
- BSD
|
185
|
+
metadata: {}
|
186
|
+
post_install_message:
|
187
|
+
rdoc_options: []
|
188
|
+
require_paths:
|
189
|
+
- lib
|
190
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '1.9'
|
195
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
196
|
+
requirements:
|
197
|
+
- - ">="
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
version: '0'
|
200
|
+
requirements: []
|
201
|
+
rubyforge_project:
|
202
|
+
rubygems_version: 2.6.8
|
203
|
+
signing_key:
|
204
|
+
specification_version: 4
|
205
|
+
summary: Downloading data from Salesforce Bulk API made easy and scalable.
|
206
|
+
test_files: []
|
207
|
+
has_rdoc:
|