diff --git a/README.md b/README.md index 2f8f74e6..5add54d2 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,10 @@ To setup the normal test environment run the following SQL: create user pgx_md5 password 'secret'; create database pgx_test; +Connect to database pgx_test and run: + + create extension hstore; + Next open connection_settings_test.go.example and make a copy without the .example. If your PostgreSQL server is accepting connections on 127.0.0.1, then you are done. diff --git a/hstore.go b/hstore.go index a7b362df..0eead6dc 100644 --- a/hstore.go +++ b/hstore.go @@ -81,6 +81,9 @@ func parseHstoreToNullHstore(s string) (store map[string]NullString, err error) } func ParseHstore(s string) (k []string, v []NullString, err error) { + if s == "" { + return + } buf := bytes.Buffer{} keys := []string{} diff --git a/hstore_test.go b/hstore_test.go new file mode 100644 index 00000000..1308cf43 --- /dev/null +++ b/hstore_test.go @@ -0,0 +1,71 @@ +package pgx_test + +import ( + "github.com/jackc/pgx" + "testing" +) + +func TestHstoreTranscode(t *testing.T) { + t.Parallel() + + conn := mustConnect(t, *defaultConnConfig) + defer closeConn(t, conn) + + type test struct { + hstore pgx.Hstore + description string + } + + tests := []test{ + {pgx.Hstore{}, "empty"}, + {pgx.Hstore{"foo": "bar"}, "single key/value"}, + {pgx.Hstore{"foo": "bar", "baz": "quz"}, "multiple key/values"}, + {pgx.Hstore{"NULL": "bar"}, `string "NULL" key`}, + {pgx.Hstore{"foo": "NULL"}, `string "NULL" value`}, + } + + specialStringTests := []struct { + input string + description string + }{ + {`"`, `double quote (")`}, + {`'`, `single quote (')`}, + {`\`, `backslash (\)`}, + {`\\`, `multiple backslashes (\\)`}, + {`=>`, `separator (=>)`}, + {` `, `space`}, + {`\ / / \\ => " ' " '`, `multiple special characters`}, + } + for _, sst := range specialStringTests { + tests = append(tests, test{pgx.Hstore{sst.input + "foo": "bar"}, "key with " + sst.description + " at beginning"}) + tests = append(tests, test{pgx.Hstore{"foo" + sst.input + "foo": "bar"}, "key with " + sst.description + " in middle"}) + tests = append(tests, test{pgx.Hstore{"foo" + sst.input: "bar"}, "key with " + sst.description + " at end"}) + tests = append(tests, test{pgx.Hstore{sst.input: "bar"}, "key is " + sst.description}) + + tests = append(tests, test{pgx.Hstore{"foo": sst.input + "bar"}, "value with " + sst.description + " at beginning"}) + tests = append(tests, test{pgx.Hstore{"foo": "bar" + sst.input + "bar"}, "value with " + sst.description + " in middle"}) + tests = append(tests, test{pgx.Hstore{"foo": "bar" + sst.input}, "value with " + sst.description + " at end"}) + tests = append(tests, test{pgx.Hstore{"foo": sst.input}, "value is " + sst.description}) + } + + for _, tt := range tests { + var result pgx.Hstore + err := conn.QueryRow("select $1::hstore", tt.hstore).Scan(&result) + if err != nil { + t.Errorf(`%s: QueryRow.Scan returned an error: %v`, tt.description, err) + } + + for key, inValue := range tt.hstore { + outValue, ok := result[key] + if ok { + if inValue != outValue { + t.Errorf(`%s: Key %s mismatch - expected %s, received %s`, tt.description, key, inValue, outValue) + } + } else { + t.Errorf(`%s: Missing key %s`, tt.description, key) + } + } + + ensureConnValid(t, conn) + } +}