Skip to content

Commit d646eb2

Browse files
authored
Implement filtered/partial index support (#2)
* Implement filtered/partial index support * Add edge test cases * Update to use latest specs for tests * Do not use lib/pq driver functions for quoting * Add test cases for postgres quoting * go mod tidy
1 parent 76576a6 commit d646eb2

File tree

6 files changed

+162
-19
lines changed

6 files changed

+162
-19
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,17 @@ func main() {
4242
## Supported Database
4343

4444
- PostgreSQL 9, 10, 11, 12 and 13
45+
46+
## Testing
47+
48+
### Start PostgreSQL server in Docker
49+
50+
```console
51+
docker run -it --rm -p 5433:5432 -e "POSTGRES_USER=rel" -e "POSTGRES_PASSWORD=test" -e "POSTGRES_DB=rel_test" postgres:14-alpine
52+
```
53+
54+
### Run tests
55+
56+
```console
57+
POSTGRESQL_DATABASE="postgres://rel:test@localhost:5433/rel_test?timezone=Asia/Jakarta" go test ./...
58+
```

go.mod

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,16 @@ module github.com/go-rel/postgres
33
go 1.17
44

55
require (
6-
github.com/go-rel/rel v0.24.0
7-
github.com/go-rel/sql v0.1.0
6+
github.com/go-rel/rel v0.25.1-0.20211011102656-a1b38f01d34a
7+
github.com/go-rel/sql v0.1.1-0.20211011073646-a38034248e90
88
github.com/lib/pq v1.10.3
99
github.com/stretchr/testify v1.7.0
1010
)
11+
12+
require (
13+
github.com/davecgh/go-spew v1.1.1 // indirect
14+
github.com/jinzhu/inflection v1.0.0 // indirect
15+
github.com/pmezard/go-difflib v1.0.0 // indirect
16+
github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e // indirect
17+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
18+
)

go.sum

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,21 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
44
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
55
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
66
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
7-
github.com/go-rel/rel v0.23.0 h1:xVte6QJhCLGQoTeHeUbIWTVDwllFCVzmJ8511XEnbtg=
8-
github.com/go-rel/rel v0.23.0/go.mod h1:/VBLj1U4ZVb53aB3n22RQwX0V9DvckohseDJQibn2Rs=
9-
github.com/go-rel/rel v0.24.0 h1:DXx8DjUwkkg9EcLfS/m5YGGjdV4fjs7dsrcDVS1Zk0w=
10-
github.com/go-rel/rel v0.24.0/go.mod h1:/VBLj1U4ZVb53aB3n22RQwX0V9DvckohseDJQibn2Rs=
11-
github.com/go-rel/sql v0.0.0-20210924144648-3d87fc16d649 h1:Dn/pawk07lSl06D7oOrQLuP9ZN9ADWWLsVsgARrQd6I=
12-
github.com/go-rel/sql v0.0.0-20210924144648-3d87fc16d649/go.mod h1:vnvQ9jFzTgFvOl5qcntoFXrs9sas39YcIJG30LFlc50=
13-
github.com/go-rel/sql v0.1.0 h1:fdkdENIgJzoqUfdbQYtPCl85jcu7Ajj8zh/A5hWS+SI=
14-
github.com/go-rel/sql v0.1.0/go.mod h1:vnvQ9jFzTgFvOl5qcntoFXrs9sas39YcIJG30LFlc50=
15-
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
7+
github.com/go-rel/rel v0.25.1-0.20211007095335-eec7ac68c920/go.mod h1:/VBLj1U4ZVb53aB3n22RQwX0V9DvckohseDJQibn2Rs=
8+
github.com/go-rel/rel v0.25.1-0.20211011102656-a1b38f01d34a h1:FAd8FrXgy+G/44OwXYOk3A0b4lhdoyKMgF9m3h6pTt4=
9+
github.com/go-rel/rel v0.25.1-0.20211011102656-a1b38f01d34a/go.mod h1:/VBLj1U4ZVb53aB3n22RQwX0V9DvckohseDJQibn2Rs=
10+
github.com/go-rel/sql v0.1.1-0.20211011073646-a38034248e90 h1:638QwWIymw8cizASdq594qlDM729vYEtJobdy8wv2Lo=
11+
github.com/go-rel/sql v0.1.1-0.20211011073646-a38034248e90/go.mod h1:ukXvTr/zbEZLu+lJCXcKpMg1Vp7Ps9cfKD+Us4kJREo=
1612
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
1713
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
1814
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
1915
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
2016
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
2117
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
2218
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
23-
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
2419
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
2520
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
2621
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
27-
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
2822
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
2923
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
3024
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@@ -57,7 +51,6 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
5751
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
5852
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
5953
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
60-
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
6154
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
6255
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
6356
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -89,14 +82,12 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
8982
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
9083
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
9184
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
92-
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
9385
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
9486
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
9587
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
9688
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
9789
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
9890
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
99-
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
10091
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
10192
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
10293
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

postgres.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,17 @@ type Postgres struct {
3030
// New postgres adapter using existing connection.
3131
func New(database *db.DB) rel.Adapter {
3232
var (
33-
bufferFactory = builder.BufferFactory{ArgumentPlaceholder: "$", ArgumentOrdinal: true, EscapePrefix: "\"", EscapeSuffix: "\""}
33+
bufferFactory = builder.BufferFactory{ArgumentPlaceholder: "$", ArgumentOrdinal: true, BoolTrueValue: "true", BoolFalseValue: "true", Quoter: Quote{}, ValueConverter: ValueConvert{}}
3434
filterBuilder = builder.Filter{}
3535
queryBuilder = builder.Query{BufferFactory: bufferFactory, Filter: filterBuilder}
3636
InsertBuilder = builder.Insert{BufferFactory: bufferFactory, ReturningPrimaryValue: true, InsertDefaultValues: true}
3737
insertAllBuilder = builder.InsertAll{BufferFactory: bufferFactory, ReturningPrimaryValue: true}
3838
updateBuilder = builder.Update{BufferFactory: bufferFactory, Query: queryBuilder, Filter: filterBuilder}
3939
deleteBuilder = builder.Delete{BufferFactory: bufferFactory, Query: queryBuilder, Filter: filterBuilder}
40-
tableBuilder = builder.Table{BufferFactory: bufferFactory, ColumnMapper: columnMapper}
41-
indexBuilder = builder.Index{BufferFactory: bufferFactory}
40+
ddlBufferFactory = builder.BufferFactory{InlineValues: true, BoolTrueValue: "true", BoolFalseValue: "true", Quoter: Quote{}, ValueConverter: ValueConvert{}}
41+
ddlQueryBuilder = builder.Query{BufferFactory: ddlBufferFactory, Filter: filterBuilder}
42+
tableBuilder = builder.Table{BufferFactory: ddlBufferFactory, ColumnMapper: columnMapper}
43+
indexBuilder = builder.Index{BufferFactory: ddlBufferFactory, Query: ddlQueryBuilder, Filter: filterBuilder, SupportFilter: true}
4244
)
4345

4446
return &Postgres{

quote.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package postgres
2+
3+
import (
4+
"database/sql/driver"
5+
"strings"
6+
"time"
7+
)
8+
9+
// Quote PostgreSQL identifiers and literals.
10+
type Quote struct{}
11+
12+
func (q Quote) ID(name string) string {
13+
end := strings.IndexRune(name, 0)
14+
if end > -1 {
15+
name = name[:end]
16+
}
17+
return `"` + strings.Replace(name, `"`, `""`, -1) + `"`
18+
}
19+
20+
func (q Quote) Value(v interface{}) string {
21+
switch v := v.(type) {
22+
default:
23+
panic("unsupported value")
24+
case string:
25+
v = strings.Replace(v, `'`, `''`, -1)
26+
if strings.Contains(v, `\`) {
27+
v = strings.Replace(v, `\`, `\\`, -1)
28+
v = ` E'` + v + `'`
29+
} else {
30+
v = `'` + v + `'`
31+
}
32+
return v
33+
}
34+
}
35+
36+
// ValueConvert converts values to PostgreSQL literals.
37+
type ValueConvert struct{}
38+
39+
func (c ValueConvert) ConvertValue(v interface{}) (driver.Value, error) {
40+
v, err := driver.DefaultParameterConverter.ConvertValue(v)
41+
if err != nil {
42+
return nil, err
43+
}
44+
switch v := v.(type) {
45+
default:
46+
return v, nil
47+
case time.Time:
48+
return v.Truncate(time.Microsecond).Format("2006-01-02 15:04:05.999999999Z07:00:00"), nil
49+
}
50+
}

quote_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package postgres
2+
3+
import (
4+
"database/sql/driver"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestQuote_Panic(t *testing.T) {
11+
quoter := Quote{}
12+
assert.PanicsWithValue(t, "unsupported value", func() {
13+
quoter.Value(1)
14+
})
15+
}
16+
17+
func TestQuote_ID(t *testing.T) {
18+
quoter := Quote{}
19+
20+
var cases = []struct {
21+
input string
22+
want string
23+
}{
24+
{`foo`, `"foo"`},
25+
{`foo bar baz`, `"foo bar baz"`},
26+
{`foo"bar`, `"foo""bar"`},
27+
{"foo\x00bar", `"foo"`},
28+
{"\x00foo", `""`},
29+
}
30+
31+
for _, test := range cases {
32+
assert.Equal(t, test.want, quoter.ID(test.input))
33+
}
34+
}
35+
36+
func TestQuote_Value(t *testing.T) {
37+
quoter := Quote{}
38+
39+
var cases = []struct {
40+
input string
41+
want string
42+
}{
43+
{`foo`, `'foo'`},
44+
{`foo bar baz`, `'foo bar baz'`},
45+
{`foo'bar`, `'foo''bar'`},
46+
{`foo\bar`, ` E'foo\\bar'`},
47+
{`foo\ba'r`, ` E'foo\\ba''r'`},
48+
{`foo"bar`, `'foo"bar'`},
49+
{`foo\x00bar`, ` E'foo\\x00bar'`},
50+
{`\x00foo`, ` E'\\x00foo'`},
51+
{`'`, `''''`},
52+
{`''`, `''''''`},
53+
{`\`, ` E'\\'`},
54+
{`'abc'; DROP TABLE users;`, `'''abc''; DROP TABLE users;'`},
55+
{`\'`, ` E'\\'''`},
56+
{`E'\''`, ` E'E''\\'''''`},
57+
{`e'\''`, ` E'e''\\'''''`},
58+
{`E'\'abc\'; DROP TABLE users;'`, ` E'E''\\''abc\\''; DROP TABLE users;'''`},
59+
{`e'\'abc\'; DROP TABLE users;'`, ` E'e''\\''abc\\''; DROP TABLE users;'''`},
60+
}
61+
62+
for _, test := range cases {
63+
assert.Equal(t, test.want, quoter.Value(test.input))
64+
}
65+
}
66+
67+
type customType int
68+
69+
func (c customType) Value() (driver.Value, error) {
70+
return int(c), nil
71+
}
72+
73+
func TestValueConvert_CustomType(t *testing.T) {
74+
valuer := ValueConvert{}
75+
v, err := valuer.ConvertValue(customType(1))
76+
assert.EqualError(t, err, "non-Value type int returned from Value")
77+
assert.Nil(t, v)
78+
}

0 commit comments

Comments
 (0)