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  }