mirror of https://github.com/jackc/pgx.git
437 lines
14 KiB
Go
437 lines
14 KiB
Go
package pgtype_test
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/jackc/pgtype"
|
|
"github.com/jackc/pgtype/testutil"
|
|
"github.com/jackc/pgx/v4"
|
|
)
|
|
|
|
func TestHstoreArrayTranscode(t *testing.T) {
|
|
conn := testutil.MustConnectPgx(t)
|
|
defer testutil.MustCloseContext(t, conn)
|
|
|
|
var hstoreOID uint32
|
|
err := conn.QueryRow(context.Background(), "select t.oid from pg_type t where t.typname='hstore';").Scan(&hstoreOID)
|
|
if err != nil {
|
|
t.Fatalf("did not find hstore OID, %v", err)
|
|
}
|
|
conn.ConnInfo().RegisterDataType(pgtype.DataType{Value: &pgtype.Hstore{}, Name: "hstore", OID: hstoreOID})
|
|
|
|
var hstoreArrayOID uint32
|
|
err = conn.QueryRow(context.Background(), "select t.oid from pg_type t where t.typname='_hstore';").Scan(&hstoreArrayOID)
|
|
if err != nil {
|
|
t.Fatalf("did not find _hstore OID, %v", err)
|
|
}
|
|
conn.ConnInfo().RegisterDataType(pgtype.DataType{Value: &pgtype.HstoreArray{}, Name: "_hstore", OID: hstoreArrayOID})
|
|
|
|
text := func(s string) pgtype.Text {
|
|
return pgtype.Text{String: s, Status: pgtype.Present}
|
|
}
|
|
|
|
values := []pgtype.Hstore{
|
|
{Map: map[string]pgtype.Text{}, Status: pgtype.Present},
|
|
{Map: map[string]pgtype.Text{"foo": text("bar")}, Status: pgtype.Present},
|
|
{Map: map[string]pgtype.Text{"foo": text("bar"), "baz": text("quz")}, Status: pgtype.Present},
|
|
{Map: map[string]pgtype.Text{"NULL": text("bar")}, Status: pgtype.Present},
|
|
{Map: map[string]pgtype.Text{"foo": text("NULL")}, Status: pgtype.Present},
|
|
{Status: pgtype.Null},
|
|
}
|
|
|
|
specialStrings := []string{
|
|
`"`,
|
|
`'`,
|
|
`\`,
|
|
`\\`,
|
|
`=>`,
|
|
` `,
|
|
`\ / / \\ => " ' " '`,
|
|
}
|
|
for _, s := range specialStrings {
|
|
// Special key values
|
|
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{s + "foo": text("bar")}, Status: pgtype.Present}) // at beginning
|
|
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo" + s + "bar": text("bar")}, Status: pgtype.Present}) // in middle
|
|
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo" + s: text("bar")}, Status: pgtype.Present}) // at end
|
|
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{s: text("bar")}, Status: pgtype.Present}) // is key
|
|
|
|
// Special value values
|
|
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text(s + "bar")}, Status: pgtype.Present}) // at beginning
|
|
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text("foo" + s + "bar")}, Status: pgtype.Present}) // in middle
|
|
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text("foo" + s)}, Status: pgtype.Present}) // at end
|
|
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text(s)}, Status: pgtype.Present}) // is key
|
|
}
|
|
|
|
src := &pgtype.HstoreArray{
|
|
Elements: values,
|
|
Dimensions: []pgtype.ArrayDimension{{Length: int32(len(values)), LowerBound: 1}},
|
|
Status: pgtype.Present,
|
|
}
|
|
|
|
_, err = conn.Prepare(context.Background(), "test", "select $1::hstore[]")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
formats := []struct {
|
|
name string
|
|
formatCode int16
|
|
}{
|
|
{name: "TextFormat", formatCode: pgx.TextFormatCode},
|
|
{name: "BinaryFormat", formatCode: pgx.BinaryFormatCode},
|
|
}
|
|
|
|
for _, fc := range formats {
|
|
queryResultFormats := pgx.QueryResultFormats{fc.formatCode}
|
|
vEncoder := testutil.ForceEncoder(src, fc.formatCode)
|
|
if vEncoder == nil {
|
|
t.Logf("%#v does not implement %v", src, fc.name)
|
|
continue
|
|
}
|
|
|
|
var result pgtype.HstoreArray
|
|
err := conn.QueryRow(context.Background(), "test", queryResultFormats, vEncoder).Scan(&result)
|
|
if err != nil {
|
|
t.Errorf("%v: %v", fc.name, err)
|
|
continue
|
|
}
|
|
|
|
if result.Status != src.Status {
|
|
t.Errorf("%v: expected Status %v, got %v", fc.formatCode, src.Status, result.Status)
|
|
continue
|
|
}
|
|
|
|
if len(result.Elements) != len(src.Elements) {
|
|
t.Errorf("%v: expected %v elements, got %v", fc.formatCode, len(src.Elements), len(result.Elements))
|
|
continue
|
|
}
|
|
|
|
for i := range result.Elements {
|
|
a := src.Elements[i]
|
|
b := result.Elements[i]
|
|
|
|
if a.Status != b.Status {
|
|
t.Errorf("%v element idx %d: expected status %v, got %v", fc.formatCode, i, a.Status, b.Status)
|
|
}
|
|
|
|
if len(a.Map) != len(b.Map) {
|
|
t.Errorf("%v element idx %d: expected %v pairs, got %v", fc.formatCode, i, len(a.Map), len(b.Map))
|
|
}
|
|
|
|
for k := range a.Map {
|
|
if a.Map[k] != b.Map[k] {
|
|
t.Errorf("%v element idx %d: expected key %v to be %v, got %v", fc.formatCode, i, k, a.Map[k], b.Map[k])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHstoreArraySet(t *testing.T) {
|
|
successfulTests := []struct {
|
|
src interface{}
|
|
result pgtype.HstoreArray
|
|
}{
|
|
{
|
|
src: []map[string]string{{"foo": "bar"}},
|
|
result: pgtype.HstoreArray{
|
|
Elements: []pgtype.Hstore{
|
|
{
|
|
Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
{
|
|
src: [][]map[string]string{{{"foo": "bar"}}, {{"baz": "quz"}}},
|
|
result: pgtype.HstoreArray{
|
|
Elements: []pgtype.Hstore{
|
|
{
|
|
Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
{
|
|
src: [][][][]map[string]string{
|
|
{{{{"foo": "bar"}, {"baz": "quz"}, {"bar": "baz"}}}},
|
|
{{{{"wibble": "wobble"}, {"wubble": "wabble"}, {"wabble": "wobble"}}}}},
|
|
result: pgtype.HstoreArray{
|
|
Elements: []pgtype.Hstore{
|
|
{
|
|
Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"bar": {String: "baz", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"wibble": {String: "wobble", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"wubble": {String: "wabble", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"wabble": {String: "wobble", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
Dimensions: []pgtype.ArrayDimension{
|
|
{LowerBound: 1, Length: 2},
|
|
{LowerBound: 1, Length: 1},
|
|
{LowerBound: 1, Length: 1},
|
|
{LowerBound: 1, Length: 3}},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
{
|
|
src: [2][1]map[string]string{{{"foo": "bar"}}, {{"baz": "quz"}}},
|
|
result: pgtype.HstoreArray{
|
|
Elements: []pgtype.Hstore{
|
|
{
|
|
Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
{
|
|
src: [2][1][1][3]map[string]string{
|
|
{{{{"foo": "bar"}, {"baz": "quz"}, {"bar": "baz"}}}},
|
|
{{{{"wibble": "wobble"}, {"wubble": "wabble"}, {"wabble": "wobble"}}}}},
|
|
result: pgtype.HstoreArray{
|
|
Elements: []pgtype.Hstore{
|
|
{
|
|
Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"bar": {String: "baz", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"wibble": {String: "wobble", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"wubble": {String: "wabble", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"wabble": {String: "wobble", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
Dimensions: []pgtype.ArrayDimension{
|
|
{LowerBound: 1, Length: 2},
|
|
{LowerBound: 1, Length: 1},
|
|
{LowerBound: 1, Length: 1},
|
|
{LowerBound: 1, Length: 3}},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, tt := range successfulTests {
|
|
var dst pgtype.HstoreArray
|
|
err := dst.Set(tt.src)
|
|
if err != nil {
|
|
t.Errorf("%d: %v", i, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(dst, tt.result) {
|
|
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.src, tt.result, dst)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHstoreArrayAssignTo(t *testing.T) {
|
|
var hstoreSlice []map[string]string
|
|
var hstoreSliceDim2 [][]map[string]string
|
|
var hstoreSliceDim4 [][][][]map[string]string
|
|
var hstoreArrayDim2 [2][1]map[string]string
|
|
var hstoreArrayDim4 [2][1][1][3]map[string]string
|
|
|
|
simpleTests := []struct {
|
|
src pgtype.HstoreArray
|
|
dst interface{}
|
|
expected interface{}
|
|
}{
|
|
{
|
|
src: pgtype.HstoreArray{
|
|
Elements: []pgtype.Hstore{
|
|
{
|
|
Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
|
Status: pgtype.Present,
|
|
},
|
|
dst: &hstoreSlice,
|
|
expected: []map[string]string{{"foo": "bar"}}},
|
|
{
|
|
src: pgtype.HstoreArray{Status: pgtype.Null}, dst: &hstoreSlice, expected: (([]map[string]string)(nil)),
|
|
},
|
|
{
|
|
src: pgtype.HstoreArray{Status: pgtype.Present}, dst: &hstoreSlice, expected: []map[string]string{},
|
|
},
|
|
{
|
|
src: pgtype.HstoreArray{
|
|
Elements: []pgtype.Hstore{
|
|
{
|
|
Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
|
Status: pgtype.Present,
|
|
},
|
|
dst: &hstoreSliceDim2,
|
|
expected: [][]map[string]string{{{"foo": "bar"}}, {{"baz": "quz"}}},
|
|
},
|
|
{
|
|
src: pgtype.HstoreArray{
|
|
Elements: []pgtype.Hstore{
|
|
{
|
|
Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"bar": {String: "baz", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"wibble": {String: "wobble", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"wubble": {String: "wabble", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"wabble": {String: "wobble", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
Dimensions: []pgtype.ArrayDimension{
|
|
{LowerBound: 1, Length: 2},
|
|
{LowerBound: 1, Length: 1},
|
|
{LowerBound: 1, Length: 1},
|
|
{LowerBound: 1, Length: 3}},
|
|
Status: pgtype.Present,
|
|
},
|
|
dst: &hstoreSliceDim4,
|
|
expected: [][][][]map[string]string{
|
|
{{{{"foo": "bar"}, {"baz": "quz"}, {"bar": "baz"}}}},
|
|
{{{{"wibble": "wobble"}, {"wubble": "wabble"}, {"wabble": "wobble"}}}}},
|
|
},
|
|
{
|
|
src: pgtype.HstoreArray{
|
|
Elements: []pgtype.Hstore{
|
|
{
|
|
Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
|
Status: pgtype.Present,
|
|
},
|
|
dst: &hstoreArrayDim2,
|
|
expected: [2][1]map[string]string{{{"foo": "bar"}}, {{"baz": "quz"}}},
|
|
},
|
|
{
|
|
src: pgtype.HstoreArray{
|
|
Elements: []pgtype.Hstore{
|
|
{
|
|
Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"bar": {String: "baz", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"wibble": {String: "wobble", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"wubble": {String: "wabble", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
{
|
|
Map: map[string]pgtype.Text{"wabble": {String: "wobble", Status: pgtype.Present}},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
Dimensions: []pgtype.ArrayDimension{
|
|
{LowerBound: 1, Length: 2},
|
|
{LowerBound: 1, Length: 1},
|
|
{LowerBound: 1, Length: 1},
|
|
{LowerBound: 1, Length: 3}},
|
|
Status: pgtype.Present,
|
|
},
|
|
dst: &hstoreArrayDim4,
|
|
expected: [2][1][1][3]map[string]string{
|
|
{{{{"foo": "bar"}, {"baz": "quz"}, {"bar": "baz"}}}},
|
|
{{{{"wibble": "wobble"}, {"wubble": "wabble"}, {"wabble": "wobble"}}}}},
|
|
},
|
|
}
|
|
|
|
for i, tt := range simpleTests {
|
|
err := tt.src.AssignTo(tt.dst)
|
|
if err != nil {
|
|
t.Errorf("%d: %v", i, err)
|
|
}
|
|
|
|
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
|
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
|
}
|
|
}
|
|
}
|