salesforce-sql 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []