Skip to content

Commit

Permalink
fix: gracefully handle empty hstore in pgdialect (#1010)
Browse files Browse the repository at this point in the history
* fix: handle empty hstore gracefuly in pgdialect

* chore: add test cases for hstore (empty hstore & query appender)
  • Loading branch information
0rax authored Aug 29, 2024
1 parent 1573ae7 commit 2f73d8a
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 2 deletions.
39 changes: 39 additions & 0 deletions dialect/pgdialect/append_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package pgdialect

import (
"fmt"
"reflect"
"testing"

"github.com/stretchr/testify/require"
"github.com/uptrace/bun/schema"
)

func TestHStoreAppender(t *testing.T) {
tests := []struct {
input map[string]string
expectedIn []string // maps being unsorted, multiple expected output are valid
}{
{nil, []string{`NULL`}},
{map[string]string{}, []string{`''`}},

{map[string]string{"": ""}, []string{`'""=>""'`}},
{map[string]string{`\`: `\`}, []string{`'"\\"=>"\\"'`}},
{map[string]string{"'": "'"}, []string{`'"''"=>"''"'`}},
{map[string]string{`'"{}`: `'"{}`}, []string{`'"''\"{}"=>"''\"{}"'`}},

{map[string]string{"1": "2", "3": "4"}, []string{`'"1"=>"2","3"=>"4"'`, `'"3"=>"4","1"=>"2"'`}},
{map[string]string{"1": ""}, []string{`'"1"=>""'`}},
{map[string]string{"1": "NULL"}, []string{`'"1"=>"NULL"'`}},
{map[string]string{"{1}": "{2}", "{3}": "{4}"}, []string{`'"{1}"=>"{2}","{3}"=>"{4}"'`, `'"{3}"=>"{4}","{1}"=>"{2}"'`}},
}

appendFunc := pgDialect.hstoreAppender(reflect.TypeOf(map[string]string{}))

for i, test := range tests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
got := appendFunc(schema.NewFormatter(pgDialect), []byte{}, reflect.ValueOf(test.input))
require.Contains(t, test.expectedIn, string(got))
})
}
}
4 changes: 2 additions & 2 deletions dialect/pgdialect/hstore_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type hstoreParser struct {

func newHStoreParser(b []byte) *hstoreParser {
p := new(hstoreParser)
if len(b) < 6 || b[0] != '"' {
if len(b) != 0 && (len(b) < 6 || b[0] != '"') {
p.err = fmt.Errorf("pgdialect: can't parse hstore: %q", b)
return p
}
Expand Down Expand Up @@ -83,7 +83,7 @@ func (p *hstoreParser) readNext() error {
default:
value := p.p.ReadLiteral(ch)
if bytes.Equal(value, []byte("NULL")) {
value = nil
p.value = ""
}
p.skipComma()
return nil
Expand Down
2 changes: 2 additions & 0 deletions dialect/pgdialect/hstore_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ func TestHStoreParser(t *testing.T) {
s string
m map[string]string
}{
{``, map[string]string{}},

{`""=>""`, map[string]string{"": ""}},
{`"\\"=>"\\"`, map[string]string{`\`: `\`}},
{`"'"=>"'"`, map[string]string{"'": "'"}},
Expand Down
16 changes: 16 additions & 0 deletions internal/dbtest/pg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,22 @@ func TestPostgresHStoreQuote(t *testing.T) {
require.Equal(t, wanted, m)
}

func TestPostgresHStoreEmpty(t *testing.T) {
db := pg(t)
t.Cleanup(func() { db.Close() })

_, err := db.Exec(`CREATE EXTENSION IF NOT EXISTS HSTORE;`)
require.NoError(t, err)

wanted := map[string]string{}
m := make(map[string]string)
err = db.NewSelect().
ColumnExpr("?::hstore", pgdialect.HStore(wanted)).
Scan(ctx, pgdialect.HStore(&m))
require.NoError(t, err)
require.Equal(t, wanted, m)
}

func TestPostgresSkipupdateField(t *testing.T) {
type Model struct {
ID int64 `bun:",pk,autoincrement"`
Expand Down

0 comments on commit 2f73d8a

Please sign in to comment.