Skip to content

Commit 54ad27d

Browse files
authored
Merge pull request #24 from boatilus/update-execute-to
Update ExecuteTo functions to return count New tests added Comment typos fixed and added more Readme typos fixed
2 parents 0b61da4 + 37ba02b commit 54ad27d

File tree

9 files changed

+282
-23
lines changed

9 files changed

+282
-23
lines changed

README.md

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Postgrest GO
22

3-
[![golangci-lint](https://github.com/supabase/postgrest-go/actions/workflows/golangci.yml/badge.svg)](https://github.com/supabase/postgrest-go/actions/workflows/golangci.yml) [![CodeFactor](https://www.codefactor.io/repository/github/supabase/postgrest-go/badge/main?s=101cab44de33934fd85cadcd9a9b535a05791670)](https://www.codefactor.io/repository/github/supabase/postgrest-go/overview/main)
3+
[![golangci-lint](https://github.com/supabase/postgrest-go/actions/workflows/golangci.yml/badge.svg)](https://github.com/supabase/postgrest-go/actions/workflows/golangci.yml) [![CodeFactor](https://www.codefactor.io/repository/github/supabase-community/postgrest-go/badge/main?s=101cab44de33934fd85cadcd9a9b535a05791670)](https://www.codefactor.io/repository/github/supabase/postgrest-go/overview/main)
44

55
Golang client for [PostgREST](https://postgrest.org). The goal of this library is to make an "ORM-like" restful interface.
66

@@ -32,29 +32,40 @@ func main() {
3232
if client.ClientError != nil {
3333
panic(client.ClientError)
3434
}
35-
35+
3636
result := client.Rpc("add_them", "", map[string]int{"a": 12, "b": 3})
3737
if client.ClientError != nil {
3838
panic(client.ClientError)
3939
}
40-
40+
4141
fmt.Println(result)
4242
}
4343
```
4444

45-
- select(): https://supabase.io/docs/reference/javascript/select
46-
- insert(): https://supabase.io/docs/reference/javascript/insert
47-
- update(): https://supabase.io/docs/reference/javascript/update
48-
- delete(): https://supabase.io/docs/reference/javascript/delete
45+
- select(): https://supabase.com/docs/reference/javascript/select
46+
- insert(): https://supabase.com/docs/reference/javascript/insert
47+
- update(): https://supabase.com/docs/reference/javascript/update
48+
- upsert(): https://supabase.com/docs/reference/javascript/upsert
49+
- delete(): https://supabase.com/docs/reference/javascript/delete
50+
51+
## Testing
52+
53+
Some tests are implemented to run against mocked Postgrest endpoints. Optionally, tests can be run against an actual Postgrest instance by setting a `POSTGREST_URL` environment variable to the fully-qualified URL to a Postgrest instance, and, optionally, an `API_KEY` environment variable (if, for example, testing against a local Supabase instance).
54+
55+
A [script](test/seed.sql) is included in the test directory that can be used to seed the test database.
56+
57+
To run all tests:
58+
59+
```bash
60+
go test ./...
61+
```
4962

5063
## License
5164

52-
This repo is liscenced under Apache License.
65+
This repo is licensed under the [Apache License](LICENSE).
5366

5467
## Sponsors
5568

5669
We are building the features of Firebase using enterprise-grade, open source products. We support existing communities wherever possible, and if the products don’t exist we build them and open source them ourselves. Thanks to these sponsors who are making the OSS ecosystem better for everyone.
5770

5871
[![New Sponsor](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)](https://github.com/sponsors/supabase)
59-
60-
![Watch this repo](https://gitcdn.xyz/repo/supabase/monorepo/master/web/static/watch-repo.gif "Watch this repo")

client.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import (
1010
)
1111

1212
var (
13-
version = "v0.0.3"
13+
version = "v0.0.6"
1414
)
1515

16+
// NewClient constructs a new client given a URL to a Postgrest instance.
1617
func NewClient(rawURL, schema string, headers map[string]string) *Client {
1718
// Create URL from rawURL
1819
baseURL, err := url.Parse(rawURL)
@@ -41,7 +42,7 @@ func NewClient(rawURL, schema string, headers map[string]string) *Client {
4142
c.clientTransport.header.Set("Content-Profile", schema)
4243
c.clientTransport.header.Set("X-Client-Info", "postgrest-go/"+version)
4344

44-
// Set optional headers if exist
45+
// Set optional headers if they exist
4546
for key, value := range headers {
4647
c.clientTransport.header.Set(key, value)
4748
}
@@ -55,24 +56,29 @@ type Client struct {
5556
clientTransport transport
5657
}
5758

59+
// TokenAuth sets authorization headers for subsequent requests.
5860
func (c *Client) TokenAuth(token string) *Client {
5961
c.clientTransport.header.Set("Authorization", "Basic "+token)
6062
c.clientTransport.header.Set("apikey", token)
6163
return c
6264
}
6365

66+
// ChangeSchema modifies the schema for subsequent requests.
6467
func (c *Client) ChangeSchema(schema string) *Client {
6568
c.clientTransport.header.Set("Accept-Profile", schema)
6669
c.clientTransport.header.Set("Content-Profile", schema)
6770
return c
6871
}
6972

73+
// From sets the table to query from.
7074
func (c *Client) From(table string) *QueryBuilder {
7175
return &QueryBuilder{client: c, tableName: table, headers: map[string]string{}, params: map[string]string{}}
7276
}
7377

78+
// Rpc executes a Postgres function (a.k.a., Remote Prodedure Call), given the
79+
// function name and, optionally, a body, returning the result as a string.
7480
func (c *Client) Rpc(name string, count string, rpcBody interface{}) string {
75-
// Get body if exist
81+
// Get body if it exists
7682
var byteBody []byte = nil
7783
if rpcBody != nil {
7884
jsonBody, err := json.Marshal(rpcBody)

execute.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ import (
1111
"strings"
1212
)
1313

14+
// countType is the integer type returned from execute functions when a count
15+
// specifier is supplied to a builder.
1416
type countType = int64
1517

1618
// ExecuteError is the error response format from postgrest. We really
1719
// only use Code and Message, but we'll keep it as a struct for now.
18-
1920
type ExecuteError struct {
2021
Hint string `json:"hint"`
2122
Details string `json:"details"`
@@ -93,15 +94,15 @@ func execute(client *Client, method string, body []byte, urlFragments []string,
9394
return executeHelper(client, method, body, urlFragments, headers, params)
9495
}
9596

96-
func executeTo(client *Client, method string, body []byte, to interface{}, urlFragments []string, headers map[string]string, params map[string]string) error {
97-
resp, _, err := executeHelper(client, method, body, urlFragments, headers, params)
97+
func executeTo(client *Client, method string, body []byte, to interface{}, urlFragments []string, headers map[string]string, params map[string]string) (countType, error) {
98+
resp, count, err := executeHelper(client, method, body, urlFragments, headers, params)
9899

99100
if err != nil {
100-
return err
101+
return count, err
101102
}
102103

103104
readableRes := bytes.NewBuffer(resp)
104105

105106
err = json.NewDecoder(readableRes).Decode(&to)
106-
return err
107+
return count, err
107108
}

export_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ var mockResponses bool = false
1616

1717
var mockPath *regexp.Regexp
1818

19+
type TestResult struct {
20+
ID float64 `json:"id"`
21+
Name string `name:"sean"`
22+
Email string `email:"sean@test.com"`
23+
}
24+
1925
// A mock table/result set.
2026
var users = []map[string]interface{}{
2127
{

filterbuilder.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,31 @@ import (
77
"strings"
88
)
99

10+
// FilterBuilder describes a builder for a filtered result set.
1011
type FilterBuilder struct {
1112
client *Client
12-
method string
13+
method string // One of "HEAD", "GET", "POST", "PUT", "DELETE"
1314
body []byte
1415
tableName string
1516
headers map[string]string
1617
params map[string]string
1718
}
1819

20+
// ExecuteString runs the Postgrest query, returning the result as a JSON
21+
// string.
1922
func (f *FilterBuilder) ExecuteString() (string, countType, error) {
2023
return executeString(f.client, f.method, f.body, []string{f.tableName}, f.headers, f.params)
2124
}
2225

26+
// Execute runs the Postgrest query, returning the result as a byte slice.
2327
func (f *FilterBuilder) Execute() ([]byte, countType, error) {
2428
return execute(f.client, f.method, f.body, []string{f.tableName}, f.headers, f.params)
2529
}
2630

27-
func (f *FilterBuilder) ExecuteTo(to interface{}) error {
31+
// ExecuteTo runs the Postgrest query, encoding the result to the supplied
32+
// interface. Note that the argument for the to parameter should always be a
33+
// reference to a slice.
34+
func (f *FilterBuilder) ExecuteTo(to interface{}) (countType, error) {
2835
return executeTo(f.client, f.method, f.body, to, []string{f.tableName}, f.headers, f.params)
2936
}
3037

@@ -39,6 +46,8 @@ func isOperator(value string) bool {
3946
return false
4047
}
4148

49+
// Filter adds a filtering operator to the query. For a list of available
50+
// operators, see: https://postgrest.org/en/stable/api.html#operators
4251
func (f *FilterBuilder) Filter(column, operator, value string) *FilterBuilder {
4352
if !isOperator(operator) {
4453
f.client.ClientError = fmt.Errorf("invalid filter operator")
@@ -190,6 +199,8 @@ func (f *FilterBuilder) Overlaps(column string, value []string) *FilterBuilder {
190199
return f
191200
}
192201

202+
// TextSearch performs a full-text search filter. For more information, see
203+
// https://postgrest.org/en/stable/api.html#fts.
193204
func (f *FilterBuilder) TextSearch(column, userQuery, config, tsType string) *FilterBuilder {
194205
var typePart, configPart string
195206
if tsType == "plain" {

filterbuilder_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package postgrest
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
7+
"github.com/jarcoal/httpmock"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestFilterBuilder_ExecuteTo(t *testing.T) {
12+
assert := assert.New(t)
13+
c := createClient(t)
14+
15+
t.Run("ValidResult", func(t *testing.T) {
16+
want := []TestResult{
17+
{
18+
ID: float64(1),
19+
Name: "sean",
20+
Email: "sean@test.com",
21+
},
22+
}
23+
24+
var got []TestResult
25+
26+
if mockResponses {
27+
httpmock.Activate()
28+
defer httpmock.DeactivateAndReset()
29+
30+
responder, _ := httpmock.NewJsonResponder(200, []map[string]interface{}{
31+
users[0],
32+
})
33+
httpmock.RegisterRegexpResponder("GET", mockPath, responder)
34+
}
35+
36+
count, err := c.From("users").Select("id, name, email", "", false).Eq("name", "sean").ExecuteTo(&got)
37+
assert.NoError(err)
38+
assert.EqualValues(want, got)
39+
assert.Equal(countType(0), count)
40+
})
41+
42+
t.Run("WithCount", func(t *testing.T) {
43+
want := []TestResult{
44+
{
45+
ID: float64(1),
46+
Name: "sean",
47+
Email: "sean@test.com",
48+
},
49+
}
50+
51+
var got []TestResult
52+
53+
if mockResponses {
54+
httpmock.Activate()
55+
defer httpmock.DeactivateAndReset()
56+
57+
httpmock.RegisterRegexpResponder("GET", mockPath, func(req *http.Request) (*http.Response, error) {
58+
resp, _ := httpmock.NewJsonResponse(200, []map[string]interface{}{
59+
users[0],
60+
})
61+
62+
resp.Header.Add("Content-Range", "0-1/1")
63+
return resp, nil
64+
})
65+
}
66+
67+
count, err := c.From("users").Select("id, name, email", "exact", false).Eq("name", "sean").ExecuteTo(&got)
68+
assert.NoError(err)
69+
assert.EqualValues(want, got)
70+
assert.Equal(countType(1), count)
71+
})
72+
}
73+
74+
func ExampleFilterBuilder_ExecuteTo() {
75+
// Given a database with a "users" table containing "id", "name" and "email"
76+
// columns:
77+
var res []struct {
78+
ID int64 `json:"id"`
79+
Name string `json:"name"`
80+
Email string `json:"email"`
81+
}
82+
83+
client := NewClient("http://localhost:3000", "", nil)
84+
count, err := client.From("users").Select("*", "exact", false).ExecuteTo(&res)
85+
if err == nil && count > 0 {
86+
// The value for res will contain all columns for all users, and count will
87+
// be the exact number of rows in the users table.
88+
}
89+
}

querybuilder.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77
)
88

9+
// QueryBuilder describes a builder for a query.
910
type QueryBuilder struct {
1011
client *Client
1112
method string
@@ -15,18 +16,25 @@ type QueryBuilder struct {
1516
params map[string]string
1617
}
1718

19+
// ExecuteString runs the Postgrest query, returning the result as a JSON
20+
// string.
1821
func (q *QueryBuilder) ExecuteString() (string, countType, error) {
1922
return executeString(q.client, q.method, q.body, []string{q.tableName}, q.headers, q.params)
2023
}
2124

25+
// Execute runs the Postgrest query, returning the result as a byte slice.
2226
func (q *QueryBuilder) Execute() ([]byte, countType, error) {
2327
return execute(q.client, q.method, q.body, []string{q.tableName}, q.headers, q.params)
2428
}
2529

26-
func (q *QueryBuilder) ExecuteTo(to interface{}) error {
30+
// ExecuteTo runs the Postgrest query, encoding the result to the supplied
31+
// interface. Note that the argument for the to parameter should always be a
32+
// reference to a slice.
33+
func (q *QueryBuilder) ExecuteTo(to interface{}) (countType, error) {
2734
return executeTo(q.client, q.method, q.body, to, []string{q.tableName}, q.headers, q.params)
2835
}
2936

37+
// Select performs vertical filtering.
3038
func (q *QueryBuilder) Select(columns, count string, head bool) *FilterBuilder {
3139
if head {
3240
q.method = "HEAD"
@@ -63,6 +71,7 @@ func (q *QueryBuilder) Select(columns, count string, head bool) *FilterBuilder {
6371
return &FilterBuilder{client: q.client, method: q.method, body: q.body, tableName: q.tableName, headers: q.headers, params: q.params}
6472
}
6573

74+
// Insert performs an insertion into the table.
6675
func (q *QueryBuilder) Insert(value interface{}, upsert bool, onConflict, returning, count string) *FilterBuilder {
6776
q.method = "POST"
6877

@@ -98,6 +107,8 @@ func (q *QueryBuilder) Insert(value interface{}, upsert bool, onConflict, return
98107
q.body = byteBody
99108
return &FilterBuilder{client: q.client, method: q.method, body: q.body, tableName: q.tableName, headers: q.headers, params: q.params}
100109
}
110+
111+
// Upsert performs an upsert into the table.
101112
func (q *QueryBuilder) Upsert(value interface{}, onConflict, returning, count string) *FilterBuilder {
102113
q.method = "POST"
103114

@@ -131,6 +142,7 @@ func (q *QueryBuilder) Upsert(value interface{}, onConflict, returning, count st
131142
return &FilterBuilder{client: q.client, method: q.method, body: q.body, tableName: q.tableName, headers: q.headers, params: q.params}
132143
}
133144

145+
// Delete performs a deletion from the table.
134146
func (q *QueryBuilder) Delete(returning, count string) *FilterBuilder {
135147
q.method = "DELETE"
136148

@@ -148,6 +160,7 @@ func (q *QueryBuilder) Delete(returning, count string) *FilterBuilder {
148160
return &FilterBuilder{client: q.client, method: q.method, body: q.body, tableName: q.tableName, headers: q.headers, params: q.params}
149161
}
150162

163+
// Update performs an update on the table.
151164
func (q *QueryBuilder) Update(value interface{}, returning, count string) *FilterBuilder {
152165
q.method = "PATCH"
153166

@@ -163,7 +176,7 @@ func (q *QueryBuilder) Update(value interface{}, returning, count string) *Filte
163176
}
164177
q.headers["Prefer"] = strings.Join(headerList, ",")
165178

166-
// Get body if exist
179+
// Get body if it exists
167180
var byteBody []byte = nil
168181
if value != nil {
169182
jsonBody, err := json.Marshal(value)

0 commit comments

Comments
 (0)