salesforce-sql 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bdbae01f13b8d65322ceee413d9fe5fda6414ae0
4
+ data.tar.gz: 20dea60817eeeea2686de725bb2ce985304c2615
5
+ SHA512:
6
+ metadata.gz: 619f77b5a3941790c970aa083441c425f4dc5e66f858e9eaa13b07c134a37c3385fe2de577946dc48f6fd4b888734dfcb18a46b831b9cae57848827876c28a6a
7
+ data.tar.gz: 19848c066cf11ca33edc1099997a4c059aa27b78271ae61605158286ce1801f18ace3e074c424aed4c2f6e1dd63519d2036b5a8d80f00db658ca7c2e7d0c31ed
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in salesforce-sql.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Juan Breinlinger
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # Salesforce::Sql
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'salesforce-sql'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install salesforce-sql
18
+
19
+ ## Usage
20
+
21
+ This gem is in a very early stage, please use under your own risk:
22
+
23
+ copy a table:
24
+
25
+
26
+ ```
27
+ require 'salesforce/sql'
28
+
29
+ # Crednetials:
30
+ src_credentials = {
31
+ :host => 'test.salesforce.com',
32
+ :username => 'user@example.com.source',
33
+ :password => 'pass',
34
+ :client_id => 'client_id',
35
+ :client_secret => 'client_secret'
36
+ }
37
+
38
+ dst_credentials = {
39
+ :host => 'test.salesforce.com',
40
+ :username => 'user@example.com.destination',
41
+ :password => 'pass',
42
+ :client_id => 'client_id',
43
+ :client_secret => 'client_secret'
44
+ }
45
+
46
+ # Initialize sql objects
47
+ src = Salesforce::Sql::App.new src_credentials
48
+ dst = Salesforce::Sql::App.new dst_credentials
49
+
50
+ account_ignore_fields = [
51
+ 'Status__c',
52
+ 'Other__c',
53
+ ]
54
+
55
+ # Delete current Account content
56
+ dst.delete 'Account'
57
+
58
+ # Copy src Accounts to Destination
59
+ dst.copy_object source: src,
60
+ object: 'Account',
61
+ ignore_fields: account_ignore_fields
62
+
63
+ # Delete contact table
64
+ dst.delete 'Contact'
65
+
66
+ # Copy contacts preserving IDs
67
+ dst.copy_object source: src,
68
+ object: 'Contact',
69
+ object_ids: contact_ids,
70
+ ignore_fields: contact_ignore_fields,
71
+ dependencies: [
72
+ {
73
+ dependency_object: 'Account',
74
+ dependency_object_pk: 'Name',
75
+ object_fk_field: 'AccountId',
76
+ },
77
+ ]
78
+
79
+ ```
80
+
81
+ You could also do partial copy specifying ids:
82
+
83
+ ```
84
+ account_ids = File.read('ids.txt').split("\n").sort.uniq
85
+ dst.copy_object source: src,
86
+ object: 'Account',
87
+ object_ids: account_ids,
88
+ ignore_fields: account_ignore_fields
89
+
90
+
91
+ ```
92
+
93
+ ## Contributing
94
+
95
+ 1. Fork it ( https://github.com/[my-github-username]/salesforce-sql/fork )
96
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
97
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
98
+ 4. Push to the branch (`git push origin my-new-feature`)
99
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,11 @@
1
+ require 'salesforce/sql/version'
2
+ require 'salesforce/sql/app'
3
+ require 'salesforce_bulk'
4
+ require 'restforce'
5
+ require 'terminfo'
6
+ require 'pry'
7
+
8
+ module Salesforce
9
+ module Sql
10
+ end
11
+ end
@@ -0,0 +1,226 @@
1
+ module Salesforce
2
+ module Sql
3
+ class App
4
+
5
+ attr_accessor :debug
6
+ attr_accessor :username
7
+ attr_accessor :step
8
+
9
+ def initialize credentials
10
+
11
+ # Login credentials
12
+ @restforce_client = restforce_rest_login credentials
13
+ @salesforce_bulk_client = salesforce_bulk_login credentials[:username], credentials[:password], credentials[:host]
14
+ @username = credentials[:username]
15
+
16
+ # Default variables:
17
+ @bulk_api_step = 10000
18
+ @debug = false
19
+ @step = 100
20
+ @default_ignore_fields = [
21
+ 'Id',
22
+ 'IsDeleted',
23
+ 'MasterRecordId',
24
+ 'RecordTypeId',
25
+ 'ParentId',
26
+ 'OwnerId',
27
+ 'CreatedById',
28
+ 'CreatedDate',
29
+ 'LastModifiedDate',
30
+ 'LastModifiedById',
31
+ 'SystemModstamp',
32
+ 'LastActivityDate',
33
+ ]
34
+
35
+ end
36
+
37
+ def get_fields object
38
+
39
+ fields = @restforce_client.describe(object)['fields'].select do |field|
40
+ field['name'][-4..-1] != '__pc' && field['calculated'] != true
41
+ end
42
+ fields.map {|f| f['name']}
43
+
44
+ end
45
+
46
+ def query_select_in query, ids = []
47
+ retval = []
48
+ if ids.empty?
49
+ retval = normalize_query @restforce_client.query(query)
50
+ else
51
+ (0..(ids.size-1)).step(@step).each do |n|
52
+
53
+ # Create the query
54
+ ids_string = ids[n..(n+@step-1)].map {|i| "'#{i}'"}.join ','
55
+ stepped_query = query + " IN (#{ids_string})"
56
+
57
+ # run it and add it to the result
58
+ retval += normalize_query @restforce_client.query(stepped_query)
59
+ end
60
+ end
61
+
62
+ retval
63
+ end
64
+
65
+ def query query, ids = []
66
+
67
+ retval = []
68
+ if ids.empty?
69
+ retval = normalize_query @restforce_client.query(query)
70
+ else
71
+ (0..(ids.size-1)).step(@step).each do |n|
72
+
73
+ # Create the query
74
+ ids_string = ids[n..(n+@step-1)].map {|i| "'#{i}'"}.join ','
75
+ stepped_query = query + " WHERE Id IN (#{ids_string})"
76
+
77
+ # run it and add it to the result
78
+ retval += normalize_query @restforce_client.query(stepped_query)
79
+ end
80
+ end
81
+
82
+ retval
83
+
84
+ end
85
+
86
+ def delete object
87
+
88
+ query = "Select Id FROM #{object}"
89
+ bulk_delete_records = normalize_query @restforce_client.query(query)
90
+
91
+ print_debug "#{bulk_delete_records.size} #{object} records added to delete on #{self.username}"
92
+
93
+ bulk_delete object, bulk_delete_records if !bulk_delete_records.empty?
94
+
95
+ end
96
+
97
+ # Description: Copy an object from one source to target
98
+ # Parameters:
99
+ # object: The object to copy
100
+ # object_ids: An array of object ids to limit the query
101
+ # ignore_fields: An array of fields to ignore
102
+ # dependencies: When trying to copy a TABLE with DEPENDENCIES
103
+ # dependency_object: The DEPENDENCY object name
104
+ # dependency_object_pk: the DEPENENCY field to be used as primary key
105
+ # object_fk_field: The foreign key field name in the TABLE
106
+
107
+ def copy_object source:, object:, object_ids: [], ignore_fields: [], dependencies: []
108
+
109
+ # Remove well known problematic fields and merge them with user requirements:
110
+ ignore_fields = (ignore_fields + @default_ignore_fields).uniq
111
+
112
+ # Get all the fields from source and destination removing __pc and calculated ones
113
+ source_object_fields = get_fields object
114
+ target_object_fields = source.get_fields object
115
+
116
+ # Get common fields
117
+ object_fields = source_object_fields & target_object_fields
118
+
119
+ # Get all the records from the source sandbox and store them in bulk_import_records
120
+ bulk_import_records = source.query "Select #{object_fields.join(',')} FROM #{object}", object_ids
121
+
122
+ # Dependencies ID Matching:
123
+ dependencies.each do |dep|
124
+
125
+ # Export the dependency object ids from the source sandbox
126
+ dependency_ids = bulk_import_records.map{|row| row[dep[:object_fk_field]]}.sort.uniq
127
+
128
+ # Use those Ids to get the object records from source including the dependency_object_pk
129
+ source_object = source.query "Select Id,#{dep[:dependency_object_pk]} FROM #{dep[:dependency_object]}", dependency_ids
130
+
131
+ # Get the dependency_object_pk values and export the IDs from the target object
132
+ dependency_object_pk_values = source_object.map {|row| row[dep[:dependency_object_pk]].gsub("'", %q(\\\'))}
133
+ target_object = self.query_select_in "Select Id,#{dep[:dependency_object_pk]} FROM #{dep[:dependency_object]} WHERE #{dep[:dependency_object_pk]}", dependency_object_pk_values
134
+
135
+ # Now we have source_object and target_object ids and values, we can do the mapping on bulk_import_records
136
+ source_object.each do |row|
137
+ source_id = row['Id']
138
+ target_id = target_object.select{|r| r[dep[:dependency_object_pk]] == row[dep[:dependency_object_pk]]}.first['Id']
139
+ bulk_import_records.each {|r| r[dep[:object_fk_field]] = target_id if r[dep[:object_fk_field]] == source_id}
140
+ end
141
+
142
+ end
143
+
144
+ # Remove ignored fields
145
+ bulk_import_records.each do |row|
146
+ ignore_fields.each {|f| row.delete f}
147
+ end
148
+
149
+ print_debug "#{bulk_import_records.size} #{object} records added to import on #{self.username}"
150
+
151
+ # Import the data using salesforce_bulk
152
+ if !bulk_import_records.empty?
153
+ bulk_insert object, bulk_import_records
154
+ end
155
+
156
+ end
157
+
158
+ private
159
+
160
+ def bulk_insert object, records
161
+ (0..(records.size-1)).step(@bulk_api_step).each do |n|
162
+ job = @salesforce_bulk_client.create object, records[n..n+@bulk_api_step-1]
163
+ salesforce_bulk_job_status job
164
+ end
165
+ end
166
+
167
+ def bulk_delete object, records
168
+ (0..(records.size-1)).step(@bulk_api_step).each do |n|
169
+ job = @salesforce_bulk_client.delete object, records[n..n+@bulk_api_step-1]
170
+ salesforce_bulk_job_status job
171
+ end
172
+ end
173
+
174
+ def normalize_query records
175
+ records.to_a.each do |row|
176
+ row.delete 'attributes'
177
+ row.delete 'IsPersonAccount'
178
+ end
179
+ end
180
+
181
+ def delete_ignored_fields set
182
+ set.to_a.each do |row|
183
+ @default_ignore_fields.each {|field| row.delete field }
184
+ end
185
+ end
186
+
187
+ def salesforce_bulk_job_status job
188
+ sleep 1 while job.check_batch_status.match(/InProgress|Queued/)
189
+ print_debug "Bulk job status: #{job.check_batch_status}"
190
+ job.check_batch_status == "Completed" ? true : false
191
+ end
192
+
193
+ def print_debug message
194
+ puts "DEBUG: #{message[0..TermInfo.screen_size.last]}" if @debug
195
+ end
196
+
197
+ def restforce_rest_login credentials
198
+ begin
199
+ client = Restforce.new :host => credentials[:host],
200
+ :username => credentials[:username],
201
+ :password => credentials[:password],
202
+ :client_id => credentials[:client_id],
203
+ :client_secret => credentials[:client_secret]
204
+ client.authenticate!
205
+ client
206
+ rescue => e
207
+ puts "Error trying to login rest API using: #{credentials[:username]} "
208
+ puts e
209
+ exit 1
210
+ end
211
+ end
212
+
213
+ def salesforce_bulk_login user, pass, host
214
+ begin
215
+ sandbox = host.match(/login/) ? nil : true
216
+ SalesforceBulk::Api.new(user,pass,sandbox)
217
+ rescue => e
218
+ puts "Error trying to login bulk API using: #{user} "
219
+ puts e
220
+ exit 1
221
+ end
222
+ end
223
+
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,5 @@
1
+ module Salesforce
2
+ module Sql
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'salesforce/sql/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "salesforce-sql"
8
+ spec.version = Salesforce::Sql::VERSION
9
+ spec.authors = ["Juan Breinlinger"]
10
+ spec.email = ["<juan.brein@breins.net>"]
11
+ spec.summary = %q{}
12
+ spec.description = %q{}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_runtime_dependency 'salesforce_bulk'
25
+ spec.add_runtime_dependency 'restforce'
26
+ spec.add_runtime_dependency 'pry'
27
+ spec.add_runtime_dependency 'ruby-terminfo'
28
+
29
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: salesforce-sql
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Juan Breinlinger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: salesforce_bulk
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: restforce
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: ruby-terminfo
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: ''
98
+ email:
99
+ - "<juan.brein@breins.net>"
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - lib/salesforce/sql.rb
110
+ - lib/salesforce/sql/app.rb
111
+ - lib/salesforce/sql/version.rb
112
+ - salesforce-sql.gemspec
113
+ homepage: ''
114
+ licenses:
115
+ - MIT
116
+ metadata: {}
117
+ post_install_message:
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubyforge_project:
133
+ rubygems_version: 2.2.2
134
+ signing_key:
135
+ specification_version: 4
136
+ summary: ''
137
+ test_files: []