github.com/mhlo/force@v0.22.28-0.20150915022417-6d05ecfb0b47/bulk.go (about) 1 package main 2 3 /* 4 5 bulk command 6 force bulk insert mydata.csv 7 8 The load process involves these steps 9 1. Create a job 10 https://instance_name—api.salesforce.com/services/async/APIversion/job 11 payload: 12 <jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload"> 13 <operation>insert</operation> 14 <object>Account</object> 15 <contentType>CSV</contentType> 16 </jobInfo> 17 2. Add batches to the created job 18 https://instance_name—api.salesforce.com/services/async/APIversion/job/jobid/batch 19 payload: 20 <sObjects xmlns="http://www.force.com/2009/06/asyncapi/dataload"> 21 <sObject> 22 <description>Created from Bulk API on Tue Apr 14 11:15:59 PDT 2009</description> 23 <name>[Bulk API] Account 0 (batch 0)</name> 24 </sObject> 25 <sObject> 26 <description>Created from Bulk API on Tue Apr 14 11:15:59 PDT 2009</description> 27 <name>[Bulk API] Account 1 (batch 0)</name> 28 </sObject> 29 </sObjects> 30 3. Close job (I assume this submits the job???) 31 https://instance_name—api.salesforce.com/services/async/APIversion/job/jobId 32 payload: 33 <jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload"> 34 <state>Closed</state> 35 </jobInfo> 36 37 Jobs and batches can be monitored. 38 39 bulk command 40 force bulk job <jobId> 41 42 bulk command 43 force bulk batches <jobId> 44 45 bulk command 46 force bulk batch <batchId> 47 48 49 50 51 52 */ 53 54 import ( 55 "encoding/xml" 56 "fmt" 57 "io/ioutil" 58 "os" 59 "time" 60 ) 61 62 var cmdBulk = &Command{ 63 Run: runBulk, 64 Usage: "bulk insert Account [csv file]", 65 Short: "Load csv file use Bulk API", 66 Long: ` 67 Load csv file use Bulk API 68 69 Examples: 70 71 force bulk insert Account [csv file] 72 73 force bulk update Account [csv file] 74 75 force bulk job [job id] 76 77 force bulk batches [job id] 78 79 force bulk batch [job id] [batch id] 80 81 force bulk batch retrieve [job id] [batch id] 82 83 force bulk query Account [SOQL] [csv output] 84 85 force bulk query retrieve [job id] [batch id] 86 `, 87 } 88 89 func runBulk(cmd *Command, args []string) { 90 91 if len(args) == 1 { 92 ErrorAndExit("Invalid command") 93 } else if len(args) == 2 { 94 if args[0] == "insert" { 95 ErrorAndExit("Missing argument for insert") 96 } else if args[0] == "job" { 97 showJobDetails(args[1]) 98 } else if args[0] == "batches" { 99 listBatches(args[1]) 100 } else { 101 ErrorAndExit("Invalid command") 102 } 103 } else if len(args) == 3 { 104 if args[0] == "insert" { 105 createBulkInsertJob(args[2], args[1], "CSV") 106 } else if args[0] == "update" { 107 createBulkUpdateJob(args[2], args[1], "CSV") 108 } else if args[0] == "batch" { 109 showBatchDetails(args[1], args[2]) 110 } else if args[0] == "query" { 111 if args[1] == "retrieve" { 112 ErrorAndExit("Query retrieve requires a job id and a batch id") 113 } else { 114 fmt.Println(string(doBulkQuery(args[1], args[2], "CSV"))) 115 } 116 } 117 } else if len(args) == 4 { 118 if args[0] == "insert" { 119 createBulkInsertJob(args[2], args[1], args[3]) 120 } else if args[0] == "update" { 121 createBulkUpdateJob(args[2], args[1], args[3]) 122 } else if args[0] == "batch" { 123 getBatchResults(args[2], args[3]) 124 } else if args[0] == "query" { 125 if args[1] == "retrieve" { 126 fmt.Println(string(getBulkQueryResults(args[2], args[3]))) 127 } else if args[1] == "status" { 128 DisplayBatchInfo(getBatchDetails(args[2], args[3])) 129 } else { 130 fmt.Println(string(doBulkQuery(args[1], args[2], args[3]))) 131 } 132 } 133 } 134 } 135 136 func doBulkQuery(objectType string, soql string, contenttype string) []byte { 137 jobInfo, err := createBulkJob(objectType, "query", contenttype) 138 force, _ := ActiveForce() 139 140 result, err := force.BulkQuery(soql, jobInfo.Id, contenttype) 141 fmt.Fprintln(os.Stderr, "bq: ", err, "result:", result, "job:", jobInfo) 142 if err != nil { 143 closeBulkJob(jobInfo.Id) 144 ErrorAndExit(err.Error()) 145 } 146 147 closeBulkJob(jobInfo.Id) 148 var bytes []byte 149 for _, sleepTime := range []time.Duration{125 * time.Second, 40 * time.Second, 150 60 * time.Second, 45 * time.Second} { 151 time.Sleep(sleepTime) 152 bytes = getBulkQueryResults(jobInfo.Id, result.Id) 153 fmt.Fprintln(os.Stderr, "gbqr:", len(bytes)) 154 if len(bytes) > 2 { 155 return bytes 156 } 157 } 158 return bytes 159 } 160 161 func getBulkQueryResults(jobId string, batchId string) (data []byte) { 162 resultIds := retrieveBulkQuery(jobId, batchId) 163 hasMultipleResultFiles := len(resultIds) > 1 164 fmt.Fprintln(os.Stderr, "gbqrSub:", resultIds) 165 166 for _, resultId := range resultIds { 167 //since this is going to stdOut, simply add header to separate "files" 168 //if it's all in the same file, don't print this separator. 169 if hasMultipleResultFiles { 170 resultHeader := fmt.Sprint("ResultId: ", resultId, "\n") 171 data = append(data[:], []byte(resultHeader)...) 172 } 173 //get next file, and append 174 var newData []byte = retrieveBulkQueryResults(jobId, batchId, resultId) 175 fmt.Fprintln(os.Stderr, "rbqr return:", len(newData)) 176 data = append(data[:], newData...) 177 } 178 179 return 180 } 181 182 func retrieveBulkQuery(jobId string, batchId string) (resultIds []string) { 183 force, _ := ActiveForce() 184 185 jobInfo, err := force.RetrieveBulkQuery(jobId, batchId) 186 if err != nil { 187 ErrorAndExit(err.Error()) 188 } 189 190 var resultList struct { 191 Results []string `xml:"result"` 192 } 193 194 xml.Unmarshal(jobInfo, &resultList) 195 resultIds = resultList.Results 196 return 197 } 198 199 func retrieveBulkQueryResults(jobId string, batchId string, resultId string) (data []byte) { 200 force, _ := ActiveForce() 201 202 data, err := force.RetrieveBulkQueryResults(jobId, batchId, resultId) 203 if err != nil { 204 ErrorAndExit(err.Error()) 205 } 206 return 207 } 208 209 func showJobDetails(jobId string) { 210 jobInfo := getJobDetails(jobId) 211 DisplayJobInfo(jobInfo) 212 } 213 214 func listBatches(jobId string) { 215 batchInfos := getBatches(jobId) 216 DisplayBatchList(batchInfos) 217 } 218 219 func showBatchDetails(jobId string, batchId string) { 220 batchInfo := getBatchDetails(jobId, batchId) 221 DisplayBatchInfo(batchInfo) 222 } 223 224 func getBatchResults(jobId string, batchId string) { 225 force, _ := ActiveForce() 226 227 data, err := force.RetrieveBulkBatchResults(jobId, batchId) 228 fmt.Println(data) 229 if err != nil { 230 ErrorAndExit(err.Error()) 231 } 232 return 233 } 234 235 func getJobDetails(jobId string) (jobInfo JobInfo) { 236 force, _ := ActiveForce() 237 238 jobInfo, err := force.GetJobInfo(jobId) 239 240 if err != nil { 241 ErrorAndExit(err.Error()) 242 } 243 return 244 } 245 246 func getBatches(jobId string) (batchInfos []BatchInfo) { 247 force, _ := ActiveForce() 248 249 batchInfos, err := force.GetBatches(jobId) 250 251 if err != nil { 252 ErrorAndExit(err.Error()) 253 } 254 return 255 } 256 257 func getBatchDetails(jobId string, batchId string) (batchInfo BatchInfo) { 258 force, _ := ActiveForce() 259 260 batchInfo, err := force.GetBatchInfo(jobId, batchId) 261 262 if err != nil { 263 ErrorAndExit(err.Error()) 264 } 265 return 266 } 267 268 func createBulkInsertJob(csvFilePath string, objectType string, format string) { 269 jobInfo, err := createBulkJob(objectType, "insert", format) 270 if err != nil { 271 ErrorAndExit(err.Error()) 272 } else { 273 batchInfo, err := addBatchToJob(csvFilePath, jobInfo.Id) 274 if err != nil { 275 closeBulkJob(jobInfo.Id) 276 ErrorAndExit(err.Error()) 277 } else { 278 closeBulkJob(jobInfo.Id) 279 fmt.Printf("Job created ( %s ) - for job status use\n force bulk batch %s %s\n", jobInfo.Id, jobInfo.Id, batchInfo.Id) 280 } 281 } 282 } 283 284 func createBulkUpdateJob(csvFilePath string, objectType string, format string) { 285 jobInfo, err := createBulkJob(objectType, "update", format) 286 if err != nil { 287 ErrorAndExit(err.Error()) 288 } else { 289 _, err := addBatchToJob(csvFilePath, jobInfo.Id) 290 if err != nil { 291 closeBulkJob(jobInfo.Id) 292 ErrorAndExit(err.Error()) 293 } else { 294 closeBulkJob(jobInfo.Id) 295 } 296 } 297 } 298 299 func addBatchToJob(csvFilePath string, jobId string) (result BatchInfo, err error) { 300 301 force, _ := ActiveForce() 302 303 filedata, err := ioutil.ReadFile(csvFilePath) 304 305 result, err = force.AddBatchToJob(string(filedata), jobId) 306 return 307 } 308 309 func getBatchInfo(jobId string, batchId string) (batchInfo BatchInfo, err error) { 310 force, _ := ActiveForce() 311 batchInfo, err = force.GetBatchInfo(jobId, batchId) 312 return 313 } 314 315 func createBulkJob(objectType string, operation string, fileFormat string) (jobInfo JobInfo, err error) { 316 force, _ := ActiveForce() 317 318 xml := ` 319 <jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload"> 320 <operation>%s</operation> 321 <object>%s</object> 322 <contentType>%s</contentType> 323 </jobInfo> 324 ` 325 data := fmt.Sprintf(xml, operation, objectType, fileFormat) 326 jobInfo, err = force.CreateBulkJob(data) 327 return 328 } 329 330 func closeBulkJob(jobId string) (jobInfo JobInfo, err error) { 331 force, _ := ActiveForce() 332 333 xml := ` 334 <jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload"> 335 <state>Closed</state> 336 </jobInfo> 337 ` 338 jobInfo, err = force.CloseBulkJob(jobId, xml) 339 if err != nil { 340 ErrorAndExit(err.Error()) 341 } 342 return 343 }