Skip to content

Commit

Permalink
[feature] Query Explain
Browse files Browse the repository at this point in the history
  • Loading branch information
TuSKan committed Nov 19, 2024
1 parent cd22b52 commit e8deb7a
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 0 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,21 @@ if err != nil {
}
```

### QueryExplain

`func (sf *Salesforce) QueryExplain(query string) (*ExplainResponse, error)`

Performs a SOQL query explain given a query string and return the performance feedback analysis report

- `query`: a SOQL query

```go
explain, err := sf.QueryExplain("SELECT Id, LastName FROM Contact WHERE LastName = 'Lee'")
if err != nil {
panic(err)
}


### Handling Relationship Queries

When querying Salesforce objects, it's common to access fields that are related through parent-child or lookup relationships. For instance, querying `Account.Name` with related `Contact` might look like this:
Expand Down
47 changes: 47 additions & 0 deletions explain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package salesforce

import (
"encoding/json"
"net/http"
"net/url"
)

type note struct {
Description string `json:"description"`
Fields []string `json:"fields"`
TableEnumOrId string `json:"tableEnumOrId"`
}

type ExplainPlain struct {
Cardinality int64 `json:"cardinality"`
Fields []string `json:"fields"`
LeadingOperationType string `json:"leadingOperationType"`
Notes []note `json:"notes"`
RelativeCost float64 `json:"relativeCost"`
SObjectCardinality int64
SObjectType string `json:"sobjectType"`
}

func performExplain(auth *authentication, query string) ([]ExplainPlain, error) {
query = url.QueryEscape(query)

explainURL := "/query/?explain=" + query
resp, err := doRequest(auth, requestPayload{
method: http.MethodGet,
uri: explainURL,
content: jsonType,
})
if err != nil {
return nil, err
}

dec := json.NewDecoder(resp.Body)
var explainResp = struct {
Plans []ExplainPlain `json:"plans"`
}{}
if queryResponseError := dec.Decode(&explainResp); queryResponseError != nil {
return nil, queryResponseError
}

return explainResp.Plans, nil
}
14 changes: 14 additions & 0 deletions salesforce.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,20 @@ func (sf *Salesforce) QueryStruct(soqlStruct any, sObject any) error {
return nil
}

func (sf *Salesforce) QueryExplain(query string) ([]ExplainPlain, error) {
authErr := validateAuth(*sf)
if authErr != nil {
return nil, authErr
}

explainResp, queryErr := performExplain(sf.auth, query)
if queryErr != nil {
return nil, queryErr
}

return explainResp, nil
}

func (sf *Salesforce) InsertOne(sObjectName string, record any) (SalesforceResult, error) {
validationErr := validateSingles(*sf, record)
if validationErr != nil {
Expand Down
68 changes: 68 additions & 0 deletions salesforce_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,74 @@ func TestSalesforce_QueryStruct(t *testing.T) {
}
}

func TestSalesforce_QueryExplain(t *testing.T) {
resp := struct {
Plans []ExplainPlain `json:"plans"`
}{
Plans: []ExplainPlain{{
Cardinality: 1,
Fields: []string{"id", "name"},
LeadingOperationType: "Index",
Notes: []note{},
RelativeCost: 1.0,
SObjectCardinality: 1,
SObjectType: "account",
}}}
server, sfAuth := setupTestServer(resp, http.StatusOK)
defer server.Close()

type fields struct {
auth *authentication
}
type args struct {
query string
}
tests := []struct {
name string
fields fields
args args
want []ExplainPlain
wantErr bool
}{
{
name: "validation_fail",
fields: fields{
auth: nil,
},
args: args{
query: "SELECT Id FROM Account",
},
want: nil,
wantErr: true,
},
{
name: "successful_explain",
fields: fields{
auth: &sfAuth,
},
args: args{
query: "SELECT Id FROM Account",
},
want: resp.Plans,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sf := &Salesforce{
auth: tt.fields.auth,
}
res, err := sf.QueryExplain(tt.args.query)
if (err != nil) != tt.wantErr {
t.Errorf("Salesforce.Query() error = %v, wantErr %v", err, tt.wantErr)
}
if !reflect.DeepEqual(res, tt.want) {
t.Errorf("Salesforce.QueryExplain() = %v, want %v", &resp, tt.want)
}
})
}
}

func TestSalesforce_InsertOne(t *testing.T) {
type account struct {
Name string
Expand Down

0 comments on commit e8deb7a

Please sign in to comment.