SOQL tags for Golang

If you are a Golang developer intending to write a client that interacts with Salesforce, this post is for you. We’ll share how you can annotate your Golang structs to generate SOQL queries to be used in Salesforce APIs and how this will make it easy for you to write Golang clients for Salesforce, without having to hard code those queries.

Querying Salesforce Records via APIs

Salesforce offers REST APIs that allow any third party to integrate with the platform, using their language of choice. One of the API endpoints (/services/data/<version>/query) allows developers to query Salesforce records using SOQL queries. SOQL is a very powerful SQL-like query language that allows developers to query Salesforce. It has extensive support for a conditional expression to filter the objects being retrieved, as well as very powerful way of performing join operations using relationship queries.

Let’s take a look at an example using the Salesforce object ‘Account’. Below is an incomplete list of fields for the ‘Account’ object. For context, you can find the complete list of fields here.

FieldName Type Details
Name string Account name
AccountNumber string Account number assigned to this account
AccountSource picklist Source of the account record. For example, Advertisement, Data.com, or Trade Show. The source is selected from a picklist of available values
BillingAddress string Billing address for this account.
HasOptedOutOfEmail boolean Indicates whether the contact doesn’t want to receive email from Salesforce
LastActivityDate Date Value is one of the following, whichever is the most recent:
Due date of the most recent event logged against the record.
Due date of the most recently closed task associated with the record.
NumberOfEmployees Number Number of employees working at the company represented by this account

If you wanted to query all Accounts where AccountSource is one of ‘Advertisement’ or ‘Data.com’ you can write the following SOQL query:

SELECT Name,AccountNumber,AccountSource,BillingAddress,HasOptedOutOfEmail,LastActivityDate,NumberOfEmployees FROM Account WHERE AccountSource IN ('Advertisement', 'Data.com')

You would then pass the above string as a query parameter to the /services/data/<version>/query endpoint to fetch the relevant records from Salesforce as follows:

/services/data/v48.0/query?q=%0ASELECT%20Name%2CAccountNumber%2CAccountSource%2CBillingAddress%2CHasOptedOutOfEmail%2CLastActivityDate%2CNumberOfEmployees%20FROM%20Account%20WHERE%20AccountSource%20IN%20%28%27Advertisement%27%2C%20%27Data.com%27%29%0A

Now let’s look at how we might fetch Salesforce records using Golang.

Using Golang to fetch Salesforce Records

Golang is a statically typed, compiled programming language designed at Google. In recent years it has gained enough popularity to consider it a widely-adopted enterprise language. Want to learn more? Get started with Golang here.

Let’s see how quickly we can call the Salesforce query API using Golang. You start by defining the Golang struct representing the Salesforce object(s) you want to query. Let’s continue using the Account object described earlier. The Golang struct representation looks as follows (note the JSON annotations to marshal and unmarshal struct to/from JSON):

type Account struct {
    Name               string    `json:"Name"`
    AccountNumber      string    `json:"AccountNumber"`
    AccountSource      string    `json:"AccountSource"`
    BillingAddress     string    `json:"BillingAddress"`
    HasOptedOutOfEmail bool      `json:"HasOptedOutOfEmail"`
    LastActivityDate   time.Time `json:"LastActivityDate"`
    NumberOfEmployees  int       `json:"NumberOfEmployees"`
}

Then we would model the response from query API as follows:

type QueryResponse struct {
    Done            bool      `json:"done"`
    NextRecordsURL  string    `json:"nextRecordsUrl"`
    Accounts        []Account `json:"records"`
    TotalSize       int       `json:"totalSize"`
}

The following snippet shows one of the ways you can call the Salesforce query API and pass along the required SOQL string:

// Select clause specifying which columns to select from Account object
selectClause := "SELECT Name,AccountNumber,AccountSource,BillingAddress,HasOptedOutOfEmail,LastActivityDate,NumberOfEmployees FROM Account"

// Filter criteria for AccountSource
sources := []string{"Advertisement", "Data.com"}

// Build the soql query using strings.Builder
var soqlBuff strings.Builder
soqlBuff.WriteString(selectClause)

// Specify where clause with AccountSource IN as filter
soqlBuff.WriteString(" WHERE AccountSource IN (")

// Iterate over filter criteria and add those 
for indx, source := range sources {
    if indx > 0 {
        soqlBuff.WriteString(",")
    }
    soqlBuff.WriteString("'")
    soqlBuff.WriteString(source)
    soqlBuff.WriteString("'")
}
soqlBuff.WriteString(")")

// Add SOQL query as query parameter
values := url.Values{}
values.Set("q", soqlBuff.String())

// Create the encoded URL path
path := fmt.Sprintf("/services/data/v48.0/query?%s", values.Encode())
serverURL := "https://<your domain>.salesforce.com"

// Create http request
req, err := http.NewRequest(http.MethodGet, serverURL+path, nil)
if err != nil {
    // Handle error case
}
req.Header.Add("Authorization", "Bearer <session ID>")
req.Header.Add("Content-Type", "application/json")

// Create http client
httpClient := &http.Client{}

// Perform the query
resp, err := httpClient.Do(req)
if err != nil {
    // Handle error case
}

// Read the response
payload, err := ioutil.ReadAll(resp.Body)
if err != nil {
    // Handle error case
}

// Unmarshal the response in QueryResponse
var queryResponse QueryResponse
err = json.Unmarshal(payload, &queryResponse)
if err != nil {
    // Handle error case
}

Note that constructing a SOQL query is quite tedious—especially if you want to add or remove any field from the ‘select’ clause, or add/remove/update filter criteria from the ‘where’ clause. What is especially error prone is that you have to maintain the select clause separately from Account struct and there is always the danger of things falling out of sync. Additionally, adding a new filter criteria means more code changes with special consideration for different logical operators.

If you were to expose this as a method to be used by different parts of your program, then you will end up taking the SOQL string—or at least the ‘where’ clause—as parameter. That might look something like this:

func getAccounts(queryCriteria string) ([]Account, error) {
    // Select clause specifying which columns to select from Account object
    selectClause := "SELECT Name,AccountNumber,AccountSource,BillingAddress,HasOptedOutOfEmail,LastActivityDate,NumberOfEmployees FROM Account"
    
    // Build the soql query using strings.Builder
    var soqlBuff strings.Builder
    soqlBuff.WriteString(selectClause)
    soqlBuff.WriteString(queryCriteria)
    
    // Rest of the code similar to above snippet
    ...
}

Notice that you expose yourself to SOQL injection as you are constructing the query by concatenating the string passed to your method. You will need to add enough validations in your method to make sure that doesn’t happen.

You might be asking, isn’t there a simpler way? The answer is: Yes! That’s where new go-soql library comes in.

Introducing go-soql Library

As more and more developers embrace Golang it’s important to provide easier means and ways to interact with Salesforce APIs. One such step is to provide a SOQL annotation library that allows Golang developers to tag their structs similarly to how they tag their structs for JSON marshaling/unmarshaling. And that is the aim of new go-soql library.

This library was developed as an internal project within Salesforce, however we saw value in open sourcing this library for benefit of the Golang developer community. That being said, it’s important to note that this library is not officially supported by Salesforce. It is maintained by the team which heavily relies on this library for its own use cases.

Let’s take a look at how it works. We’ll use the same Account object and Golang struct defined above, so you can see exactly how this new SOQL tag library makes your life easier. We start by annotating our Account struct as follows:

type Account struct {
    Name               string    `soql:"selectColumn,fieldName=Name" json:"Name"`
    AccountNumber      string    `soql:"selectColumn,fieldName=AccountNumber" json:"AccountNumber"`
    AccountSource      string    `soql:"selectColumn,fieldName=AccountSource" json:"AccountSource"`
    BillingAddress     string    `soql:"selectColumn,fieldName=BillingAddress" json:"BillingAddress"`
    HasOptedOutOfEmail bool      `soql:"selectColumn,fieldName=HasOptedOutOfEmail" json:"HasOptedOutOfEmail"`
    LastActivityDate   time.Time `soql:"selectColumn,fieldName=LastActivityDate" json:"LastActivityDate"`
    NumberOfEmployees  int       `soql:"selectColumn,fieldName=NumberOfEmployees" json:"NumberOfEmployees"`
}

If we just want to generate the select clause without conditional expressions then it can be done as follows:

import "github.com/forcedotcom/go-soql"
...
selectClause, err := soql.MarshalSelectClause(Account{}, "")

This will result in following string:

Name,AccountNumber,AccountSource,BillingAddress,HasOptedOutOfEmail,LastActivityDate,NumberOfEmployees

Now if you add/remove any field in the Account struct it will automatically be reflected in the ‘select’ clause. As you might have already noticed this annotation goes hand in hand with json annotations so the response from the Salesforce API can be directly unmarshalled into the Account struct (as can be seen from the client code snippet in previous section).

While this addresses one problem in our initial code (addition/removal of new field needs to be maintained in two places), it is, of course, of limited use. We want the full SOQL query to be automatically generated. In order to do so, we need to define a couple more structs:

type AccountQueryCriteria struct {
    AccountSource []string `soql:"inOperator,fieldName=AccountSource"`
}

type AccountSoqlQuery struct {
    SelectClause  Account              `soql:"selectClause,tableName=Account"`
    WhereClause   AccountQueryCriteria `soql:"whereClause"`
}

Now, we can generate a complete SOQL query using the above two structs:

soqlStruct := AccountSoqlQuery{
    SelectClause: Account{},
    WhereClause: AccountQueryCriteria{
        AccountSource: []string{"Advertisement", "Data.com"},
    },
}
soqlQuery, err := soql.Marshal(soqlStruct)

And, voila! This will generate the SOQL query that we expect. Now we can use that in our call to /services/data/<version>/query API. Note that you can change the AccountQueryCriteria struct to modify the conditional expression. If you add more than one field to AccountQueryCriteria then they are combined using the AND logical operator.

Now your client code will look something like this. No more SOQL query string maintenance, or worrying about how to construct where clauses with combinations of different logical operators. Just define your struct and marshal it into SOQL query.

// Construct SOQL struct that includes which object you want to query and its query criteria
soqlStruct := AccountSoqlQuery{
    SelectClause: Account{},
    WhereClause: AccountQueryCriteria{
        AccountSource: []string{"Advertisement", "Data.com"},
    },
}

// Generate SOQL query using the above struct
soqlQuery, err := soql.Marshal(soqlStruct)

// Add SOQL query as query parameter
values := url.Values{}
values.Set("q", soqlQuery)

// Create the encoded URL path
path := fmt.Sprintf("/services/data/v48.0/query?%s",values.Encode())
serverURL := "https://<your domain>.salesforce.com"

// Create http request
req, err := http.NewRequest(http.MethodGet, serverURL+path, nil)
if err != nil {
   // Handle error case
}
req.Header.Add("Authorization", "Bearer <session ID>")
req.Header.Add("Content-Type", "application/json")

// Create http client
httpClient := &http.Client{}

// Perform the query
resp, err := httpClient.Do(req)
if err != nil {
   // Handle error case
}

// Read the response
payload, err := ioutil.ReadAll(resp.Body)
if err != nil {
   // Handle error case    
}

// Unmarshal the response in QueryResponse
var queryResponse QueryResponse
err = json.Unmarshal(payload, &queryResponse)
if err != nil {
   // Handle error case
}

You can also create a function that takes AccountQueryCriteria as a parameter and exposes it to be used by different parts of your program to query Account objects using different query criteria.

func getAccounts(queryCriteria AccountQueryCriteria) ([]Account, error) {
   soqlStruct := AccountSoqlQuery{
        SelectClause: Account{},
        WhereClause: queryCriteria,
    }
    soqlQuery, err := soql.Marshal(soqlStruct)
    // Same code as above snippet to actually construct and perform http query
}

Since SOQL is generated by using this struct, the SOQL injection vulnerability is addressed. go-soql will take care of constructing a valid SOQL by properly escaping the user provided parameters.

As you can see from above, using the go-soql library for writing Golang clients for Salesforce is much easier since all you need to do is define the struct and annotate it.

Summary

Hopefully, you now have an idea of the power of the go-soql library. This blog post just scratches the surface of what the go-soql library can do. The library has extensive support for logical operators as well as child-to-parent and parent-to-child relationships. You can find all those details and more in the README of the repo.

Additional Resources
Git repo: https://github.com/forcedotcom/go-soql
Related Trailhead modules:

Author Bio:
Atul Kshirsagar is a Software Architect at Salesforce. He is passionate about technology, open source projects and loves to build software. He has presented at multiple conferences and contributed to many open source projects. In his current role as Software Architect at Salesforce he is focussed on leading the efforts to automate the entire patching lifecycle for the DB layer within the Salesforce Platform.
LinkedIn: https://www.linkedin.com/in/kshirsagar
Twitter: @atulckshirsagar