├── .github └── workflows │ └── ci.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── aclitem.go ├── aclitem_array.go ├── aclitem_array_test.go ├── aclitem_test.go ├── array.go ├── array_test.go ├── array_type.go ├── array_type_test.go ├── bit.go ├── bit_test.go ├── bool.go ├── bool_array.go ├── bool_array_test.go ├── bool_test.go ├── box.go ├── box_test.go ├── bpchar.go ├── bpchar_array.go ├── bpchar_array_test.go ├── bpchar_test.go ├── bytea.go ├── bytea_array.go ├── bytea_array_test.go ├── bytea_test.go ├── cid.go ├── cid_test.go ├── cidr.go ├── cidr_array.go ├── cidr_array_test.go ├── circle.go ├── circle_test.go ├── composite_bench_test.go ├── composite_fields.go ├── composite_fields_test.go ├── composite_type.go ├── composite_type_test.go ├── convert.go ├── custom_composite_test.go ├── database_sql.go ├── date.go ├── date_array.go ├── date_array_test.go ├── date_test.go ├── daterange.go ├── daterange_test.go ├── enum_array.go ├── enum_array_test.go ├── enum_type.go ├── enum_type_test.go ├── ext ├── gofrs-uuid │ ├── uuid.go │ └── uuid_test.go └── shopspring-numeric │ ├── decimal.go │ └── decimal_test.go ├── float4.go ├── float4_array.go ├── float4_array_test.go ├── float4_test.go ├── float8.go ├── float8_array.go ├── float8_array_test.go ├── float8_test.go ├── generic_binary.go ├── generic_text.go ├── go.mod ├── go.sum ├── hstore.go ├── hstore_array.go ├── hstore_array_test.go ├── hstore_test.go ├── inet.go ├── inet_array.go ├── inet_array_test.go ├── inet_test.go ├── int2.go ├── int2_array.go ├── int2_array_test.go ├── int2_test.go ├── int4.go ├── int4_array.go ├── int4_array_test.go ├── int4_multirange.go ├── int4_multirange_test.go ├── int4_test.go ├── int4range.go ├── int4range_test.go ├── int8.go ├── int8_array.go ├── int8_array_test.go ├── int8_multirange.go ├── int8_multirange_test.go ├── int8_test.go ├── int8range.go ├── int8range_test.go ├── interval.go ├── interval_test.go ├── json.go ├── json_array.go ├── json_array_test.go ├── json_test.go ├── jsonb.go ├── jsonb_array.go ├── jsonb_array_test.go ├── jsonb_test.go ├── line.go ├── line_test.go ├── lseg.go ├── lseg_test.go ├── ltree.go ├── ltree_test.go ├── macaddr.go ├── macaddr_array.go ├── macaddr_array_test.go ├── macaddr_test.go ├── multirange.go ├── multirange_test.go ├── name.go ├── name_test.go ├── num_multirange.go ├── num_multirange_test.go ├── numeric.go ├── numeric_array.go ├── numeric_array_test.go ├── numeric_test.go ├── numrange.go ├── numrange_test.go ├── oid.go ├── oid_value.go ├── oid_value_test.go ├── path.go ├── path_test.go ├── pgtype.go ├── pgtype_test.go ├── pguint32.go ├── pgxtype ├── README.md └── pgxtype.go ├── point.go ├── point_test.go ├── polygon.go ├── polygon_test.go ├── qchar.go ├── qchar_test.go ├── range.go ├── range_test.go ├── record.go ├── record_array.go ├── record_array_test.go ├── record_test.go ├── testutil └── testutil.go ├── text.go ├── text_array.go ├── text_array_test.go ├── text_test.go ├── tid.go ├── tid_test.go ├── time.go ├── time_test.go ├── timestamp.go ├── timestamp_array.go ├── timestamp_array_test.go ├── timestamp_test.go ├── timestamptz.go ├── timestamptz_array.go ├── timestamptz_array_test.go ├── timestamptz_test.go ├── tsrange.go ├── tsrange_array.go ├── tsrange_test.go ├── tstzrange.go ├── tstzrange_array.go ├── tstzrange_test.go ├── typed_array.go.erb ├── typed_array_gen.sh ├── typed_multirange.go.erb ├── typed_multirange_gen.sh ├── typed_range.go.erb ├── typed_range_gen.sh ├── unknown.go ├── uuid.go ├── uuid_array.go ├── uuid_array_test.go ├── uuid_test.go ├── varbit.go ├── varbit_test.go ├── varchar.go ├── varchar_array.go ├── varchar_array_test.go ├── xid.go ├── xid_test.go └── zeronull ├── doc.go ├── float8.go ├── float8_test.go ├── int2.go ├── int2_test.go ├── int4.go ├── int4_test.go ├── int8.go ├── int8_test.go ├── text.go ├── text_test.go ├── timestamp.go ├── timestamp_test.go ├── timestamptz.go ├── timestamptz_test.go ├── uuid.go └── uuid_test.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | test: 12 | name: Test 13 | runs-on: ubuntu-latest 14 | 15 | services: 16 | postgres: 17 | image: postgres 18 | env: 19 | POSTGRES_PASSWORD: secret 20 | options: >- 21 | --health-cmd pg_isready 22 | --health-interval 10s 23 | --health-timeout 5s 24 | --health-retries 5 25 | ports: 26 | - 5432:5432 27 | 28 | steps: 29 | 30 | - name: Set up Go 1.x 31 | uses: actions/setup-go@v2 32 | with: 33 | go-version: ^1.13 34 | 35 | - name: Check out code into the Go module directory 36 | uses: actions/checkout@v2 37 | 38 | - name: Create hstore extension 39 | run: psql -c 'create extension hstore' 40 | env: 41 | PGHOST: localhost 42 | PGUSER: postgres 43 | PGPASSWORD: secret 44 | PGSSLMODE: disable 45 | 46 | - name: Create ltree extension 47 | run: psql -c 'create extension ltree' 48 | env: 49 | PGHOST: localhost 50 | PGUSER: postgres 51 | PGPASSWORD: secret 52 | PGSSLMODE: disable 53 | 54 | - name: Test 55 | run: go test -v ./... 56 | env: 57 | PGHOST: localhost 58 | PGUSER: postgres 59 | PGPASSWORD: secret 60 | PGSSLMODE: disable 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2021 Jack Christensen 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](https://godoc.org/github.com/jackc/pgtype?status.svg)](https://godoc.org/github.com/jackc/pgtype) 2 | ![CI](https://github.com/jackc/pgtype/workflows/CI/badge.svg) 3 | 4 | --- 5 | 6 | This version is used with pgx `v4`. In pgx `v5` it is part of the https://github.com/jackc/pgx repository. This `v4` 7 | version will reach end-of-life on July 1, 2025. Only security bug fixes will be made to this version. 8 | 9 | --- 10 | 11 | # pgtype 12 | 13 | pgtype implements Go types for over 70 PostgreSQL types. pgtype is the type system underlying the 14 | https://github.com/jackc/pgx PostgreSQL driver. These types support the binary format for enhanced performance with pgx. 15 | They also support the database/sql `Scan` and `Value` interfaces and can be used with https://github.com/lib/pq. 16 | -------------------------------------------------------------------------------- /aclitem.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | ) 7 | 8 | // ACLItem is used for PostgreSQL's aclitem data type. A sample aclitem 9 | // might look like this: 10 | // 11 | // postgres=arwdDxt/postgres 12 | // 13 | // Note, however, that because the user/role name part of an aclitem is 14 | // an identifier, it follows all the usual formatting rules for SQL 15 | // identifiers: if it contains spaces and other special characters, 16 | // it should appear in double-quotes: 17 | // 18 | // postgres=arwdDxt/"role with spaces" 19 | // 20 | type ACLItem struct { 21 | String string 22 | Status Status 23 | } 24 | 25 | func (dst *ACLItem) Set(src interface{}) error { 26 | if src == nil { 27 | *dst = ACLItem{Status: Null} 28 | return nil 29 | } 30 | 31 | if value, ok := src.(interface{ Get() interface{} }); ok { 32 | value2 := value.Get() 33 | if value2 != value { 34 | return dst.Set(value2) 35 | } 36 | } 37 | 38 | switch value := src.(type) { 39 | case string: 40 | *dst = ACLItem{String: value, Status: Present} 41 | case *string: 42 | if value == nil { 43 | *dst = ACLItem{Status: Null} 44 | } else { 45 | *dst = ACLItem{String: *value, Status: Present} 46 | } 47 | default: 48 | if originalSrc, ok := underlyingStringType(src); ok { 49 | return dst.Set(originalSrc) 50 | } 51 | return fmt.Errorf("cannot convert %v to ACLItem", value) 52 | } 53 | 54 | return nil 55 | } 56 | 57 | func (dst ACLItem) Get() interface{} { 58 | switch dst.Status { 59 | case Present: 60 | return dst.String 61 | case Null: 62 | return nil 63 | default: 64 | return dst.Status 65 | } 66 | } 67 | 68 | func (src *ACLItem) AssignTo(dst interface{}) error { 69 | switch src.Status { 70 | case Present: 71 | switch v := dst.(type) { 72 | case *string: 73 | *v = src.String 74 | return nil 75 | default: 76 | if nextDst, retry := GetAssignToDstType(dst); retry { 77 | return src.AssignTo(nextDst) 78 | } 79 | return fmt.Errorf("unable to assign to %T", dst) 80 | } 81 | case Null: 82 | return NullAssignTo(dst) 83 | } 84 | 85 | return fmt.Errorf("cannot decode %#v into %T", src, dst) 86 | } 87 | 88 | func (dst *ACLItem) DecodeText(ci *ConnInfo, src []byte) error { 89 | if src == nil { 90 | *dst = ACLItem{Status: Null} 91 | return nil 92 | } 93 | 94 | *dst = ACLItem{String: string(src), Status: Present} 95 | return nil 96 | } 97 | 98 | func (src ACLItem) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 99 | switch src.Status { 100 | case Null: 101 | return nil, nil 102 | case Undefined: 103 | return nil, errUndefined 104 | } 105 | 106 | return append(buf, src.String...), nil 107 | } 108 | 109 | // Scan implements the database/sql Scanner interface. 110 | func (dst *ACLItem) Scan(src interface{}) error { 111 | if src == nil { 112 | *dst = ACLItem{Status: Null} 113 | return nil 114 | } 115 | 116 | switch src := src.(type) { 117 | case string: 118 | return dst.DecodeText(nil, []byte(src)) 119 | case []byte: 120 | srcCopy := make([]byte, len(src)) 121 | copy(srcCopy, src) 122 | return dst.DecodeText(nil, srcCopy) 123 | } 124 | 125 | return fmt.Errorf("cannot scan %T", src) 126 | } 127 | 128 | // Value implements the database/sql/driver Valuer interface. 129 | func (src ACLItem) Value() (driver.Value, error) { 130 | switch src.Status { 131 | case Present: 132 | return src.String, nil 133 | case Null: 134 | return nil, nil 135 | default: 136 | return nil, errUndefined 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /aclitem_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | ) 10 | 11 | func TestACLItemTranscode(t *testing.T) { 12 | testutil.TestSuccessfulTranscode(t, "aclitem", []interface{}{ 13 | &pgtype.ACLItem{String: "postgres=arwdDxt/postgres", Status: pgtype.Present}, 14 | //&pgtype.ACLItem{String: `postgres=arwdDxt/" tricky, ' } "" \ test user "`, Status: pgtype.Present}, 15 | &pgtype.ACLItem{Status: pgtype.Null}, 16 | }) 17 | } 18 | 19 | func TestACLItemSet(t *testing.T) { 20 | successfulTests := []struct { 21 | source interface{} 22 | result pgtype.ACLItem 23 | }{ 24 | {source: "postgres=arwdDxt/postgres", result: pgtype.ACLItem{String: "postgres=arwdDxt/postgres", Status: pgtype.Present}}, 25 | {source: (*string)(nil), result: pgtype.ACLItem{Status: pgtype.Null}}, 26 | } 27 | 28 | for i, tt := range successfulTests { 29 | var d pgtype.ACLItem 30 | err := d.Set(tt.source) 31 | if err != nil { 32 | t.Errorf("%d: %v", i, err) 33 | } 34 | 35 | if d != tt.result { 36 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, d) 37 | } 38 | } 39 | } 40 | 41 | func TestACLItemAssignTo(t *testing.T) { 42 | var s string 43 | var ps *string 44 | 45 | simpleTests := []struct { 46 | src pgtype.ACLItem 47 | dst interface{} 48 | expected interface{} 49 | }{ 50 | {src: pgtype.ACLItem{String: "postgres=arwdDxt/postgres", Status: pgtype.Present}, dst: &s, expected: "postgres=arwdDxt/postgres"}, 51 | {src: pgtype.ACLItem{Status: pgtype.Null}, dst: &ps, expected: ((*string)(nil))}, 52 | } 53 | 54 | for i, tt := range simpleTests { 55 | err := tt.src.AssignTo(tt.dst) 56 | if err != nil { 57 | t.Errorf("%d: %v", i, err) 58 | } 59 | 60 | if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected { 61 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 62 | } 63 | } 64 | 65 | pointerAllocTests := []struct { 66 | src pgtype.ACLItem 67 | dst interface{} 68 | expected interface{} 69 | }{ 70 | {src: pgtype.ACLItem{String: "postgres=arwdDxt/postgres", Status: pgtype.Present}, dst: &ps, expected: "postgres=arwdDxt/postgres"}, 71 | } 72 | 73 | for i, tt := range pointerAllocTests { 74 | err := tt.src.AssignTo(tt.dst) 75 | if err != nil { 76 | t.Errorf("%d: %v", i, err) 77 | } 78 | 79 | if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected { 80 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 81 | } 82 | } 83 | 84 | errorTests := []struct { 85 | src pgtype.ACLItem 86 | dst interface{} 87 | }{ 88 | {src: pgtype.ACLItem{Status: pgtype.Null}, dst: &s}, 89 | } 90 | 91 | for i, tt := range errorTests { 92 | err := tt.src.AssignTo(tt.dst) 93 | if err == nil { 94 | t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /array_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestParseUntypedTextArray(t *testing.T) { 12 | tests := []struct { 13 | source string 14 | result pgtype.UntypedTextArray 15 | }{ 16 | { 17 | source: "{}", 18 | result: pgtype.UntypedTextArray{ 19 | Elements: nil, 20 | Quoted: nil, 21 | Dimensions: nil, 22 | }, 23 | }, 24 | { 25 | source: "{1}", 26 | result: pgtype.UntypedTextArray{ 27 | Elements: []string{"1"}, 28 | Quoted: []bool{false}, 29 | Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}}, 30 | }, 31 | }, 32 | { 33 | source: "{a,b}", 34 | result: pgtype.UntypedTextArray{ 35 | Elements: []string{"a", "b"}, 36 | Quoted: []bool{false, false}, 37 | Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}}, 38 | }, 39 | }, 40 | { 41 | source: `{"NULL"}`, 42 | result: pgtype.UntypedTextArray{ 43 | Elements: []string{"NULL"}, 44 | Quoted: []bool{true}, 45 | Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}}, 46 | }, 47 | }, 48 | { 49 | source: `{""}`, 50 | result: pgtype.UntypedTextArray{ 51 | Elements: []string{""}, 52 | Quoted: []bool{true}, 53 | Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}}, 54 | }, 55 | }, 56 | { 57 | source: `{"He said, \"Hello.\""}`, 58 | result: pgtype.UntypedTextArray{ 59 | Elements: []string{`He said, "Hello."`}, 60 | Quoted: []bool{true}, 61 | Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}}, 62 | }, 63 | }, 64 | { 65 | source: "{{a,b},{c,d},{e,f}}", 66 | result: pgtype.UntypedTextArray{ 67 | Elements: []string{"a", "b", "c", "d", "e", "f"}, 68 | Quoted: []bool{false, false, false, false, false, false}, 69 | Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}}, 70 | }, 71 | }, 72 | { 73 | source: "{{{a,b},{c,d},{e,f}},{{a,b},{c,d},{e,f}}}", 74 | result: pgtype.UntypedTextArray{ 75 | Elements: []string{"a", "b", "c", "d", "e", "f", "a", "b", "c", "d", "e", "f"}, 76 | Quoted: []bool{false, false, false, false, false, false, false, false, false, false, false, false}, 77 | Dimensions: []pgtype.ArrayDimension{ 78 | {Length: 2, LowerBound: 1}, 79 | {Length: 3, LowerBound: 1}, 80 | {Length: 2, LowerBound: 1}, 81 | }, 82 | }, 83 | }, 84 | { 85 | source: "[4:4]={1}", 86 | result: pgtype.UntypedTextArray{ 87 | Elements: []string{"1"}, 88 | Quoted: []bool{false}, 89 | Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 4}}, 90 | }, 91 | }, 92 | { 93 | source: "[4:5][2:3]={{a,b},{c,d}}", 94 | result: pgtype.UntypedTextArray{ 95 | Elements: []string{"a", "b", "c", "d"}, 96 | Quoted: []bool{false, false, false, false}, 97 | Dimensions: []pgtype.ArrayDimension{ 98 | {Length: 2, LowerBound: 4}, 99 | {Length: 2, LowerBound: 2}, 100 | }, 101 | }, 102 | }, 103 | { 104 | source: "[-4:-2]={1,2,3}", 105 | result: pgtype.UntypedTextArray{ 106 | Elements: []string{"1", "2", "3"}, 107 | Quoted: []bool{false, false, false}, 108 | Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: -4}}, 109 | }, 110 | }, 111 | } 112 | 113 | for i, tt := range tests { 114 | r, err := pgtype.ParseUntypedTextArray(tt.source) 115 | if err != nil { 116 | t.Errorf("%d: %v", i, err) 117 | continue 118 | } 119 | 120 | if !reflect.DeepEqual(*r, tt.result) { 121 | t.Errorf("%d: expected %+v to be parsed to %+v, but it was %+v", i, tt.source, tt.result, *r) 122 | } 123 | } 124 | } 125 | 126 | // https://github.com/jackc/pgx/issues/881 127 | func TestArrayAssignToEmptyToNonSlice(t *testing.T) { 128 | var a pgtype.Int4Array 129 | err := a.Set([]int32{}) 130 | require.NoError(t, err) 131 | 132 | var iface interface{} 133 | err = a.AssignTo(&iface) 134 | require.EqualError(t, err, "cannot assign *pgtype.Int4Array to *interface {}") 135 | } 136 | -------------------------------------------------------------------------------- /array_type_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestArrayTypeValue(t *testing.T) { 13 | arrayType := pgtype.NewArrayType("_text", pgtype.TextOID, func() pgtype.ValueTranscoder { return &pgtype.Text{} }) 14 | 15 | err := arrayType.Set(nil) 16 | require.NoError(t, err) 17 | 18 | gotValue := arrayType.Get() 19 | require.Nil(t, gotValue) 20 | 21 | slice := []string{"foo", "bar"} 22 | err = arrayType.AssignTo(&slice) 23 | require.NoError(t, err) 24 | require.Nil(t, slice) 25 | 26 | err = arrayType.Set([]string{}) 27 | require.NoError(t, err) 28 | 29 | gotValue = arrayType.Get() 30 | require.Len(t, gotValue, 0) 31 | 32 | err = arrayType.AssignTo(&slice) 33 | require.NoError(t, err) 34 | require.EqualValues(t, []string{}, slice) 35 | 36 | err = arrayType.Set([]string{"baz", "quz"}) 37 | require.NoError(t, err) 38 | 39 | gotValue = arrayType.Get() 40 | require.Len(t, gotValue, 2) 41 | 42 | err = arrayType.AssignTo(&slice) 43 | require.NoError(t, err) 44 | require.EqualValues(t, []string{"baz", "quz"}, slice) 45 | } 46 | 47 | func TestArrayTypeTranscode(t *testing.T) { 48 | conn := testutil.MustConnectPgx(t) 49 | defer testutil.MustCloseContext(t, conn) 50 | 51 | conn.ConnInfo().RegisterDataType(pgtype.DataType{ 52 | Value: pgtype.NewArrayType("_text", pgtype.TextOID, func() pgtype.ValueTranscoder { return &pgtype.Text{} }), 53 | Name: "_text", 54 | OID: pgtype.TextArrayOID, 55 | }) 56 | 57 | var dstStrings []string 58 | err := conn.QueryRow(context.Background(), "select $1::text[]", []string{"red", "green", "blue"}).Scan(&dstStrings) 59 | require.NoError(t, err) 60 | 61 | require.EqualValues(t, []string{"red", "green", "blue"}, dstStrings) 62 | } 63 | 64 | func TestArrayTypeEmptyArrayDoesNotBreakArrayType(t *testing.T) { 65 | conn := testutil.MustConnectPgx(t) 66 | defer testutil.MustCloseContext(t, conn) 67 | 68 | conn.ConnInfo().RegisterDataType(pgtype.DataType{ 69 | Value: pgtype.NewArrayType("_text", pgtype.TextOID, func() pgtype.ValueTranscoder { return &pgtype.Text{} }), 70 | Name: "_text", 71 | OID: pgtype.TextArrayOID, 72 | }) 73 | 74 | var dstStrings []string 75 | err := conn.QueryRow(context.Background(), "select '{}'::text[]").Scan(&dstStrings) 76 | require.NoError(t, err) 77 | 78 | require.EqualValues(t, []string{}, dstStrings) 79 | 80 | err = conn.QueryRow(context.Background(), "select $1::text[]", []string{"red", "green", "blue"}).Scan(&dstStrings) 81 | require.NoError(t, err) 82 | 83 | require.EqualValues(t, []string{"red", "green", "blue"}, dstStrings) 84 | } 85 | -------------------------------------------------------------------------------- /bit.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | ) 6 | 7 | type Bit Varbit 8 | 9 | func (dst *Bit) Set(src interface{}) error { 10 | return (*Varbit)(dst).Set(src) 11 | } 12 | 13 | func (dst Bit) Get() interface{} { 14 | return (Varbit)(dst).Get() 15 | } 16 | 17 | func (src *Bit) AssignTo(dst interface{}) error { 18 | return (*Varbit)(src).AssignTo(dst) 19 | } 20 | 21 | func (dst *Bit) DecodeBinary(ci *ConnInfo, src []byte) error { 22 | return (*Varbit)(dst).DecodeBinary(ci, src) 23 | } 24 | 25 | func (src Bit) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 26 | return (Varbit)(src).EncodeBinary(ci, buf) 27 | } 28 | 29 | func (dst *Bit) DecodeText(ci *ConnInfo, src []byte) error { 30 | return (*Varbit)(dst).DecodeText(ci, src) 31 | } 32 | 33 | func (src Bit) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 34 | return (Varbit)(src).EncodeText(ci, buf) 35 | } 36 | 37 | // Scan implements the database/sql Scanner interface. 38 | func (dst *Bit) Scan(src interface{}) error { 39 | return (*Varbit)(dst).Scan(src) 40 | } 41 | 42 | // Value implements the database/sql/driver Valuer interface. 43 | func (src Bit) Value() (driver.Value, error) { 44 | return (Varbit)(src).Value() 45 | } 46 | -------------------------------------------------------------------------------- /bit_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype" 7 | "github.com/jackc/pgtype/testutil" 8 | ) 9 | 10 | func TestBitTranscode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "bit(40)", []interface{}{ 12 | &pgtype.Varbit{Bytes: []byte{0, 0, 0, 0, 0}, Len: 40, Status: pgtype.Present}, 13 | &pgtype.Varbit{Bytes: []byte{0, 1, 128, 254, 255}, Len: 40, Status: pgtype.Present}, 14 | &pgtype.Varbit{Status: pgtype.Null}, 15 | }) 16 | } 17 | 18 | func TestBitNormalize(t *testing.T) { 19 | testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{ 20 | { 21 | SQL: "select B'111111111'", 22 | Value: &pgtype.Bit{Bytes: []byte{255, 128}, Len: 9, Status: pgtype.Present}, 23 | }, 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /box.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/binary" 6 | "fmt" 7 | "math" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/jackc/pgio" 12 | ) 13 | 14 | type Box struct { 15 | P [2]Vec2 16 | Status Status 17 | } 18 | 19 | func (dst *Box) Set(src interface{}) error { 20 | return fmt.Errorf("cannot convert %v to Box", src) 21 | } 22 | 23 | func (dst Box) Get() interface{} { 24 | switch dst.Status { 25 | case Present: 26 | return dst 27 | case Null: 28 | return nil 29 | default: 30 | return dst.Status 31 | } 32 | } 33 | 34 | func (src *Box) AssignTo(dst interface{}) error { 35 | return fmt.Errorf("cannot assign %v to %T", src, dst) 36 | } 37 | 38 | func (dst *Box) DecodeText(ci *ConnInfo, src []byte) error { 39 | if src == nil { 40 | *dst = Box{Status: Null} 41 | return nil 42 | } 43 | 44 | if len(src) < 11 { 45 | return fmt.Errorf("invalid length for Box: %v", len(src)) 46 | } 47 | 48 | str := string(src[1:]) 49 | 50 | var end int 51 | end = strings.IndexByte(str, ',') 52 | 53 | x1, err := strconv.ParseFloat(str[:end], 64) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | str = str[end+1:] 59 | end = strings.IndexByte(str, ')') 60 | 61 | y1, err := strconv.ParseFloat(str[:end], 64) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | str = str[end+3:] 67 | end = strings.IndexByte(str, ',') 68 | 69 | x2, err := strconv.ParseFloat(str[:end], 64) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | str = str[end+1 : len(str)-1] 75 | 76 | y2, err := strconv.ParseFloat(str, 64) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | *dst = Box{P: [2]Vec2{{x1, y1}, {x2, y2}}, Status: Present} 82 | return nil 83 | } 84 | 85 | func (dst *Box) DecodeBinary(ci *ConnInfo, src []byte) error { 86 | if src == nil { 87 | *dst = Box{Status: Null} 88 | return nil 89 | } 90 | 91 | if len(src) != 32 { 92 | return fmt.Errorf("invalid length for Box: %v", len(src)) 93 | } 94 | 95 | x1 := binary.BigEndian.Uint64(src) 96 | y1 := binary.BigEndian.Uint64(src[8:]) 97 | x2 := binary.BigEndian.Uint64(src[16:]) 98 | y2 := binary.BigEndian.Uint64(src[24:]) 99 | 100 | *dst = Box{ 101 | P: [2]Vec2{ 102 | {math.Float64frombits(x1), math.Float64frombits(y1)}, 103 | {math.Float64frombits(x2), math.Float64frombits(y2)}, 104 | }, 105 | Status: Present, 106 | } 107 | return nil 108 | } 109 | 110 | func (src Box) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 111 | switch src.Status { 112 | case Null: 113 | return nil, nil 114 | case Undefined: 115 | return nil, errUndefined 116 | } 117 | 118 | buf = append(buf, fmt.Sprintf(`(%s,%s),(%s,%s)`, 119 | strconv.FormatFloat(src.P[0].X, 'f', -1, 64), 120 | strconv.FormatFloat(src.P[0].Y, 'f', -1, 64), 121 | strconv.FormatFloat(src.P[1].X, 'f', -1, 64), 122 | strconv.FormatFloat(src.P[1].Y, 'f', -1, 64), 123 | )...) 124 | return buf, nil 125 | } 126 | 127 | func (src Box) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 128 | switch src.Status { 129 | case Null: 130 | return nil, nil 131 | case Undefined: 132 | return nil, errUndefined 133 | } 134 | 135 | buf = pgio.AppendUint64(buf, math.Float64bits(src.P[0].X)) 136 | buf = pgio.AppendUint64(buf, math.Float64bits(src.P[0].Y)) 137 | buf = pgio.AppendUint64(buf, math.Float64bits(src.P[1].X)) 138 | buf = pgio.AppendUint64(buf, math.Float64bits(src.P[1].Y)) 139 | 140 | return buf, nil 141 | } 142 | 143 | // Scan implements the database/sql Scanner interface. 144 | func (dst *Box) Scan(src interface{}) error { 145 | if src == nil { 146 | *dst = Box{Status: Null} 147 | return nil 148 | } 149 | 150 | switch src := src.(type) { 151 | case string: 152 | return dst.DecodeText(nil, []byte(src)) 153 | case []byte: 154 | srcCopy := make([]byte, len(src)) 155 | copy(srcCopy, src) 156 | return dst.DecodeText(nil, srcCopy) 157 | } 158 | 159 | return fmt.Errorf("cannot scan %T", src) 160 | } 161 | 162 | // Value implements the database/sql/driver Valuer interface. 163 | func (src Box) Value() (driver.Value, error) { 164 | return EncodeValueText(src) 165 | } 166 | -------------------------------------------------------------------------------- /box_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype" 7 | "github.com/jackc/pgtype/testutil" 8 | ) 9 | 10 | func TestBoxTranscode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "box", []interface{}{ 12 | &pgtype.Box{ 13 | P: [2]pgtype.Vec2{{7.1, 5.2345678}, {3.14, 1.678}}, 14 | Status: pgtype.Present, 15 | }, 16 | &pgtype.Box{ 17 | P: [2]pgtype.Vec2{{7.1, 1.678}, {-13.14, -5.234}}, 18 | Status: pgtype.Present, 19 | }, 20 | &pgtype.Box{Status: pgtype.Null}, 21 | }) 22 | } 23 | 24 | func TestBoxNormalize(t *testing.T) { 25 | testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{ 26 | { 27 | SQL: "select '3.14, 1.678, 7.1, 5.234'::box", 28 | Value: &pgtype.Box{ 29 | P: [2]pgtype.Vec2{{7.1, 5.234}, {3.14, 1.678}}, 30 | Status: pgtype.Present, 31 | }, 32 | }, 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /bpchar.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | ) 7 | 8 | // BPChar is fixed-length, blank padded char type 9 | // character(n), char(n) 10 | type BPChar Text 11 | 12 | // Set converts from src to dst. 13 | func (dst *BPChar) Set(src interface{}) error { 14 | return (*Text)(dst).Set(src) 15 | } 16 | 17 | // Get returns underlying value 18 | func (dst BPChar) Get() interface{} { 19 | return (Text)(dst).Get() 20 | } 21 | 22 | // AssignTo assigns from src to dst. 23 | func (src *BPChar) AssignTo(dst interface{}) error { 24 | switch src.Status { 25 | case Present: 26 | switch v := dst.(type) { 27 | case *rune: 28 | runes := []rune(src.String) 29 | if len(runes) == 1 { 30 | *v = runes[0] 31 | return nil 32 | } 33 | case *string: 34 | *v = src.String 35 | return nil 36 | case *[]byte: 37 | *v = make([]byte, len(src.String)) 38 | copy(*v, src.String) 39 | return nil 40 | default: 41 | if nextDst, retry := GetAssignToDstType(dst); retry { 42 | return src.AssignTo(nextDst) 43 | } 44 | return fmt.Errorf("unable to assign to %T", dst) 45 | } 46 | case Null: 47 | return NullAssignTo(dst) 48 | } 49 | 50 | return fmt.Errorf("cannot decode %#v into %T", src, dst) 51 | } 52 | 53 | func (BPChar) PreferredResultFormat() int16 { 54 | return TextFormatCode 55 | } 56 | 57 | func (dst *BPChar) DecodeText(ci *ConnInfo, src []byte) error { 58 | return (*Text)(dst).DecodeText(ci, src) 59 | } 60 | 61 | func (dst *BPChar) DecodeBinary(ci *ConnInfo, src []byte) error { 62 | return (*Text)(dst).DecodeBinary(ci, src) 63 | } 64 | 65 | func (BPChar) PreferredParamFormat() int16 { 66 | return TextFormatCode 67 | } 68 | 69 | func (src BPChar) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 70 | return (Text)(src).EncodeText(ci, buf) 71 | } 72 | 73 | func (src BPChar) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 74 | return (Text)(src).EncodeBinary(ci, buf) 75 | } 76 | 77 | // Scan implements the database/sql Scanner interface. 78 | func (dst *BPChar) Scan(src interface{}) error { 79 | return (*Text)(dst).Scan(src) 80 | } 81 | 82 | // Value implements the database/sql/driver Valuer interface. 83 | func (src BPChar) Value() (driver.Value, error) { 84 | return (Text)(src).Value() 85 | } 86 | 87 | func (src BPChar) MarshalJSON() ([]byte, error) { 88 | return (Text)(src).MarshalJSON() 89 | } 90 | 91 | func (dst *BPChar) UnmarshalJSON(b []byte) error { 92 | return (*Text)(dst).UnmarshalJSON(b) 93 | } 94 | -------------------------------------------------------------------------------- /bpchar_array_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype" 7 | "github.com/jackc/pgtype/testutil" 8 | ) 9 | 10 | func TestBPCharArrayTranscode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "char(8)[]", []interface{}{ 12 | &pgtype.BPCharArray{ 13 | Elements: nil, 14 | Dimensions: nil, 15 | Status: pgtype.Present, 16 | }, 17 | &pgtype.BPCharArray{ 18 | Elements: []pgtype.BPChar{ 19 | pgtype.BPChar{String: "foo ", Status: pgtype.Present}, 20 | pgtype.BPChar{Status: pgtype.Null}, 21 | }, 22 | Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}}, 23 | Status: pgtype.Present, 24 | }, 25 | &pgtype.BPCharArray{Status: pgtype.Null}, 26 | &pgtype.BPCharArray{ 27 | Elements: []pgtype.BPChar{ 28 | pgtype.BPChar{String: "bar ", Status: pgtype.Present}, 29 | pgtype.BPChar{String: "NuLL ", Status: pgtype.Present}, 30 | pgtype.BPChar{String: `wow"quz\`, Status: pgtype.Present}, 31 | pgtype.BPChar{String: "1 ", Status: pgtype.Present}, 32 | pgtype.BPChar{String: "1 ", Status: pgtype.Present}, 33 | pgtype.BPChar{String: "null ", Status: pgtype.Present}, 34 | }, 35 | Dimensions: []pgtype.ArrayDimension{ 36 | {Length: 3, LowerBound: 1}, 37 | {Length: 2, LowerBound: 1}, 38 | }, 39 | Status: pgtype.Present, 40 | }, 41 | &pgtype.BPCharArray{ 42 | Elements: []pgtype.BPChar{ 43 | pgtype.BPChar{String: " bar ", Status: pgtype.Present}, 44 | pgtype.BPChar{String: " baz ", Status: pgtype.Present}, 45 | pgtype.BPChar{String: " quz ", Status: pgtype.Present}, 46 | pgtype.BPChar{String: "foo ", Status: pgtype.Present}, 47 | }, 48 | Dimensions: []pgtype.ArrayDimension{ 49 | {Length: 2, LowerBound: 4}, 50 | {Length: 2, LowerBound: 2}, 51 | }, 52 | Status: pgtype.Present, 53 | }, 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /bpchar_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | ) 10 | 11 | func TestChar3Transcode(t *testing.T) { 12 | testutil.TestSuccessfulTranscodeEqFunc(t, "char(3)", []interface{}{ 13 | &pgtype.BPChar{String: "a ", Status: pgtype.Present}, 14 | &pgtype.BPChar{String: " a ", Status: pgtype.Present}, 15 | &pgtype.BPChar{String: "嗨 ", Status: pgtype.Present}, 16 | &pgtype.BPChar{String: " ", Status: pgtype.Present}, 17 | &pgtype.BPChar{Status: pgtype.Null}, 18 | }, func(aa, bb interface{}) bool { 19 | a := aa.(pgtype.BPChar) 20 | b := bb.(pgtype.BPChar) 21 | 22 | return a.Status == b.Status && a.String == b.String 23 | }) 24 | } 25 | 26 | func TestBPCharAssignTo(t *testing.T) { 27 | var ( 28 | str string 29 | run rune 30 | ) 31 | simpleTests := []struct { 32 | src pgtype.BPChar 33 | dst interface{} 34 | expected interface{} 35 | }{ 36 | {src: pgtype.BPChar{String: "simple", Status: pgtype.Present}, dst: &str, expected: "simple"}, 37 | {src: pgtype.BPChar{String: "嗨", Status: pgtype.Present}, dst: &run, expected: '嗨'}, 38 | } 39 | 40 | for i, tt := range simpleTests { 41 | err := tt.src.AssignTo(tt.dst) 42 | if err != nil { 43 | t.Errorf("%d: %v", i, err) 44 | } 45 | 46 | if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected { 47 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /bytea.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/hex" 6 | "fmt" 7 | ) 8 | 9 | type Bytea struct { 10 | Bytes []byte 11 | Status Status 12 | } 13 | 14 | func (dst *Bytea) Set(src interface{}) error { 15 | if src == nil { 16 | *dst = Bytea{Status: Null} 17 | return nil 18 | } 19 | 20 | if value, ok := src.(interface{ Get() interface{} }); ok { 21 | value2 := value.Get() 22 | if value2 != value { 23 | return dst.Set(value2) 24 | } 25 | } 26 | 27 | switch value := src.(type) { 28 | case []byte: 29 | if value != nil { 30 | *dst = Bytea{Bytes: value, Status: Present} 31 | } else { 32 | *dst = Bytea{Status: Null} 33 | } 34 | default: 35 | if originalSrc, ok := underlyingBytesType(src); ok { 36 | return dst.Set(originalSrc) 37 | } 38 | return fmt.Errorf("cannot convert %v to Bytea", value) 39 | } 40 | 41 | return nil 42 | } 43 | 44 | func (dst Bytea) Get() interface{} { 45 | switch dst.Status { 46 | case Present: 47 | return dst.Bytes 48 | case Null: 49 | return nil 50 | default: 51 | return dst.Status 52 | } 53 | } 54 | 55 | func (src *Bytea) AssignTo(dst interface{}) error { 56 | switch src.Status { 57 | case Present: 58 | switch v := dst.(type) { 59 | case *[]byte: 60 | buf := make([]byte, len(src.Bytes)) 61 | copy(buf, src.Bytes) 62 | *v = buf 63 | return nil 64 | default: 65 | if nextDst, retry := GetAssignToDstType(dst); retry { 66 | return src.AssignTo(nextDst) 67 | } 68 | return fmt.Errorf("unable to assign to %T", dst) 69 | } 70 | case Null: 71 | return NullAssignTo(dst) 72 | } 73 | 74 | return fmt.Errorf("cannot decode %#v into %T", src, dst) 75 | } 76 | 77 | // DecodeText only supports the hex format. This has been the default since 78 | // PostgreSQL 9.0. 79 | func (dst *Bytea) DecodeText(ci *ConnInfo, src []byte) error { 80 | if src == nil { 81 | *dst = Bytea{Status: Null} 82 | return nil 83 | } 84 | 85 | if len(src) < 2 || src[0] != '\\' || src[1] != 'x' { 86 | return fmt.Errorf("invalid hex format") 87 | } 88 | 89 | buf := make([]byte, (len(src)-2)/2) 90 | _, err := hex.Decode(buf, src[2:]) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | *dst = Bytea{Bytes: buf, Status: Present} 96 | return nil 97 | } 98 | 99 | func (dst *Bytea) DecodeBinary(ci *ConnInfo, src []byte) error { 100 | if src == nil { 101 | *dst = Bytea{Status: Null} 102 | return nil 103 | } 104 | 105 | *dst = Bytea{Bytes: src, Status: Present} 106 | return nil 107 | } 108 | 109 | func (src Bytea) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 110 | switch src.Status { 111 | case Null: 112 | return nil, nil 113 | case Undefined: 114 | return nil, errUndefined 115 | } 116 | 117 | buf = append(buf, `\x`...) 118 | buf = append(buf, hex.EncodeToString(src.Bytes)...) 119 | return buf, nil 120 | } 121 | 122 | func (src Bytea) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 123 | switch src.Status { 124 | case Null: 125 | return nil, nil 126 | case Undefined: 127 | return nil, errUndefined 128 | } 129 | 130 | return append(buf, src.Bytes...), nil 131 | } 132 | 133 | // Scan implements the database/sql Scanner interface. 134 | func (dst *Bytea) Scan(src interface{}) error { 135 | if src == nil { 136 | *dst = Bytea{Status: Null} 137 | return nil 138 | } 139 | 140 | switch src := src.(type) { 141 | case string: 142 | return dst.DecodeText(nil, []byte(src)) 143 | case []byte: 144 | buf := make([]byte, len(src)) 145 | copy(buf, src) 146 | *dst = Bytea{Bytes: buf, Status: Present} 147 | return nil 148 | } 149 | 150 | return fmt.Errorf("cannot scan %T", src) 151 | } 152 | 153 | // Value implements the database/sql/driver Valuer interface. 154 | func (src Bytea) Value() (driver.Value, error) { 155 | switch src.Status { 156 | case Present: 157 | return src.Bytes, nil 158 | case Null: 159 | return nil, nil 160 | default: 161 | return nil, errUndefined 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /bytea_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | ) 10 | 11 | func TestByteaTranscode(t *testing.T) { 12 | testutil.TestSuccessfulTranscode(t, "bytea", []interface{}{ 13 | &pgtype.Bytea{Bytes: []byte{1, 2, 3}, Status: pgtype.Present}, 14 | &pgtype.Bytea{Bytes: []byte{}, Status: pgtype.Present}, 15 | &pgtype.Bytea{Bytes: nil, Status: pgtype.Null}, 16 | }) 17 | } 18 | 19 | func TestByteaSet(t *testing.T) { 20 | successfulTests := []struct { 21 | source interface{} 22 | result pgtype.Bytea 23 | }{ 24 | {source: []byte{1, 2, 3}, result: pgtype.Bytea{Bytes: []byte{1, 2, 3}, Status: pgtype.Present}}, 25 | {source: []byte{}, result: pgtype.Bytea{Bytes: []byte{}, Status: pgtype.Present}}, 26 | {source: []byte(nil), result: pgtype.Bytea{Status: pgtype.Null}}, 27 | {source: _byteSlice{1, 2, 3}, result: pgtype.Bytea{Bytes: []byte{1, 2, 3}, Status: pgtype.Present}}, 28 | {source: _byteSlice(nil), result: pgtype.Bytea{Status: pgtype.Null}}, 29 | } 30 | 31 | for i, tt := range successfulTests { 32 | var r pgtype.Bytea 33 | err := r.Set(tt.source) 34 | if err != nil { 35 | t.Errorf("%d: %v", i, err) 36 | } 37 | 38 | if !reflect.DeepEqual(r, tt.result) { 39 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) 40 | } 41 | } 42 | } 43 | 44 | func TestByteaAssignTo(t *testing.T) { 45 | var buf []byte 46 | var _buf _byteSlice 47 | var pbuf *[]byte 48 | var _pbuf *_byteSlice 49 | 50 | simpleTests := []struct { 51 | src pgtype.Bytea 52 | dst interface{} 53 | expected interface{} 54 | }{ 55 | {src: pgtype.Bytea{Bytes: []byte{1, 2, 3}, Status: pgtype.Present}, dst: &buf, expected: []byte{1, 2, 3}}, 56 | {src: pgtype.Bytea{Bytes: []byte{1, 2, 3}, Status: pgtype.Present}, dst: &_buf, expected: _byteSlice{1, 2, 3}}, 57 | {src: pgtype.Bytea{Bytes: []byte{1, 2, 3}, Status: pgtype.Present}, dst: &pbuf, expected: &[]byte{1, 2, 3}}, 58 | {src: pgtype.Bytea{Bytes: []byte{1, 2, 3}, Status: pgtype.Present}, dst: &_pbuf, expected: &_byteSlice{1, 2, 3}}, 59 | {src: pgtype.Bytea{Status: pgtype.Null}, dst: &pbuf, expected: ((*[]byte)(nil))}, 60 | {src: pgtype.Bytea{Status: pgtype.Null}, dst: &_pbuf, expected: ((*_byteSlice)(nil))}, 61 | } 62 | 63 | for i, tt := range simpleTests { 64 | err := tt.src.AssignTo(tt.dst) 65 | if err != nil { 66 | t.Errorf("%d: %v", i, err) 67 | } 68 | 69 | if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) { 70 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cid.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | ) 6 | 7 | // CID is PostgreSQL's Command Identifier type. 8 | // 9 | // When one does 10 | // 11 | // select cmin, cmax, * from some_table; 12 | // 13 | // it is the data type of the cmin and cmax hidden system columns. 14 | // 15 | // It is currently implemented as an unsigned four byte integer. 16 | // Its definition can be found in src/include/c.h as CommandId 17 | // in the PostgreSQL sources. 18 | type CID pguint32 19 | 20 | // Set converts from src to dst. Note that as CID is not a general 21 | // number type Set does not do automatic type conversion as other number 22 | // types do. 23 | func (dst *CID) Set(src interface{}) error { 24 | return (*pguint32)(dst).Set(src) 25 | } 26 | 27 | func (dst CID) Get() interface{} { 28 | return (pguint32)(dst).Get() 29 | } 30 | 31 | // AssignTo assigns from src to dst. Note that as CID is not a general number 32 | // type AssignTo does not do automatic type conversion as other number types do. 33 | func (src *CID) AssignTo(dst interface{}) error { 34 | return (*pguint32)(src).AssignTo(dst) 35 | } 36 | 37 | func (dst *CID) DecodeText(ci *ConnInfo, src []byte) error { 38 | return (*pguint32)(dst).DecodeText(ci, src) 39 | } 40 | 41 | func (dst *CID) DecodeBinary(ci *ConnInfo, src []byte) error { 42 | return (*pguint32)(dst).DecodeBinary(ci, src) 43 | } 44 | 45 | func (src CID) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 46 | return (pguint32)(src).EncodeText(ci, buf) 47 | } 48 | 49 | func (src CID) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 50 | return (pguint32)(src).EncodeBinary(ci, buf) 51 | } 52 | 53 | // Scan implements the database/sql Scanner interface. 54 | func (dst *CID) Scan(src interface{}) error { 55 | return (*pguint32)(dst).Scan(src) 56 | } 57 | 58 | // Value implements the database/sql/driver Valuer interface. 59 | func (src CID) Value() (driver.Value, error) { 60 | return (pguint32)(src).Value() 61 | } 62 | -------------------------------------------------------------------------------- /cid_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | ) 10 | 11 | func TestCIDTranscode(t *testing.T) { 12 | pgTypeName := "cid" 13 | values := []interface{}{ 14 | &pgtype.CID{Uint: 42, Status: pgtype.Present}, 15 | &pgtype.CID{Status: pgtype.Null}, 16 | } 17 | eqFunc := func(a, b interface{}) bool { 18 | return reflect.DeepEqual(a, b) 19 | } 20 | 21 | testutil.TestPgxSuccessfulTranscodeEqFunc(t, pgTypeName, values, eqFunc) 22 | 23 | for _, driverName := range []string{"github.com/lib/pq", "github.com/jackc/pgx/stdlib"} { 24 | testutil.TestDatabaseSQLSuccessfulTranscodeEqFunc(t, driverName, pgTypeName, values, eqFunc) 25 | } 26 | } 27 | 28 | func TestCIDSet(t *testing.T) { 29 | successfulTests := []struct { 30 | source interface{} 31 | result pgtype.CID 32 | }{ 33 | {source: uint32(1), result: pgtype.CID{Uint: 1, Status: pgtype.Present}}, 34 | } 35 | 36 | for i, tt := range successfulTests { 37 | var r pgtype.CID 38 | err := r.Set(tt.source) 39 | if err != nil { 40 | t.Errorf("%d: %v", i, err) 41 | } 42 | 43 | if r != tt.result { 44 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) 45 | } 46 | } 47 | } 48 | 49 | func TestCIDAssignTo(t *testing.T) { 50 | var ui32 uint32 51 | var pui32 *uint32 52 | 53 | simpleTests := []struct { 54 | src pgtype.CID 55 | dst interface{} 56 | expected interface{} 57 | }{ 58 | {src: pgtype.CID{Uint: 42, Status: pgtype.Present}, dst: &ui32, expected: uint32(42)}, 59 | {src: pgtype.CID{Status: pgtype.Null}, dst: &pui32, expected: ((*uint32)(nil))}, 60 | } 61 | 62 | for i, tt := range simpleTests { 63 | err := tt.src.AssignTo(tt.dst) 64 | if err != nil { 65 | t.Errorf("%d: %v", i, err) 66 | } 67 | 68 | if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected { 69 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 70 | } 71 | } 72 | 73 | pointerAllocTests := []struct { 74 | src pgtype.CID 75 | dst interface{} 76 | expected interface{} 77 | }{ 78 | {src: pgtype.CID{Uint: 42, Status: pgtype.Present}, dst: &pui32, expected: uint32(42)}, 79 | } 80 | 81 | for i, tt := range pointerAllocTests { 82 | err := tt.src.AssignTo(tt.dst) 83 | if err != nil { 84 | t.Errorf("%d: %v", i, err) 85 | } 86 | 87 | if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected { 88 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 89 | } 90 | } 91 | 92 | errorTests := []struct { 93 | src pgtype.CID 94 | dst interface{} 95 | }{ 96 | {src: pgtype.CID{Status: pgtype.Null}, dst: &ui32}, 97 | } 98 | 99 | for i, tt := range errorTests { 100 | err := tt.src.AssignTo(tt.dst) 101 | if err == nil { 102 | t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /cidr.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import "database/sql/driver" 4 | 5 | type CIDR Inet 6 | 7 | func (dst *CIDR) Set(src interface{}) error { 8 | return (*Inet)(dst).Set(src) 9 | } 10 | 11 | func (dst CIDR) Get() interface{} { 12 | return (Inet)(dst).Get() 13 | } 14 | 15 | func (src *CIDR) AssignTo(dst interface{}) error { 16 | return (*Inet)(src).AssignTo(dst) 17 | } 18 | 19 | func (dst *CIDR) DecodeText(ci *ConnInfo, src []byte) error { 20 | return (*Inet)(dst).DecodeText(ci, src) 21 | } 22 | 23 | func (dst *CIDR) DecodeBinary(ci *ConnInfo, src []byte) error { 24 | return (*Inet)(dst).DecodeBinary(ci, src) 25 | } 26 | 27 | func (src CIDR) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 28 | return (Inet)(src).EncodeText(ci, buf) 29 | } 30 | 31 | func (src CIDR) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 32 | return (Inet)(src).EncodeBinary(ci, buf) 33 | } 34 | 35 | // Scan implements the database/sql Scanner interface. 36 | func (dst *CIDR) Scan(src interface{}) error { 37 | return (*Inet)(dst).Scan(src) 38 | } 39 | 40 | // Value implements the database/sql/driver Valuer interface. 41 | func (src CIDR) Value() (driver.Value, error) { 42 | return (Inet)(src).Value() 43 | } 44 | -------------------------------------------------------------------------------- /circle.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/binary" 6 | "fmt" 7 | "math" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/jackc/pgio" 12 | ) 13 | 14 | type Circle struct { 15 | P Vec2 16 | R float64 17 | Status Status 18 | } 19 | 20 | func (dst *Circle) Set(src interface{}) error { 21 | return fmt.Errorf("cannot convert %v to Circle", src) 22 | } 23 | 24 | func (dst Circle) Get() interface{} { 25 | switch dst.Status { 26 | case Present: 27 | return dst 28 | case Null: 29 | return nil 30 | default: 31 | return dst.Status 32 | } 33 | } 34 | 35 | func (src *Circle) AssignTo(dst interface{}) error { 36 | return fmt.Errorf("cannot assign %v to %T", src, dst) 37 | } 38 | 39 | func (dst *Circle) DecodeText(ci *ConnInfo, src []byte) error { 40 | if src == nil { 41 | *dst = Circle{Status: Null} 42 | return nil 43 | } 44 | 45 | if len(src) < 9 { 46 | return fmt.Errorf("invalid length for Circle: %v", len(src)) 47 | } 48 | 49 | str := string(src[2:]) 50 | end := strings.IndexByte(str, ',') 51 | x, err := strconv.ParseFloat(str[:end], 64) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | str = str[end+1:] 57 | end = strings.IndexByte(str, ')') 58 | 59 | y, err := strconv.ParseFloat(str[:end], 64) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | str = str[end+2 : len(str)-1] 65 | 66 | r, err := strconv.ParseFloat(str, 64) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | *dst = Circle{P: Vec2{x, y}, R: r, Status: Present} 72 | return nil 73 | } 74 | 75 | func (dst *Circle) DecodeBinary(ci *ConnInfo, src []byte) error { 76 | if src == nil { 77 | *dst = Circle{Status: Null} 78 | return nil 79 | } 80 | 81 | if len(src) != 24 { 82 | return fmt.Errorf("invalid length for Circle: %v", len(src)) 83 | } 84 | 85 | x := binary.BigEndian.Uint64(src) 86 | y := binary.BigEndian.Uint64(src[8:]) 87 | r := binary.BigEndian.Uint64(src[16:]) 88 | 89 | *dst = Circle{ 90 | P: Vec2{math.Float64frombits(x), math.Float64frombits(y)}, 91 | R: math.Float64frombits(r), 92 | Status: Present, 93 | } 94 | return nil 95 | } 96 | 97 | func (src Circle) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 98 | switch src.Status { 99 | case Null: 100 | return nil, nil 101 | case Undefined: 102 | return nil, errUndefined 103 | } 104 | 105 | buf = append(buf, fmt.Sprintf(`<(%s,%s),%s>`, 106 | strconv.FormatFloat(src.P.X, 'f', -1, 64), 107 | strconv.FormatFloat(src.P.Y, 'f', -1, 64), 108 | strconv.FormatFloat(src.R, 'f', -1, 64), 109 | )...) 110 | 111 | return buf, nil 112 | } 113 | 114 | func (src Circle) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 115 | switch src.Status { 116 | case Null: 117 | return nil, nil 118 | case Undefined: 119 | return nil, errUndefined 120 | } 121 | 122 | buf = pgio.AppendUint64(buf, math.Float64bits(src.P.X)) 123 | buf = pgio.AppendUint64(buf, math.Float64bits(src.P.Y)) 124 | buf = pgio.AppendUint64(buf, math.Float64bits(src.R)) 125 | return buf, nil 126 | } 127 | 128 | // Scan implements the database/sql Scanner interface. 129 | func (dst *Circle) Scan(src interface{}) error { 130 | if src == nil { 131 | *dst = Circle{Status: Null} 132 | return nil 133 | } 134 | 135 | switch src := src.(type) { 136 | case string: 137 | return dst.DecodeText(nil, []byte(src)) 138 | case []byte: 139 | srcCopy := make([]byte, len(src)) 140 | copy(srcCopy, src) 141 | return dst.DecodeText(nil, srcCopy) 142 | } 143 | 144 | return fmt.Errorf("cannot scan %T", src) 145 | } 146 | 147 | // Value implements the database/sql/driver Valuer interface. 148 | func (src Circle) Value() (driver.Value, error) { 149 | return EncodeValueText(src) 150 | } 151 | -------------------------------------------------------------------------------- /circle_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype" 7 | "github.com/jackc/pgtype/testutil" 8 | ) 9 | 10 | func TestCircleTranscode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "circle", []interface{}{ 12 | &pgtype.Circle{P: pgtype.Vec2{1.234, 5.67890123}, R: 3.5, Status: pgtype.Present}, 13 | &pgtype.Circle{P: pgtype.Vec2{-1.234, -5.6789}, R: 12.9, Status: pgtype.Present}, 14 | &pgtype.Circle{Status: pgtype.Null}, 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /composite_bench_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgio" 7 | "github.com/jackc/pgtype" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | type MyCompositeRaw struct { 12 | A int32 13 | B *string 14 | } 15 | 16 | func (src MyCompositeRaw) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 17 | buf = pgio.AppendUint32(buf, 2) 18 | 19 | buf = pgio.AppendUint32(buf, pgtype.Int4OID) 20 | buf = pgio.AppendInt32(buf, 4) 21 | buf = pgio.AppendInt32(buf, src.A) 22 | 23 | buf = pgio.AppendUint32(buf, pgtype.TextOID) 24 | if src.B != nil { 25 | buf = pgio.AppendInt32(buf, int32(len(*src.B))) 26 | buf = append(buf, (*src.B)...) 27 | } else { 28 | buf = pgio.AppendInt32(buf, -1) 29 | } 30 | 31 | return buf, nil 32 | } 33 | 34 | func (dst *MyCompositeRaw) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error { 35 | a := pgtype.Int4{} 36 | b := pgtype.Text{} 37 | 38 | scanner := pgtype.NewCompositeBinaryScanner(ci, src) 39 | scanner.ScanDecoder(&a) 40 | scanner.ScanDecoder(&b) 41 | 42 | if scanner.Err() != nil { 43 | return scanner.Err() 44 | } 45 | 46 | dst.A = a.Int 47 | if b.Status == pgtype.Present { 48 | dst.B = &b.String 49 | } else { 50 | dst.B = nil 51 | } 52 | 53 | return nil 54 | } 55 | 56 | var x []byte 57 | 58 | func BenchmarkBinaryEncodingManual(b *testing.B) { 59 | buf := make([]byte, 0, 128) 60 | ci := pgtype.NewConnInfo() 61 | v := MyCompositeRaw{4, ptrS("ABCDEFG")} 62 | 63 | b.ResetTimer() 64 | for n := 0; n < b.N; n++ { 65 | buf, _ = v.EncodeBinary(ci, buf[:0]) 66 | } 67 | x = buf 68 | } 69 | 70 | func BenchmarkBinaryEncodingHelper(b *testing.B) { 71 | buf := make([]byte, 0, 128) 72 | ci := pgtype.NewConnInfo() 73 | v := MyType{4, ptrS("ABCDEFG")} 74 | 75 | b.ResetTimer() 76 | for n := 0; n < b.N; n++ { 77 | buf, _ = v.EncodeBinary(ci, buf[:0]) 78 | } 79 | x = buf 80 | } 81 | 82 | func BenchmarkBinaryEncodingComposite(b *testing.B) { 83 | buf := make([]byte, 0, 128) 84 | ci := pgtype.NewConnInfo() 85 | f1 := 2 86 | f2 := ptrS("bar") 87 | c, err := pgtype.NewCompositeType("test", []pgtype.CompositeTypeField{ 88 | {"a", pgtype.Int4OID}, 89 | {"b", pgtype.TextOID}, 90 | }, ci) 91 | require.NoError(b, err) 92 | 93 | b.ResetTimer() 94 | for n := 0; n < b.N; n++ { 95 | c.Set([]interface{}{f1, f2}) 96 | buf, _ = c.EncodeBinary(ci, buf[:0]) 97 | } 98 | x = buf 99 | } 100 | 101 | func BenchmarkBinaryEncodingJSON(b *testing.B) { 102 | buf := make([]byte, 0, 128) 103 | ci := pgtype.NewConnInfo() 104 | v := MyCompositeRaw{4, ptrS("ABCDEFG")} 105 | j := pgtype.JSON{} 106 | 107 | b.ResetTimer() 108 | for n := 0; n < b.N; n++ { 109 | j.Set(v) 110 | buf, _ = j.EncodeBinary(ci, buf[:0]) 111 | } 112 | x = buf 113 | } 114 | 115 | var dstRaw MyCompositeRaw 116 | 117 | func BenchmarkBinaryDecodingManual(b *testing.B) { 118 | ci := pgtype.NewConnInfo() 119 | buf, _ := MyType{4, ptrS("ABCDEFG")}.EncodeBinary(ci, nil) 120 | dst := MyCompositeRaw{} 121 | 122 | b.ResetTimer() 123 | for n := 0; n < b.N; n++ { 124 | err := dst.DecodeBinary(ci, buf) 125 | E(err) 126 | } 127 | dstRaw = dst 128 | } 129 | 130 | var dstMyType MyType 131 | 132 | func BenchmarkBinaryDecodingHelpers(b *testing.B) { 133 | ci := pgtype.NewConnInfo() 134 | buf, _ := MyType{4, ptrS("ABCDEFG")}.EncodeBinary(ci, nil) 135 | dst := MyType{} 136 | 137 | b.ResetTimer() 138 | for n := 0; n < b.N; n++ { 139 | err := dst.DecodeBinary(ci, buf) 140 | E(err) 141 | } 142 | dstMyType = dst 143 | } 144 | 145 | var gf1 int 146 | var gf2 *string 147 | 148 | func BenchmarkBinaryDecodingCompositeScan(b *testing.B) { 149 | ci := pgtype.NewConnInfo() 150 | buf, _ := MyType{4, ptrS("ABCDEFG")}.EncodeBinary(ci, nil) 151 | var f1 int 152 | var f2 *string 153 | 154 | c, err := pgtype.NewCompositeType("test", []pgtype.CompositeTypeField{ 155 | {"a", pgtype.Int4OID}, 156 | {"b", pgtype.TextOID}, 157 | }, ci) 158 | require.NoError(b, err) 159 | 160 | b.ResetTimer() 161 | for n := 0; n < b.N; n++ { 162 | err := c.DecodeBinary(ci, buf) 163 | if err != nil { 164 | b.Fatal(err) 165 | } 166 | err = c.AssignTo([]interface{}{&f1, &f2}) 167 | if err != nil { 168 | b.Fatal(err) 169 | } 170 | } 171 | gf1 = f1 172 | gf2 = f2 173 | } 174 | 175 | func BenchmarkBinaryDecodingJSON(b *testing.B) { 176 | ci := pgtype.NewConnInfo() 177 | j := pgtype.JSON{} 178 | j.Set(MyCompositeRaw{4, ptrS("ABCDEFG")}) 179 | buf, _ := j.EncodeBinary(ci, nil) 180 | 181 | j = pgtype.JSON{} 182 | dst := MyCompositeRaw{} 183 | 184 | b.ResetTimer() 185 | for n := 0; n < b.N; n++ { 186 | err := j.DecodeBinary(ci, buf) 187 | E(err) 188 | err = j.AssignTo(&dst) 189 | E(err) 190 | } 191 | dstRaw = dst 192 | } 193 | -------------------------------------------------------------------------------- /composite_fields.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import "fmt" 4 | 5 | // CompositeFields scans the fields of a composite type into the elements of the CompositeFields value. To scan a 6 | // nullable value use a *CompositeFields. It will be set to nil in case of null. 7 | // 8 | // CompositeFields implements EncodeBinary and EncodeText. However, functionality is limited due to CompositeFields not 9 | // knowing the PostgreSQL schema of the composite type. Prefer using a registered CompositeType. 10 | type CompositeFields []interface{} 11 | 12 | func (cf CompositeFields) DecodeBinary(ci *ConnInfo, src []byte) error { 13 | if len(cf) == 0 { 14 | return fmt.Errorf("cannot decode into empty CompositeFields") 15 | } 16 | 17 | if src == nil { 18 | return fmt.Errorf("cannot decode unexpected null into CompositeFields") 19 | } 20 | 21 | scanner := NewCompositeBinaryScanner(ci, src) 22 | 23 | for _, f := range cf { 24 | scanner.ScanValue(f) 25 | } 26 | 27 | if scanner.Err() != nil { 28 | return scanner.Err() 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func (cf CompositeFields) DecodeText(ci *ConnInfo, src []byte) error { 35 | if len(cf) == 0 { 36 | return fmt.Errorf("cannot decode into empty CompositeFields") 37 | } 38 | 39 | if src == nil { 40 | return fmt.Errorf("cannot decode unexpected null into CompositeFields") 41 | } 42 | 43 | scanner := NewCompositeTextScanner(ci, src) 44 | 45 | for _, f := range cf { 46 | scanner.ScanValue(f) 47 | } 48 | 49 | if scanner.Err() != nil { 50 | return scanner.Err() 51 | } 52 | 53 | return nil 54 | } 55 | 56 | // EncodeText encodes composite fields into the text format. Prefer registering a CompositeType to using 57 | // CompositeFields to encode directly. 58 | func (cf CompositeFields) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 59 | b := NewCompositeTextBuilder(ci, buf) 60 | 61 | for _, f := range cf { 62 | if textEncoder, ok := f.(TextEncoder); ok { 63 | b.AppendEncoder(textEncoder) 64 | } else { 65 | b.AppendValue(f) 66 | } 67 | } 68 | 69 | return b.Finish() 70 | } 71 | 72 | // EncodeBinary encodes composite fields into the binary format. Unlike CompositeType the schema of the destination is 73 | // unknown. Prefer registering a CompositeType to using CompositeFields to encode directly. Because the binary 74 | // composite format requires the OID of each field to be specified the only types that will work are those known to 75 | // ConnInfo. 76 | // 77 | // In particular: 78 | // 79 | // * Nil cannot be used because there is no way to determine what type it. 80 | // * Integer types must be exact matches. e.g. A Go int32 into a PostgreSQL bigint will fail. 81 | // * No dereferencing will be done. e.g. *Text must be used instead of Text. 82 | func (cf CompositeFields) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 83 | b := NewCompositeBinaryBuilder(ci, buf) 84 | 85 | for _, f := range cf { 86 | dt, ok := ci.DataTypeForValue(f) 87 | if !ok { 88 | return nil, fmt.Errorf("Unknown OID for %#v", f) 89 | } 90 | 91 | if binaryEncoder, ok := f.(BinaryEncoder); ok { 92 | b.AppendEncoder(dt.OID, binaryEncoder) 93 | } else { 94 | err := dt.Value.Set(f) 95 | if err != nil { 96 | return nil, err 97 | } 98 | if binaryEncoder, ok := dt.Value.(BinaryEncoder); ok { 99 | b.AppendEncoder(dt.OID, binaryEncoder) 100 | } else { 101 | return nil, fmt.Errorf("Cannot encode binary format for %v", f) 102 | } 103 | } 104 | } 105 | 106 | return b.Finish() 107 | } 108 | -------------------------------------------------------------------------------- /custom_composite_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/jackc/pgtype" 10 | pgx "github.com/jackc/pgx/v4" 11 | ) 12 | 13 | type MyType struct { 14 | a int32 // NULL will cause decoding error 15 | b *string // there can be NULL in this position in SQL 16 | } 17 | 18 | func (dst *MyType) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error { 19 | if src == nil { 20 | return errors.New("NULL values can't be decoded. Scan into a &*MyType to handle NULLs") 21 | } 22 | 23 | if err := (pgtype.CompositeFields{&dst.a, &dst.b}).DecodeBinary(ci, src); err != nil { 24 | return err 25 | } 26 | 27 | return nil 28 | } 29 | 30 | func (src MyType) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) (newBuf []byte, err error) { 31 | a := pgtype.Int4{src.a, pgtype.Present} 32 | var b pgtype.Text 33 | if src.b != nil { 34 | b = pgtype.Text{*src.b, pgtype.Present} 35 | } else { 36 | b = pgtype.Text{Status: pgtype.Null} 37 | } 38 | 39 | return (pgtype.CompositeFields{&a, &b}).EncodeBinary(ci, buf) 40 | } 41 | 42 | func ptrS(s string) *string { 43 | return &s 44 | } 45 | 46 | func E(err error) { 47 | if err != nil { 48 | panic(err) 49 | } 50 | } 51 | 52 | // ExampleCustomCompositeTypes demonstrates how support for custom types mappable to SQL 53 | // composites can be added. 54 | func Example_customCompositeTypes() { 55 | conn, err := pgx.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE")) 56 | E(err) 57 | 58 | defer conn.Close(context.Background()) 59 | _, err = conn.Exec(context.Background(), `drop type if exists mytype; 60 | 61 | create type mytype as ( 62 | a int4, 63 | b text 64 | );`) 65 | E(err) 66 | defer conn.Exec(context.Background(), "drop type mytype") 67 | 68 | var result *MyType 69 | 70 | // Demonstrates both passing and reading back composite values 71 | err = conn.QueryRow(context.Background(), "select $1::mytype", 72 | pgx.QueryResultFormats{pgx.BinaryFormatCode}, MyType{1, ptrS("foo")}). 73 | Scan(&result) 74 | E(err) 75 | 76 | fmt.Printf("First row: a=%d b=%s\n", result.a, *result.b) 77 | 78 | // Because we scan into &*MyType, NULLs are handled generically by assigning nil to result 79 | err = conn.QueryRow(context.Background(), "select NULL::mytype", pgx.QueryResultFormats{pgx.BinaryFormatCode}).Scan(&result) 80 | E(err) 81 | 82 | fmt.Printf("Second row: %v\n", result) 83 | 84 | // Output: 85 | // First row: a=1 b=foo 86 | // Second row: 87 | } 88 | -------------------------------------------------------------------------------- /database_sql.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "errors" 6 | ) 7 | 8 | func DatabaseSQLValue(ci *ConnInfo, src Value) (interface{}, error) { 9 | if valuer, ok := src.(driver.Valuer); ok { 10 | return valuer.Value() 11 | } 12 | 13 | if textEncoder, ok := src.(TextEncoder); ok { 14 | buf, err := textEncoder.EncodeText(ci, nil) 15 | if err != nil { 16 | return nil, err 17 | } 18 | return string(buf), nil 19 | } 20 | 21 | if binaryEncoder, ok := src.(BinaryEncoder); ok { 22 | buf, err := binaryEncoder.EncodeBinary(ci, nil) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return buf, nil 27 | } 28 | 29 | return nil, errors.New("cannot convert to database/sql compatible value") 30 | } 31 | 32 | func EncodeValueText(src TextEncoder) (interface{}, error) { 33 | var encBuf [36]byte 34 | buf, err := src.EncodeText(nil, encBuf[:0]) 35 | if err != nil { 36 | return nil, err 37 | } 38 | if buf == nil { 39 | return nil, nil 40 | } 41 | return string(buf), err 42 | } 43 | -------------------------------------------------------------------------------- /enum_type.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import "fmt" 4 | 5 | // EnumType represents an enum type. While it implements Value, this is only in service of its type conversion duties 6 | // when registered as a data type in a ConnType. It should not be used directly as a Value. 7 | type EnumType struct { 8 | value string 9 | status Status 10 | 11 | typeName string // PostgreSQL type name 12 | members []string // enum members 13 | membersMap map[string]string // map to quickly lookup member and reuse string instead of allocating 14 | } 15 | 16 | // NewEnumType initializes a new EnumType. It retains a read-only reference to members. members must not be changed. 17 | func NewEnumType(typeName string, members []string) *EnumType { 18 | et := &EnumType{typeName: typeName, members: members} 19 | et.membersMap = make(map[string]string, len(members)) 20 | for _, m := range members { 21 | et.membersMap[m] = m 22 | } 23 | return et 24 | } 25 | 26 | func (et *EnumType) NewTypeValue() Value { 27 | return &EnumType{ 28 | value: et.value, 29 | status: et.status, 30 | 31 | typeName: et.typeName, 32 | members: et.members, 33 | membersMap: et.membersMap, 34 | } 35 | } 36 | 37 | func (et *EnumType) TypeName() string { 38 | return et.typeName 39 | } 40 | 41 | func (et *EnumType) Members() []string { 42 | return et.members 43 | } 44 | 45 | // Set assigns src to dst. Set purposely does not check that src is a member. This allows continued error free 46 | // operation in the event the PostgreSQL enum type is modified during a connection. 47 | func (dst *EnumType) Set(src interface{}) error { 48 | if src == nil { 49 | dst.status = Null 50 | return nil 51 | } 52 | 53 | if value, ok := src.(interface{ Get() interface{} }); ok { 54 | value2 := value.Get() 55 | if value2 != value { 56 | return dst.Set(value2) 57 | } 58 | } 59 | 60 | switch value := src.(type) { 61 | case string: 62 | dst.value = value 63 | dst.status = Present 64 | case *string: 65 | if value == nil { 66 | dst.status = Null 67 | } else { 68 | dst.value = *value 69 | dst.status = Present 70 | } 71 | case []byte: 72 | if value == nil { 73 | dst.status = Null 74 | } else { 75 | dst.value = string(value) 76 | dst.status = Present 77 | } 78 | default: 79 | if originalSrc, ok := underlyingStringType(src); ok { 80 | return dst.Set(originalSrc) 81 | } 82 | return fmt.Errorf("cannot convert %v to enum %s", value, dst.typeName) 83 | } 84 | 85 | return nil 86 | } 87 | 88 | func (dst EnumType) Get() interface{} { 89 | switch dst.status { 90 | case Present: 91 | return dst.value 92 | case Null: 93 | return nil 94 | default: 95 | return dst.status 96 | } 97 | } 98 | 99 | func (src *EnumType) AssignTo(dst interface{}) error { 100 | switch src.status { 101 | case Present: 102 | switch v := dst.(type) { 103 | case *string: 104 | *v = src.value 105 | return nil 106 | case *[]byte: 107 | *v = make([]byte, len(src.value)) 108 | copy(*v, src.value) 109 | return nil 110 | default: 111 | if nextDst, retry := GetAssignToDstType(dst); retry { 112 | return src.AssignTo(nextDst) 113 | } 114 | return fmt.Errorf("unable to assign to %T", dst) 115 | } 116 | case Null: 117 | return NullAssignTo(dst) 118 | } 119 | 120 | return fmt.Errorf("cannot decode %#v into %T", src, dst) 121 | } 122 | 123 | func (EnumType) PreferredResultFormat() int16 { 124 | return TextFormatCode 125 | } 126 | 127 | func (dst *EnumType) DecodeText(ci *ConnInfo, src []byte) error { 128 | if src == nil { 129 | dst.status = Null 130 | return nil 131 | } 132 | 133 | // Lookup the string in membersMap to avoid an allocation. 134 | if s, found := dst.membersMap[string(src)]; found { 135 | dst.value = s 136 | } else { 137 | // If an enum type is modified after the initial connection it is possible to receive an unexpected value. 138 | // Gracefully handle this situation. Purposely NOT modifying members and membersMap to allow for sharing members 139 | // and membersMap between connections. 140 | dst.value = string(src) 141 | } 142 | dst.status = Present 143 | 144 | return nil 145 | } 146 | 147 | func (dst *EnumType) DecodeBinary(ci *ConnInfo, src []byte) error { 148 | return dst.DecodeText(ci, src) 149 | } 150 | 151 | func (EnumType) PreferredParamFormat() int16 { 152 | return TextFormatCode 153 | } 154 | 155 | func (src EnumType) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 156 | switch src.status { 157 | case Null: 158 | return nil, nil 159 | case Undefined: 160 | return nil, errUndefined 161 | } 162 | 163 | return append(buf, src.value...), nil 164 | } 165 | 166 | func (src EnumType) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 167 | return src.EncodeText(ci, buf) 168 | } 169 | -------------------------------------------------------------------------------- /enum_type_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "testing" 7 | 8 | "github.com/jackc/pgtype" 9 | "github.com/jackc/pgtype/testutil" 10 | "github.com/jackc/pgx/v4" 11 | "github.com/stretchr/testify/assert" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func setupEnum(t *testing.T, conn *pgx.Conn) *pgtype.EnumType { 16 | _, err := conn.Exec(context.Background(), "drop type if exists pgtype_enum_color;") 17 | require.NoError(t, err) 18 | 19 | _, err = conn.Exec(context.Background(), "create type pgtype_enum_color as enum ('blue', 'green', 'purple');") 20 | require.NoError(t, err) 21 | 22 | var oid uint32 23 | err = conn.QueryRow(context.Background(), "select oid from pg_type where typname=$1;", "pgtype_enum_color").Scan(&oid) 24 | require.NoError(t, err) 25 | 26 | et := pgtype.NewEnumType("pgtype_enum_color", []string{"blue", "green", "purple"}) 27 | conn.ConnInfo().RegisterDataType(pgtype.DataType{Value: et, Name: "pgtype_enum_color", OID: oid}) 28 | 29 | return et 30 | } 31 | 32 | func cleanupEnum(t *testing.T, conn *pgx.Conn) { 33 | _, err := conn.Exec(context.Background(), "drop type if exists pgtype_enum_color;") 34 | require.NoError(t, err) 35 | } 36 | 37 | func TestEnumTypeTranscode(t *testing.T) { 38 | conn := testutil.MustConnectPgx(t) 39 | defer testutil.MustCloseContext(t, conn) 40 | 41 | setupEnum(t, conn) 42 | defer cleanupEnum(t, conn) 43 | 44 | var dst string 45 | err := conn.QueryRow(context.Background(), "select $1::pgtype_enum_color", "blue").Scan(&dst) 46 | require.NoError(t, err) 47 | require.EqualValues(t, "blue", dst) 48 | } 49 | 50 | func TestEnumTypeSet(t *testing.T) { 51 | conn := testutil.MustConnectPgx(t) 52 | defer testutil.MustCloseContext(t, conn) 53 | 54 | enumType := setupEnum(t, conn) 55 | defer cleanupEnum(t, conn) 56 | 57 | successfulTests := []struct { 58 | source interface{} 59 | result interface{} 60 | }{ 61 | {source: "blue", result: "blue"}, 62 | {source: _string("green"), result: "green"}, 63 | {source: (*string)(nil), result: nil}, 64 | } 65 | 66 | for i, tt := range successfulTests { 67 | err := enumType.Set(tt.source) 68 | assert.NoErrorf(t, err, "%d", i) 69 | assert.Equalf(t, tt.result, enumType.Get(), "%d", i) 70 | } 71 | } 72 | 73 | func TestEnumTypeAssignTo(t *testing.T) { 74 | conn := testutil.MustConnectPgx(t) 75 | defer testutil.MustCloseContext(t, conn) 76 | 77 | enumType := setupEnum(t, conn) 78 | defer cleanupEnum(t, conn) 79 | 80 | { 81 | var s string 82 | 83 | err := enumType.Set("blue") 84 | require.NoError(t, err) 85 | 86 | err = enumType.AssignTo(&s) 87 | require.NoError(t, err) 88 | 89 | assert.EqualValues(t, "blue", s) 90 | } 91 | 92 | { 93 | var ps *string 94 | 95 | err := enumType.Set("blue") 96 | require.NoError(t, err) 97 | 98 | err = enumType.AssignTo(&ps) 99 | require.NoError(t, err) 100 | 101 | assert.EqualValues(t, "blue", *ps) 102 | } 103 | 104 | { 105 | var ps *string 106 | 107 | err := enumType.Set(nil) 108 | require.NoError(t, err) 109 | 110 | err = enumType.AssignTo(&ps) 111 | require.NoError(t, err) 112 | 113 | assert.EqualValues(t, (*string)(nil), ps) 114 | } 115 | 116 | var buf []byte 117 | bytesTests := []struct { 118 | src interface{} 119 | dst *[]byte 120 | expected []byte 121 | }{ 122 | {src: "blue", dst: &buf, expected: []byte("blue")}, 123 | {src: nil, dst: &buf, expected: nil}, 124 | } 125 | 126 | for i, tt := range bytesTests { 127 | err := enumType.Set(tt.src) 128 | require.NoError(t, err, "%d", i) 129 | 130 | err = enumType.AssignTo(tt.dst) 131 | require.NoError(t, err, "%d", i) 132 | 133 | if !bytes.Equal(*tt.dst, tt.expected) { 134 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, tt.dst) 135 | } 136 | } 137 | 138 | { 139 | var s string 140 | 141 | err := enumType.Set(nil) 142 | require.NoError(t, err) 143 | 144 | err = enumType.AssignTo(&s) 145 | require.Error(t, err) 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /ext/gofrs-uuid/uuid_test.go: -------------------------------------------------------------------------------- 1 | package uuid_test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | gofrs "github.com/jackc/pgtype/ext/gofrs-uuid" 9 | "github.com/jackc/pgtype/testutil" 10 | ) 11 | 12 | func TestUUIDTranscode(t *testing.T) { 13 | testutil.TestSuccessfulTranscode(t, "uuid", []interface{}{ 14 | &gofrs.UUID{UUID: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Status: pgtype.Present}, 15 | &gofrs.UUID{Status: pgtype.Null}, 16 | }) 17 | } 18 | 19 | func TestUUIDSet(t *testing.T) { 20 | successfulTests := []struct { 21 | source interface{} 22 | result gofrs.UUID 23 | }{ 24 | { 25 | source: &gofrs.UUID{UUID: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Status: pgtype.Present}, 26 | result: gofrs.UUID{UUID: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Status: pgtype.Present}, 27 | }, 28 | { 29 | source: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, 30 | result: gofrs.UUID{UUID: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Status: pgtype.Present}, 31 | }, 32 | { 33 | source: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, 34 | result: gofrs.UUID{UUID: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Status: pgtype.Present}, 35 | }, 36 | { 37 | source: "00010203-0405-0607-0809-0a0b0c0d0e0f", 38 | result: gofrs.UUID{UUID: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Status: pgtype.Present}, 39 | }, 40 | } 41 | 42 | for i, tt := range successfulTests { 43 | var r gofrs.UUID 44 | err := r.Set(tt.source) 45 | if err != nil { 46 | t.Errorf("%d: %v", i, err) 47 | } 48 | 49 | if r != tt.result { 50 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) 51 | } 52 | } 53 | } 54 | 55 | func TestUUIDAssignTo(t *testing.T) { 56 | { 57 | src := gofrs.UUID{UUID: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Status: pgtype.Present} 58 | var dst [16]byte 59 | expected := [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} 60 | 61 | err := src.AssignTo(&dst) 62 | if err != nil { 63 | t.Error(err) 64 | } 65 | 66 | if dst != expected { 67 | t.Errorf("expected %v to assign %v, but result was %v", src, expected, dst) 68 | } 69 | } 70 | 71 | { 72 | src := gofrs.UUID{UUID: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Status: pgtype.Present} 73 | var dst []byte 74 | expected := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} 75 | 76 | err := src.AssignTo(&dst) 77 | if err != nil { 78 | t.Error(err) 79 | } 80 | 81 | if !bytes.Equal(dst, expected) { 82 | t.Errorf("expected %v to assign %v, but result was %v", src, expected, dst) 83 | } 84 | } 85 | 86 | { 87 | src := gofrs.UUID{UUID: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Status: pgtype.Present} 88 | var dst string 89 | expected := "00010203-0405-0607-0809-0a0b0c0d0e0f" 90 | 91 | err := src.AssignTo(&dst) 92 | if err != nil { 93 | t.Error(err) 94 | } 95 | 96 | if dst != expected { 97 | t.Errorf("expected %v to assign %v, but result was %v", src, expected, dst) 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /generic_binary.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | ) 6 | 7 | // GenericBinary is a placeholder for binary format values that no other type exists 8 | // to handle. 9 | type GenericBinary Bytea 10 | 11 | func (dst *GenericBinary) Set(src interface{}) error { 12 | return (*Bytea)(dst).Set(src) 13 | } 14 | 15 | func (dst GenericBinary) Get() interface{} { 16 | return (Bytea)(dst).Get() 17 | } 18 | 19 | func (src *GenericBinary) AssignTo(dst interface{}) error { 20 | return (*Bytea)(src).AssignTo(dst) 21 | } 22 | 23 | func (dst *GenericBinary) DecodeBinary(ci *ConnInfo, src []byte) error { 24 | return (*Bytea)(dst).DecodeBinary(ci, src) 25 | } 26 | 27 | func (src GenericBinary) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 28 | return (Bytea)(src).EncodeBinary(ci, buf) 29 | } 30 | 31 | // Scan implements the database/sql Scanner interface. 32 | func (dst *GenericBinary) Scan(src interface{}) error { 33 | return (*Bytea)(dst).Scan(src) 34 | } 35 | 36 | // Value implements the database/sql/driver Valuer interface. 37 | func (src GenericBinary) Value() (driver.Value, error) { 38 | return (Bytea)(src).Value() 39 | } 40 | -------------------------------------------------------------------------------- /generic_text.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | ) 6 | 7 | // GenericText is a placeholder for text format values that no other type exists 8 | // to handle. 9 | type GenericText Text 10 | 11 | func (dst *GenericText) Set(src interface{}) error { 12 | return (*Text)(dst).Set(src) 13 | } 14 | 15 | func (dst GenericText) Get() interface{} { 16 | return (Text)(dst).Get() 17 | } 18 | 19 | func (src *GenericText) AssignTo(dst interface{}) error { 20 | return (*Text)(src).AssignTo(dst) 21 | } 22 | 23 | func (dst *GenericText) DecodeText(ci *ConnInfo, src []byte) error { 24 | return (*Text)(dst).DecodeText(ci, src) 25 | } 26 | 27 | func (src GenericText) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 28 | return (Text)(src).EncodeText(ci, buf) 29 | } 30 | 31 | // Scan implements the database/sql Scanner interface. 32 | func (dst *GenericText) Scan(src interface{}) error { 33 | return (*Text)(dst).Scan(src) 34 | } 35 | 36 | // Value implements the database/sql/driver Valuer interface. 37 | func (src GenericText) Value() (driver.Value, error) { 38 | return (Text)(src).Value() 39 | } 40 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jackc/pgtype 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/gofrs/uuid v4.0.0+incompatible 7 | github.com/jackc/pgconn v1.14.3 8 | github.com/jackc/pgio v1.0.0 9 | github.com/jackc/pgx/v4 v4.18.2 10 | github.com/lib/pq v1.10.2 11 | github.com/shopspring/decimal v1.2.0 12 | github.com/stretchr/testify v1.8.1 13 | ) 14 | -------------------------------------------------------------------------------- /int4_multirange_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype" 7 | "github.com/jackc/pgtype/testutil" 8 | ) 9 | 10 | func TestInt4multirangeTranscode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "int4multirange", []interface{}{ 12 | &pgtype.Int4multirange{ 13 | Ranges: nil, 14 | Status: pgtype.Present, 15 | }, 16 | &pgtype.Int4multirange{ 17 | Ranges: []pgtype.Int4range{ 18 | { 19 | Lower: pgtype.Int4{Int: -543, Status: pgtype.Present}, 20 | Upper: pgtype.Int4{Int: 342, Status: pgtype.Present}, 21 | LowerType: pgtype.Inclusive, 22 | UpperType: pgtype.Exclusive, 23 | Status: pgtype.Present, 24 | }, 25 | }, 26 | Status: pgtype.Present, 27 | }, 28 | &pgtype.Int4multirange{ 29 | Ranges: []pgtype.Int4range{ 30 | { 31 | Lower: pgtype.Int4{Int: -42, Status: pgtype.Present}, 32 | Upper: pgtype.Int4{Int: -5, Status: pgtype.Present}, 33 | LowerType: pgtype.Inclusive, 34 | UpperType: pgtype.Exclusive, 35 | Status: pgtype.Present, 36 | }, 37 | { 38 | Lower: pgtype.Int4{Int: 5, Status: pgtype.Present}, 39 | Upper: pgtype.Int4{Int: 42, Status: pgtype.Present}, 40 | LowerType: pgtype.Inclusive, 41 | UpperType: pgtype.Exclusive, 42 | Status: pgtype.Present, 43 | }, 44 | { 45 | Lower: pgtype.Int4{Int: 52, Status: pgtype.Present}, 46 | LowerType: pgtype.Inclusive, 47 | UpperType: pgtype.Unbounded, 48 | Status: pgtype.Present, 49 | }, 50 | }, 51 | Status: pgtype.Present, 52 | }, 53 | }) 54 | } 55 | 56 | func TestInt4multirangeNormalize(t *testing.T) { 57 | testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{ 58 | { 59 | SQL: "select int4multirange(int4range(1, 14, '(]'), int4range(20, 25, '()'))", 60 | Value: pgtype.Int4multirange{ 61 | Ranges: []pgtype.Int4range{ 62 | { 63 | Lower: pgtype.Int4{Int: 2, Status: pgtype.Present}, 64 | Upper: pgtype.Int4{Int: 15, Status: pgtype.Present}, 65 | LowerType: pgtype.Inclusive, 66 | UpperType: pgtype.Exclusive, 67 | Status: pgtype.Present, 68 | }, 69 | { 70 | Lower: pgtype.Int4{Int: 21, Status: pgtype.Present}, 71 | Upper: pgtype.Int4{Int: 25, Status: pgtype.Present}, 72 | LowerType: pgtype.Inclusive, 73 | UpperType: pgtype.Exclusive, 74 | Status: pgtype.Present, 75 | }, 76 | }, 77 | Status: pgtype.Present, 78 | }, 79 | }, 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /int4range_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype" 7 | "github.com/jackc/pgtype/testutil" 8 | ) 9 | 10 | func TestInt4rangeTranscode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "int4range", []interface{}{ 12 | &pgtype.Int4range{LowerType: pgtype.Empty, UpperType: pgtype.Empty, Status: pgtype.Present}, 13 | &pgtype.Int4range{Lower: pgtype.Int4{Int: 1, Status: pgtype.Present}, Upper: pgtype.Int4{Int: 10, Status: pgtype.Present}, LowerType: pgtype.Inclusive, UpperType: pgtype.Exclusive, Status: pgtype.Present}, 14 | &pgtype.Int4range{Lower: pgtype.Int4{Int: -42, Status: pgtype.Present}, Upper: pgtype.Int4{Int: -5, Status: pgtype.Present}, LowerType: pgtype.Inclusive, UpperType: pgtype.Exclusive, Status: pgtype.Present}, 15 | &pgtype.Int4range{Lower: pgtype.Int4{Int: 1, Status: pgtype.Present}, LowerType: pgtype.Inclusive, UpperType: pgtype.Unbounded, Status: pgtype.Present}, 16 | &pgtype.Int4range{Upper: pgtype.Int4{Int: 1, Status: pgtype.Present}, LowerType: pgtype.Unbounded, UpperType: pgtype.Exclusive, Status: pgtype.Present}, 17 | &pgtype.Int4range{Status: pgtype.Null}, 18 | }) 19 | } 20 | 21 | func TestInt4rangeNormalize(t *testing.T) { 22 | testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{ 23 | { 24 | SQL: "select int4range(1, 10, '(]')", 25 | Value: pgtype.Int4range{Lower: pgtype.Int4{Int: 2, Status: pgtype.Present}, Upper: pgtype.Int4{Int: 11, Status: pgtype.Present}, LowerType: pgtype.Inclusive, UpperType: pgtype.Exclusive, Status: pgtype.Present}, 26 | }, 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /int8_multirange_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype" 7 | "github.com/jackc/pgtype/testutil" 8 | ) 9 | 10 | func TestInt8multirangeTranscode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "int8multirange", []interface{}{ 12 | &pgtype.Int8multirange{ 13 | Ranges: nil, 14 | Status: pgtype.Present, 15 | }, 16 | &pgtype.Int8multirange{ 17 | Ranges: []pgtype.Int8range{ 18 | { 19 | Lower: pgtype.Int8{Int: -543, Status: pgtype.Present}, 20 | Upper: pgtype.Int8{Int: 342, Status: pgtype.Present}, 21 | LowerType: pgtype.Inclusive, 22 | UpperType: pgtype.Exclusive, 23 | Status: pgtype.Present, 24 | }, 25 | }, 26 | Status: pgtype.Present, 27 | }, 28 | &pgtype.Int8multirange{ 29 | Ranges: []pgtype.Int8range{ 30 | { 31 | Lower: pgtype.Int8{Int: -42, Status: pgtype.Present}, 32 | Upper: pgtype.Int8{Int: -5, Status: pgtype.Present}, 33 | LowerType: pgtype.Inclusive, 34 | UpperType: pgtype.Exclusive, 35 | Status: pgtype.Present, 36 | }, 37 | { 38 | Lower: pgtype.Int8{Int: 5, Status: pgtype.Present}, 39 | Upper: pgtype.Int8{Int: 42, Status: pgtype.Present}, 40 | LowerType: pgtype.Inclusive, 41 | UpperType: pgtype.Exclusive, 42 | Status: pgtype.Present, 43 | }, 44 | { 45 | Lower: pgtype.Int8{Int: 52, Status: pgtype.Present}, 46 | LowerType: pgtype.Inclusive, 47 | UpperType: pgtype.Unbounded, 48 | Status: pgtype.Present, 49 | }, 50 | }, 51 | Status: pgtype.Present, 52 | }, 53 | }) 54 | } 55 | 56 | func TestInt8multirangeNormalize(t *testing.T) { 57 | testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{ 58 | { 59 | SQL: "select int8multirange(int8range(1, 14, '(]'), int8range(20, 25, '()'))", 60 | Value: pgtype.Int8multirange{ 61 | Ranges: []pgtype.Int8range{ 62 | { 63 | Lower: pgtype.Int8{Int: 2, Status: pgtype.Present}, 64 | Upper: pgtype.Int8{Int: 15, Status: pgtype.Present}, 65 | LowerType: pgtype.Inclusive, 66 | UpperType: pgtype.Exclusive, 67 | Status: pgtype.Present, 68 | }, 69 | { 70 | Lower: pgtype.Int8{Int: 21, Status: pgtype.Present}, 71 | Upper: pgtype.Int8{Int: 25, Status: pgtype.Present}, 72 | LowerType: pgtype.Inclusive, 73 | UpperType: pgtype.Exclusive, 74 | Status: pgtype.Present, 75 | }, 76 | }, 77 | Status: pgtype.Present, 78 | }, 79 | }, 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /int8range_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype" 7 | "github.com/jackc/pgtype/testutil" 8 | ) 9 | 10 | func TestInt8rangeTranscode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "Int8range", []interface{}{ 12 | &pgtype.Int8range{LowerType: pgtype.Empty, UpperType: pgtype.Empty, Status: pgtype.Present}, 13 | &pgtype.Int8range{Lower: pgtype.Int8{Int: 1, Status: pgtype.Present}, Upper: pgtype.Int8{Int: 10, Status: pgtype.Present}, LowerType: pgtype.Inclusive, UpperType: pgtype.Exclusive, Status: pgtype.Present}, 14 | &pgtype.Int8range{Lower: pgtype.Int8{Int: -42, Status: pgtype.Present}, Upper: pgtype.Int8{Int: -5, Status: pgtype.Present}, LowerType: pgtype.Inclusive, UpperType: pgtype.Exclusive, Status: pgtype.Present}, 15 | &pgtype.Int8range{Lower: pgtype.Int8{Int: 1, Status: pgtype.Present}, LowerType: pgtype.Inclusive, UpperType: pgtype.Unbounded, Status: pgtype.Present}, 16 | &pgtype.Int8range{Upper: pgtype.Int8{Int: 1, Status: pgtype.Present}, LowerType: pgtype.Unbounded, UpperType: pgtype.Exclusive, Status: pgtype.Present}, 17 | &pgtype.Int8range{Status: pgtype.Null}, 18 | }) 19 | } 20 | 21 | func TestInt8rangeNormalize(t *testing.T) { 22 | testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{ 23 | { 24 | SQL: "select Int8range(1, 10, '(]')", 25 | Value: pgtype.Int8range{Lower: pgtype.Int8{Int: 2, Status: pgtype.Present}, Upper: pgtype.Int8{Int: 11, Status: pgtype.Present}, LowerType: pgtype.Inclusive, UpperType: pgtype.Exclusive, Status: pgtype.Present}, 26 | }, 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /interval_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestIntervalTranscode(t *testing.T) { 14 | testutil.TestSuccessfulTranscode(t, "interval", []interface{}{ 15 | &pgtype.Interval{Microseconds: 1, Status: pgtype.Present}, 16 | &pgtype.Interval{Microseconds: 1000000, Status: pgtype.Present}, 17 | &pgtype.Interval{Microseconds: 1000001, Status: pgtype.Present}, 18 | &pgtype.Interval{Microseconds: 123202800000000, Status: pgtype.Present}, 19 | &pgtype.Interval{Days: 1, Status: pgtype.Present}, 20 | &pgtype.Interval{Months: 1, Status: pgtype.Present}, 21 | &pgtype.Interval{Months: 12, Status: pgtype.Present}, 22 | &pgtype.Interval{Months: 13, Days: 15, Microseconds: 1000001, Status: pgtype.Present}, 23 | &pgtype.Interval{Microseconds: -1, Status: pgtype.Present}, 24 | &pgtype.Interval{Microseconds: -1000000, Status: pgtype.Present}, 25 | &pgtype.Interval{Microseconds: -1000001, Status: pgtype.Present}, 26 | &pgtype.Interval{Microseconds: -123202800000000, Status: pgtype.Present}, 27 | &pgtype.Interval{Days: -1, Status: pgtype.Present}, 28 | &pgtype.Interval{Months: -1, Status: pgtype.Present}, 29 | &pgtype.Interval{Months: -12, Status: pgtype.Present}, 30 | &pgtype.Interval{Months: -13, Days: -15, Microseconds: -1000001, Status: pgtype.Present}, 31 | &pgtype.Interval{Status: pgtype.Null}, 32 | }) 33 | } 34 | 35 | func TestIntervalNormalize(t *testing.T) { 36 | testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{ 37 | { 38 | SQL: "select '1 second'::interval", 39 | Value: &pgtype.Interval{Microseconds: 1000000, Status: pgtype.Present}, 40 | }, 41 | { 42 | SQL: "select '1.000001 second'::interval", 43 | Value: &pgtype.Interval{Microseconds: 1000001, Status: pgtype.Present}, 44 | }, 45 | { 46 | SQL: "select '34223 hours'::interval", 47 | Value: &pgtype.Interval{Microseconds: 123202800000000, Status: pgtype.Present}, 48 | }, 49 | { 50 | SQL: "select '1 day'::interval", 51 | Value: &pgtype.Interval{Days: 1, Status: pgtype.Present}, 52 | }, 53 | { 54 | SQL: "select '1 month'::interval", 55 | Value: &pgtype.Interval{Months: 1, Status: pgtype.Present}, 56 | }, 57 | { 58 | SQL: "select '1 year'::interval", 59 | Value: &pgtype.Interval{Months: 12, Status: pgtype.Present}, 60 | }, 61 | { 62 | SQL: "select '-13 mon'::interval", 63 | Value: &pgtype.Interval{Months: -13, Status: pgtype.Present}, 64 | }, 65 | }) 66 | } 67 | 68 | func TestIntervalLossyConversionToDuration(t *testing.T) { 69 | interval := &pgtype.Interval{Months: 1, Days: 1, Status: pgtype.Present} 70 | var d time.Duration 71 | err := interval.AssignTo(&d) 72 | require.NoError(t, err) 73 | assert.EqualValues(t, int64(2678400000000000), d.Nanoseconds()) 74 | } 75 | -------------------------------------------------------------------------------- /json_array_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/jackc/pgtype" 9 | "github.com/jackc/pgtype/testutil" 10 | ) 11 | 12 | func TestJSONArrayTranscode(t *testing.T) { 13 | testutil.TestSuccessfulTranscode(t, "json[]", []interface{}{ 14 | &pgtype.JSONArray{ 15 | Elements: nil, 16 | Dimensions: nil, 17 | Status: pgtype.Present, 18 | }, 19 | &pgtype.JSONArray{ 20 | Elements: []pgtype.JSON{ 21 | {Bytes: []byte(`"foo"`), Status: pgtype.Present}, 22 | {Status: pgtype.Null}, 23 | }, 24 | Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}}, 25 | Status: pgtype.Present, 26 | }, 27 | &pgtype.JSONArray{Status: pgtype.Null}, 28 | &pgtype.JSONArray{ 29 | Elements: []pgtype.JSON{ 30 | {Bytes: []byte(`"foo"`), Status: pgtype.Present}, 31 | {Bytes: []byte("null"), Status: pgtype.Present}, 32 | {Bytes: []byte("42"), Status: pgtype.Present}, 33 | }, 34 | Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}}, 35 | Status: pgtype.Present, 36 | }, 37 | }) 38 | } 39 | 40 | func TestJSONArraySet(t *testing.T) { 41 | successfulTests := []struct { 42 | source interface{} 43 | result pgtype.JSONArray 44 | }{ 45 | {source: []string{"{}"}, result: pgtype.JSONArray{ 46 | Elements: []pgtype.JSON{{Bytes: []byte("{}"), Status: pgtype.Present}}, 47 | Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}}, 48 | Status: pgtype.Present, 49 | }}, 50 | {source: [][]byte{[]byte("{}")}, result: pgtype.JSONArray{ 51 | Elements: []pgtype.JSON{{Bytes: []byte("{}"), Status: pgtype.Present}}, 52 | Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}}, 53 | Status: pgtype.Present, 54 | }}, 55 | {source: [][]byte{[]byte(`{"foo":1}`), []byte(`{"bar":2}`)}, result: pgtype.JSONArray{ 56 | Elements: []pgtype.JSON{{Bytes: []byte(`{"foo":1}`), Status: pgtype.Present}, {Bytes: []byte(`{"bar":2}`), Status: pgtype.Present}}, 57 | Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}}, 58 | Status: pgtype.Present, 59 | }}, 60 | {source: []json.RawMessage{json.RawMessage(`{"foo":1}`), json.RawMessage(`{"bar":2}`)}, result: pgtype.JSONArray{ 61 | Elements: []pgtype.JSON{{Bytes: []byte(`{"foo":1}`), Status: pgtype.Present}, {Bytes: []byte(`{"bar":2}`), Status: pgtype.Present}}, 62 | Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}}, 63 | Status: pgtype.Present, 64 | }}, 65 | {source: []json.RawMessage{json.RawMessage(`{"foo":12}`), json.RawMessage(`{"bar":2}`)}, result: pgtype.JSONArray{ 66 | Elements: []pgtype.JSON{{Bytes: []byte(`{"foo":12}`), Status: pgtype.Present}, {Bytes: []byte(`{"bar":2}`), Status: pgtype.Present}}, 67 | Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}}, 68 | Status: pgtype.Present, 69 | }}, 70 | {source: []json.RawMessage{json.RawMessage(`{"foo":1}`), json.RawMessage(`{"bar":{"x":2}}`)}, result: pgtype.JSONArray{ 71 | Elements: []pgtype.JSON{{Bytes: []byte(`{"foo":1}`), Status: pgtype.Present}, {Bytes: []byte(`{"bar":{"x":2}}`), Status: pgtype.Present}}, 72 | Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}}, 73 | Status: pgtype.Present, 74 | }}, 75 | } 76 | 77 | for i, tt := range successfulTests { 78 | var d pgtype.JSONArray 79 | err := d.Set(tt.source) 80 | if err != nil { 81 | t.Errorf("%d: %v", i, err) 82 | } 83 | 84 | if !reflect.DeepEqual(d, tt.result) { 85 | t.Errorf("%d: expected %+v to convert to %+v, but it was %+v", i, tt.source, tt.result, d) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /jsonb.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | ) 7 | 8 | type JSONB JSON 9 | 10 | func (dst *JSONB) Set(src interface{}) error { 11 | return (*JSON)(dst).Set(src) 12 | } 13 | 14 | func (dst JSONB) Get() interface{} { 15 | return (JSON)(dst).Get() 16 | } 17 | 18 | func (src *JSONB) AssignTo(dst interface{}) error { 19 | return (*JSON)(src).AssignTo(dst) 20 | } 21 | 22 | func (JSONB) PreferredResultFormat() int16 { 23 | return TextFormatCode 24 | } 25 | 26 | func (dst *JSONB) DecodeText(ci *ConnInfo, src []byte) error { 27 | return (*JSON)(dst).DecodeText(ci, src) 28 | } 29 | 30 | func (dst *JSONB) DecodeBinary(ci *ConnInfo, src []byte) error { 31 | if src == nil { 32 | *dst = JSONB{Status: Null} 33 | return nil 34 | } 35 | 36 | if len(src) == 0 { 37 | return fmt.Errorf("jsonb too short") 38 | } 39 | 40 | if src[0] != 1 { 41 | return fmt.Errorf("unknown jsonb version number %d", src[0]) 42 | } 43 | 44 | *dst = JSONB{Bytes: src[1:], Status: Present} 45 | return nil 46 | 47 | } 48 | 49 | func (JSONB) PreferredParamFormat() int16 { 50 | return TextFormatCode 51 | } 52 | 53 | func (src JSONB) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 54 | return (JSON)(src).EncodeText(ci, buf) 55 | } 56 | 57 | func (src JSONB) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 58 | switch src.Status { 59 | case Null: 60 | return nil, nil 61 | case Undefined: 62 | return nil, errUndefined 63 | } 64 | 65 | buf = append(buf, 1) 66 | return append(buf, src.Bytes...), nil 67 | } 68 | 69 | // Scan implements the database/sql Scanner interface. 70 | func (dst *JSONB) Scan(src interface{}) error { 71 | return (*JSON)(dst).Scan(src) 72 | } 73 | 74 | // Value implements the database/sql/driver Valuer interface. 75 | func (src JSONB) Value() (driver.Value, error) { 76 | return (JSON)(src).Value() 77 | } 78 | 79 | func (src JSONB) MarshalJSON() ([]byte, error) { 80 | return (JSON)(src).MarshalJSON() 81 | } 82 | 83 | func (dst *JSONB) UnmarshalJSON(b []byte) error { 84 | return (*JSON)(dst).UnmarshalJSON(b) 85 | } 86 | -------------------------------------------------------------------------------- /jsonb_array_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/jackc/pgtype" 9 | "github.com/jackc/pgtype/testutil" 10 | ) 11 | 12 | func TestJSONBArrayTranscode(t *testing.T) { 13 | testutil.TestSuccessfulTranscode(t, "jsonb[]", []interface{}{ 14 | &pgtype.JSONBArray{ 15 | Elements: nil, 16 | Dimensions: nil, 17 | Status: pgtype.Present, 18 | }, 19 | &pgtype.JSONBArray{ 20 | Elements: []pgtype.JSONB{ 21 | {Bytes: []byte(`"foo"`), Status: pgtype.Present}, 22 | {Status: pgtype.Null}, 23 | }, 24 | Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}}, 25 | Status: pgtype.Present, 26 | }, 27 | &pgtype.JSONBArray{Status: pgtype.Null}, 28 | &pgtype.JSONBArray{ 29 | Elements: []pgtype.JSONB{ 30 | {Bytes: []byte(`"foo"`), Status: pgtype.Present}, 31 | {Bytes: []byte("null"), Status: pgtype.Present}, 32 | {Bytes: []byte("42"), Status: pgtype.Present}, 33 | }, 34 | Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}}, 35 | Status: pgtype.Present, 36 | }, 37 | }) 38 | } 39 | 40 | func TestJSONBArraySet(t *testing.T) { 41 | successfulTests := []struct { 42 | source interface{} 43 | result pgtype.JSONBArray 44 | }{ 45 | {source: []string{"{}"}, result: pgtype.JSONBArray{ 46 | Elements: []pgtype.JSONB{pgtype.JSONB{Bytes: []byte("{}"), Status: pgtype.Present}}, 47 | Dimensions: []pgtype.ArrayDimension{pgtype.ArrayDimension{Length: 1, LowerBound: 1}}, 48 | Status: pgtype.Present, 49 | }}, 50 | {source: [][]byte{[]byte("{}")}, result: pgtype.JSONBArray{ 51 | Elements: []pgtype.JSONB{pgtype.JSONB{Bytes: []byte("{}"), Status: pgtype.Present}}, 52 | Dimensions: []pgtype.ArrayDimension{pgtype.ArrayDimension{Length: 1, LowerBound: 1}}, 53 | Status: pgtype.Present, 54 | }}, 55 | {source: [][]byte{[]byte(`{"foo":1}`), []byte(`{"bar":2}`)}, result: pgtype.JSONBArray{ 56 | Elements: []pgtype.JSONB{pgtype.JSONB{Bytes: []byte(`{"foo":1}`), Status: pgtype.Present}, pgtype.JSONB{Bytes: []byte(`{"bar":2}`), Status: pgtype.Present}}, 57 | Dimensions: []pgtype.ArrayDimension{pgtype.ArrayDimension{Length: 2, LowerBound: 1}}, 58 | Status: pgtype.Present, 59 | }}, 60 | {source: []json.RawMessage{json.RawMessage(`{"foo":1}`), json.RawMessage(`{"bar":2}`)}, result: pgtype.JSONBArray{ 61 | Elements: []pgtype.JSONB{pgtype.JSONB{Bytes: []byte(`{"foo":1}`), Status: pgtype.Present}, pgtype.JSONB{Bytes: []byte(`{"bar":2}`), Status: pgtype.Present}}, 62 | Dimensions: []pgtype.ArrayDimension{pgtype.ArrayDimension{Length: 2, LowerBound: 1}}, 63 | Status: pgtype.Present, 64 | }}, 65 | {source: []json.RawMessage{json.RawMessage(`{"foo":12}`), json.RawMessage(`{"bar":2}`)}, result: pgtype.JSONBArray{ 66 | Elements: []pgtype.JSONB{pgtype.JSONB{Bytes: []byte(`{"foo":12}`), Status: pgtype.Present}, pgtype.JSONB{Bytes: []byte(`{"bar":2}`), Status: pgtype.Present}}, 67 | Dimensions: []pgtype.ArrayDimension{pgtype.ArrayDimension{Length: 2, LowerBound: 1}}, 68 | Status: pgtype.Present, 69 | }}, 70 | {source: []json.RawMessage{json.RawMessage(`{"foo":1}`), json.RawMessage(`{"bar":{"x":2}}`)}, result: pgtype.JSONBArray{ 71 | Elements: []pgtype.JSONB{pgtype.JSONB{Bytes: []byte(`{"foo":1}`), Status: pgtype.Present}, pgtype.JSONB{Bytes: []byte(`{"bar":{"x":2}}`), Status: pgtype.Present}}, 72 | Dimensions: []pgtype.ArrayDimension{pgtype.ArrayDimension{Length: 2, LowerBound: 1}}, 73 | Status: pgtype.Present, 74 | }}, 75 | } 76 | 77 | for i, tt := range successfulTests { 78 | var d pgtype.JSONBArray 79 | err := d.Set(tt.source) 80 | if err != nil { 81 | t.Errorf("%d: %v", i, err) 82 | } 83 | 84 | if !reflect.DeepEqual(d, tt.result) { 85 | t.Errorf("%d: expected %+v to convert to %+v, but it was %+v", i, tt.source, tt.result, d) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /jsonb_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/jackc/pgtype" 9 | "github.com/jackc/pgtype/testutil" 10 | ) 11 | 12 | func TestJSONBTranscode(t *testing.T) { 13 | conn := testutil.MustConnectPgx(t) 14 | defer testutil.MustCloseContext(t, conn) 15 | if _, ok := conn.ConnInfo().DataTypeForName("jsonb"); !ok { 16 | t.Skip("Skipping due to no jsonb type") 17 | } 18 | 19 | testutil.TestSuccessfulTranscode(t, "jsonb", []interface{}{ 20 | &pgtype.JSONB{Bytes: []byte("{}"), Status: pgtype.Present}, 21 | &pgtype.JSONB{Bytes: []byte("null"), Status: pgtype.Present}, 22 | &pgtype.JSONB{Bytes: []byte("42"), Status: pgtype.Present}, 23 | &pgtype.JSONB{Bytes: []byte(`"hello"`), Status: pgtype.Present}, 24 | &pgtype.JSONB{Status: pgtype.Null}, 25 | }) 26 | } 27 | 28 | func TestJSONBSet(t *testing.T) { 29 | successfulTests := []struct { 30 | source interface{} 31 | result pgtype.JSONB 32 | }{ 33 | {source: "{}", result: pgtype.JSONB{Bytes: []byte("{}"), Status: pgtype.Present}}, 34 | {source: []byte("{}"), result: pgtype.JSONB{Bytes: []byte("{}"), Status: pgtype.Present}}, 35 | {source: ([]byte)(nil), result: pgtype.JSONB{Status: pgtype.Null}}, 36 | {source: (*string)(nil), result: pgtype.JSONB{Status: pgtype.Null}}, 37 | {source: []int{1, 2, 3}, result: pgtype.JSONB{Bytes: []byte("[1,2,3]"), Status: pgtype.Present}}, 38 | {source: map[string]interface{}{"foo": "bar"}, result: pgtype.JSONB{Bytes: []byte(`{"foo":"bar"}`), Status: pgtype.Present}}, 39 | } 40 | 41 | for i, tt := range successfulTests { 42 | var d pgtype.JSONB 43 | err := d.Set(tt.source) 44 | if err != nil { 45 | t.Errorf("%d: %v", i, err) 46 | } 47 | 48 | if !reflect.DeepEqual(d, tt.result) { 49 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, d) 50 | } 51 | } 52 | } 53 | 54 | func TestJSONBAssignTo(t *testing.T) { 55 | var s string 56 | var ps *string 57 | var b []byte 58 | 59 | rawStringTests := []struct { 60 | src pgtype.JSONB 61 | dst *string 62 | expected string 63 | }{ 64 | {src: pgtype.JSONB{Bytes: []byte("{}"), Status: pgtype.Present}, dst: &s, expected: "{}"}, 65 | } 66 | 67 | for i, tt := range rawStringTests { 68 | err := tt.src.AssignTo(tt.dst) 69 | if err != nil { 70 | t.Errorf("%d: %v", i, err) 71 | } 72 | 73 | if *tt.dst != tt.expected { 74 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, *tt.dst) 75 | } 76 | } 77 | 78 | rawBytesTests := []struct { 79 | src pgtype.JSONB 80 | dst *[]byte 81 | expected []byte 82 | }{ 83 | {src: pgtype.JSONB{Bytes: []byte("{}"), Status: pgtype.Present}, dst: &b, expected: []byte("{}")}, 84 | {src: pgtype.JSONB{Status: pgtype.Null}, dst: &b, expected: (([]byte)(nil))}, 85 | } 86 | 87 | for i, tt := range rawBytesTests { 88 | err := tt.src.AssignTo(tt.dst) 89 | if err != nil { 90 | t.Errorf("%d: %v", i, err) 91 | } 92 | 93 | if !bytes.Equal(tt.expected, *tt.dst) { 94 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, *tt.dst) 95 | } 96 | } 97 | 98 | var mapDst map[string]interface{} 99 | type structDst struct { 100 | Name string `json:"name"` 101 | Age int `json:"age"` 102 | } 103 | var strDst structDst 104 | 105 | unmarshalTests := []struct { 106 | src pgtype.JSONB 107 | dst interface{} 108 | expected interface{} 109 | }{ 110 | {src: pgtype.JSONB{Bytes: []byte(`{"foo":"bar"}`), Status: pgtype.Present}, dst: &mapDst, expected: map[string]interface{}{"foo": "bar"}}, 111 | {src: pgtype.JSONB{Bytes: []byte(`{"name":"John","age":42}`), Status: pgtype.Present}, dst: &strDst, expected: structDst{Name: "John", Age: 42}}, 112 | } 113 | for i, tt := range unmarshalTests { 114 | err := tt.src.AssignTo(tt.dst) 115 | if err != nil { 116 | t.Errorf("%d: %v", i, err) 117 | } 118 | 119 | if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) { 120 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 121 | } 122 | } 123 | 124 | pointerAllocTests := []struct { 125 | src pgtype.JSONB 126 | dst **string 127 | expected *string 128 | }{ 129 | {src: pgtype.JSONB{Status: pgtype.Null}, dst: &ps, expected: ((*string)(nil))}, 130 | } 131 | 132 | for i, tt := range pointerAllocTests { 133 | err := tt.src.AssignTo(tt.dst) 134 | if err != nil { 135 | t.Errorf("%d: %v", i, err) 136 | } 137 | 138 | if *tt.dst != tt.expected { 139 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, *tt.dst) 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /line.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/binary" 6 | "fmt" 7 | "math" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/jackc/pgio" 12 | ) 13 | 14 | type Line struct { 15 | A, B, C float64 16 | Status Status 17 | } 18 | 19 | func (dst *Line) Set(src interface{}) error { 20 | return fmt.Errorf("cannot convert %v to Line", src) 21 | } 22 | 23 | func (dst Line) Get() interface{} { 24 | switch dst.Status { 25 | case Present: 26 | return dst 27 | case Null: 28 | return nil 29 | default: 30 | return dst.Status 31 | } 32 | } 33 | 34 | func (src *Line) AssignTo(dst interface{}) error { 35 | return fmt.Errorf("cannot assign %v to %T", src, dst) 36 | } 37 | 38 | func (dst *Line) DecodeText(ci *ConnInfo, src []byte) error { 39 | if src == nil { 40 | *dst = Line{Status: Null} 41 | return nil 42 | } 43 | 44 | if len(src) < 7 { 45 | return fmt.Errorf("invalid length for Line: %v", len(src)) 46 | } 47 | 48 | parts := strings.SplitN(string(src[1:len(src)-1]), ",", 3) 49 | if len(parts) < 3 { 50 | return fmt.Errorf("invalid format for line") 51 | } 52 | 53 | a, err := strconv.ParseFloat(parts[0], 64) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | b, err := strconv.ParseFloat(parts[1], 64) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | c, err := strconv.ParseFloat(parts[2], 64) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | *dst = Line{A: a, B: b, C: c, Status: Present} 69 | return nil 70 | } 71 | 72 | func (dst *Line) DecodeBinary(ci *ConnInfo, src []byte) error { 73 | if src == nil { 74 | *dst = Line{Status: Null} 75 | return nil 76 | } 77 | 78 | if len(src) != 24 { 79 | return fmt.Errorf("invalid length for Line: %v", len(src)) 80 | } 81 | 82 | a := binary.BigEndian.Uint64(src) 83 | b := binary.BigEndian.Uint64(src[8:]) 84 | c := binary.BigEndian.Uint64(src[16:]) 85 | 86 | *dst = Line{ 87 | A: math.Float64frombits(a), 88 | B: math.Float64frombits(b), 89 | C: math.Float64frombits(c), 90 | Status: Present, 91 | } 92 | return nil 93 | } 94 | 95 | func (src Line) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 96 | switch src.Status { 97 | case Null: 98 | return nil, nil 99 | case Undefined: 100 | return nil, errUndefined 101 | } 102 | 103 | buf = append(buf, fmt.Sprintf(`{%s,%s,%s}`, 104 | strconv.FormatFloat(src.A, 'f', -1, 64), 105 | strconv.FormatFloat(src.B, 'f', -1, 64), 106 | strconv.FormatFloat(src.C, 'f', -1, 64), 107 | )...) 108 | 109 | return buf, nil 110 | } 111 | 112 | func (src Line) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 113 | switch src.Status { 114 | case Null: 115 | return nil, nil 116 | case Undefined: 117 | return nil, errUndefined 118 | } 119 | 120 | buf = pgio.AppendUint64(buf, math.Float64bits(src.A)) 121 | buf = pgio.AppendUint64(buf, math.Float64bits(src.B)) 122 | buf = pgio.AppendUint64(buf, math.Float64bits(src.C)) 123 | return buf, nil 124 | } 125 | 126 | // Scan implements the database/sql Scanner interface. 127 | func (dst *Line) Scan(src interface{}) error { 128 | if src == nil { 129 | *dst = Line{Status: Null} 130 | return nil 131 | } 132 | 133 | switch src := src.(type) { 134 | case string: 135 | return dst.DecodeText(nil, []byte(src)) 136 | case []byte: 137 | srcCopy := make([]byte, len(src)) 138 | copy(srcCopy, src) 139 | return dst.DecodeText(nil, srcCopy) 140 | } 141 | 142 | return fmt.Errorf("cannot scan %T", src) 143 | } 144 | 145 | // Value implements the database/sql/driver Valuer interface. 146 | func (src Line) Value() (driver.Value, error) { 147 | return EncodeValueText(src) 148 | } 149 | -------------------------------------------------------------------------------- /line_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | ) 10 | 11 | func TestLineTranscode(t *testing.T) { 12 | conn := testutil.MustConnectPgx(t) 13 | if _, ok := conn.ConnInfo().DataTypeForName("line"); !ok { 14 | t.Skip("Skipping due to no line type") 15 | } 16 | 17 | // line may exist but not be usable on 9.3 :( 18 | var isPG93 bool 19 | err := conn.QueryRow(context.Background(), "select version() ~ '9.3'").Scan(&isPG93) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | if isPG93 { 24 | t.Skip("Skipping due to unimplemented line type in PG 9.3") 25 | } 26 | 27 | testutil.TestSuccessfulTranscode(t, "line", []interface{}{ 28 | &pgtype.Line{ 29 | A: 1.23, B: 4.56, C: 7.89012345, 30 | Status: pgtype.Present, 31 | }, 32 | &pgtype.Line{ 33 | A: -1.23, B: -4.56, C: -7.89, 34 | Status: pgtype.Present, 35 | }, 36 | &pgtype.Line{Status: pgtype.Null}, 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /lseg.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/binary" 6 | "fmt" 7 | "math" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/jackc/pgio" 12 | ) 13 | 14 | type Lseg struct { 15 | P [2]Vec2 16 | Status Status 17 | } 18 | 19 | func (dst *Lseg) Set(src interface{}) error { 20 | return fmt.Errorf("cannot convert %v to Lseg", src) 21 | } 22 | 23 | func (dst Lseg) Get() interface{} { 24 | switch dst.Status { 25 | case Present: 26 | return dst 27 | case Null: 28 | return nil 29 | default: 30 | return dst.Status 31 | } 32 | } 33 | 34 | func (src *Lseg) AssignTo(dst interface{}) error { 35 | return fmt.Errorf("cannot assign %v to %T", src, dst) 36 | } 37 | 38 | func (dst *Lseg) DecodeText(ci *ConnInfo, src []byte) error { 39 | if src == nil { 40 | *dst = Lseg{Status: Null} 41 | return nil 42 | } 43 | 44 | if len(src) < 11 { 45 | return fmt.Errorf("invalid length for Lseg: %v", len(src)) 46 | } 47 | 48 | str := string(src[2:]) 49 | 50 | var end int 51 | end = strings.IndexByte(str, ',') 52 | 53 | x1, err := strconv.ParseFloat(str[:end], 64) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | str = str[end+1:] 59 | end = strings.IndexByte(str, ')') 60 | 61 | y1, err := strconv.ParseFloat(str[:end], 64) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | str = str[end+3:] 67 | end = strings.IndexByte(str, ',') 68 | 69 | x2, err := strconv.ParseFloat(str[:end], 64) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | str = str[end+1 : len(str)-2] 75 | 76 | y2, err := strconv.ParseFloat(str, 64) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | *dst = Lseg{P: [2]Vec2{{x1, y1}, {x2, y2}}, Status: Present} 82 | return nil 83 | } 84 | 85 | func (dst *Lseg) DecodeBinary(ci *ConnInfo, src []byte) error { 86 | if src == nil { 87 | *dst = Lseg{Status: Null} 88 | return nil 89 | } 90 | 91 | if len(src) != 32 { 92 | return fmt.Errorf("invalid length for Lseg: %v", len(src)) 93 | } 94 | 95 | x1 := binary.BigEndian.Uint64(src) 96 | y1 := binary.BigEndian.Uint64(src[8:]) 97 | x2 := binary.BigEndian.Uint64(src[16:]) 98 | y2 := binary.BigEndian.Uint64(src[24:]) 99 | 100 | *dst = Lseg{ 101 | P: [2]Vec2{ 102 | {math.Float64frombits(x1), math.Float64frombits(y1)}, 103 | {math.Float64frombits(x2), math.Float64frombits(y2)}, 104 | }, 105 | Status: Present, 106 | } 107 | return nil 108 | } 109 | 110 | func (src Lseg) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 111 | switch src.Status { 112 | case Null: 113 | return nil, nil 114 | case Undefined: 115 | return nil, errUndefined 116 | } 117 | 118 | buf = append(buf, fmt.Sprintf(`[(%s,%s),(%s,%s)]`, 119 | strconv.FormatFloat(src.P[0].X, 'f', -1, 64), 120 | strconv.FormatFloat(src.P[0].Y, 'f', -1, 64), 121 | strconv.FormatFloat(src.P[1].X, 'f', -1, 64), 122 | strconv.FormatFloat(src.P[1].Y, 'f', -1, 64), 123 | )...) 124 | 125 | return buf, nil 126 | } 127 | 128 | func (src Lseg) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 129 | switch src.Status { 130 | case Null: 131 | return nil, nil 132 | case Undefined: 133 | return nil, errUndefined 134 | } 135 | 136 | buf = pgio.AppendUint64(buf, math.Float64bits(src.P[0].X)) 137 | buf = pgio.AppendUint64(buf, math.Float64bits(src.P[0].Y)) 138 | buf = pgio.AppendUint64(buf, math.Float64bits(src.P[1].X)) 139 | buf = pgio.AppendUint64(buf, math.Float64bits(src.P[1].Y)) 140 | return buf, nil 141 | } 142 | 143 | // Scan implements the database/sql Scanner interface. 144 | func (dst *Lseg) Scan(src interface{}) error { 145 | if src == nil { 146 | *dst = Lseg{Status: Null} 147 | return nil 148 | } 149 | 150 | switch src := src.(type) { 151 | case string: 152 | return dst.DecodeText(nil, []byte(src)) 153 | case []byte: 154 | srcCopy := make([]byte, len(src)) 155 | copy(srcCopy, src) 156 | return dst.DecodeText(nil, srcCopy) 157 | } 158 | 159 | return fmt.Errorf("cannot scan %T", src) 160 | } 161 | 162 | // Value implements the database/sql/driver Valuer interface. 163 | func (src Lseg) Value() (driver.Value, error) { 164 | return EncodeValueText(src) 165 | } 166 | -------------------------------------------------------------------------------- /lseg_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype" 7 | "github.com/jackc/pgtype/testutil" 8 | ) 9 | 10 | func TestLsegTranscode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "lseg", []interface{}{ 12 | &pgtype.Lseg{ 13 | P: [2]pgtype.Vec2{{3.14, 1.678}, {7.1, 5.2345678901}}, 14 | Status: pgtype.Present, 15 | }, 16 | &pgtype.Lseg{ 17 | P: [2]pgtype.Vec2{{7.1, 1.678}, {-13.14, -5.234}}, 18 | Status: pgtype.Present, 19 | }, 20 | &pgtype.Lseg{Status: pgtype.Null}, 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /ltree.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | ) 7 | 8 | type Ltree Text 9 | 10 | func (dst *Ltree) Set(src interface{}) error { 11 | return (*Text)(dst).Set(src) 12 | } 13 | 14 | func (dst Ltree) Get() interface{} { 15 | return (Text)(dst).Get() 16 | } 17 | 18 | func (src *Ltree) AssignTo(dst interface{}) error { 19 | return (*Text)(src).AssignTo(dst) 20 | } 21 | 22 | func (src Ltree) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 23 | return (Text)(src).EncodeText(ci, buf) 24 | } 25 | 26 | func (src Ltree) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 27 | switch src.Status { 28 | case Null: 29 | return nil, nil 30 | case Undefined: 31 | return nil, errUndefined 32 | } 33 | buf = append(buf, 1) 34 | return append(buf, src.String...), nil 35 | } 36 | 37 | func (Ltree) PreferredResultFormat() int16 { 38 | return TextFormatCode 39 | } 40 | 41 | func (dst *Ltree) DecodeText(ci *ConnInfo, src []byte) error { 42 | return (*Text)(dst).DecodeText(ci, src) 43 | } 44 | 45 | func (dst *Ltree) DecodeBinary(ci *ConnInfo, src []byte) error { 46 | if src == nil { 47 | *dst = Ltree{Status: Null} 48 | return nil 49 | } 50 | 51 | // Get Ltree version, only 1 is allowed 52 | version := src[0] 53 | if version != 1 { 54 | return fmt.Errorf("unsupported ltree version %d", version) 55 | } 56 | 57 | ltreeStr := string(src[1:]) 58 | *dst = Ltree{String: ltreeStr, Status: Present} 59 | return nil 60 | } 61 | 62 | func (Ltree) PreferredParamFormat() int16 { 63 | return TextFormatCode 64 | } 65 | 66 | func (dst *Ltree) Scan(src interface{}) error { 67 | return (*Text)(dst).Scan(src) 68 | } 69 | 70 | func (src Ltree) Value() (driver.Value, error) { 71 | return (Text)(src).Value() 72 | } 73 | -------------------------------------------------------------------------------- /ltree_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | ) 10 | 11 | func TestLtreeTranscode(t *testing.T) { 12 | values := []interface{}{ 13 | &pgtype.Ltree{String: "", Status: pgtype.Present}, 14 | &pgtype.Ltree{String: "All.foo.one", Status: pgtype.Present}, 15 | &pgtype.Ltree{Status: pgtype.Null}, 16 | } 17 | 18 | testutil.TestSuccessfulTranscodeEqFunc( 19 | t, "ltree", values, func(ai, bi interface{}) bool { 20 | a := ai.(pgtype.Ltree) 21 | b := bi.(pgtype.Ltree) 22 | 23 | if a.String != b.String || a.Status != b.Status { 24 | return false 25 | } 26 | return true 27 | }, 28 | ) 29 | 30 | } 31 | 32 | func TestLtreeSet(t *testing.T) { 33 | successfulTests := []struct { 34 | src interface{} 35 | result pgtype.Ltree 36 | }{ 37 | {src: "All.foo.bar", result: pgtype.Ltree{String: "All.foo.bar", Status: pgtype.Present}}, 38 | {src: (*string)(nil), result: pgtype.Ltree{Status: pgtype.Null}}, 39 | } 40 | for i, tt := range successfulTests { 41 | var dst pgtype.Ltree 42 | err := dst.Set(tt.src) 43 | if err != nil { 44 | t.Errorf("%d: %v", i, err) 45 | } 46 | if !reflect.DeepEqual(dst, tt.result) { 47 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.src, tt.result, dst) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /macaddr.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | "net" 7 | ) 8 | 9 | type Macaddr struct { 10 | Addr net.HardwareAddr 11 | Status Status 12 | } 13 | 14 | func (dst *Macaddr) Set(src interface{}) error { 15 | if src == nil { 16 | *dst = Macaddr{Status: Null} 17 | return nil 18 | } 19 | 20 | if value, ok := src.(interface{ Get() interface{} }); ok { 21 | value2 := value.Get() 22 | if value2 != value { 23 | return dst.Set(value2) 24 | } 25 | } 26 | 27 | switch value := src.(type) { 28 | case net.HardwareAddr: 29 | addr := make(net.HardwareAddr, len(value)) 30 | copy(addr, value) 31 | *dst = Macaddr{Addr: addr, Status: Present} 32 | case string: 33 | addr, err := net.ParseMAC(value) 34 | if err != nil { 35 | return err 36 | } 37 | *dst = Macaddr{Addr: addr, Status: Present} 38 | case *net.HardwareAddr: 39 | if value == nil { 40 | *dst = Macaddr{Status: Null} 41 | } else { 42 | return dst.Set(*value) 43 | } 44 | case *string: 45 | if value == nil { 46 | *dst = Macaddr{Status: Null} 47 | } else { 48 | return dst.Set(*value) 49 | } 50 | default: 51 | if originalSrc, ok := underlyingPtrType(src); ok { 52 | return dst.Set(originalSrc) 53 | } 54 | return fmt.Errorf("cannot convert %v to Macaddr", value) 55 | } 56 | 57 | return nil 58 | } 59 | 60 | func (dst Macaddr) Get() interface{} { 61 | switch dst.Status { 62 | case Present: 63 | return dst.Addr 64 | case Null: 65 | return nil 66 | default: 67 | return dst.Status 68 | } 69 | } 70 | 71 | func (src *Macaddr) AssignTo(dst interface{}) error { 72 | switch src.Status { 73 | case Present: 74 | switch v := dst.(type) { 75 | case *net.HardwareAddr: 76 | *v = make(net.HardwareAddr, len(src.Addr)) 77 | copy(*v, src.Addr) 78 | return nil 79 | case *string: 80 | *v = src.Addr.String() 81 | return nil 82 | default: 83 | if nextDst, retry := GetAssignToDstType(dst); retry { 84 | return src.AssignTo(nextDst) 85 | } 86 | return fmt.Errorf("unable to assign to %T", dst) 87 | } 88 | case Null: 89 | return NullAssignTo(dst) 90 | } 91 | 92 | return fmt.Errorf("cannot decode %#v into %T", src, dst) 93 | } 94 | 95 | func (dst *Macaddr) DecodeText(ci *ConnInfo, src []byte) error { 96 | if src == nil { 97 | *dst = Macaddr{Status: Null} 98 | return nil 99 | } 100 | 101 | addr, err := net.ParseMAC(string(src)) 102 | if err != nil { 103 | return err 104 | } 105 | 106 | *dst = Macaddr{Addr: addr, Status: Present} 107 | return nil 108 | } 109 | 110 | func (dst *Macaddr) DecodeBinary(ci *ConnInfo, src []byte) error { 111 | if src == nil { 112 | *dst = Macaddr{Status: Null} 113 | return nil 114 | } 115 | 116 | if len(src) != 6 { 117 | return fmt.Errorf("Received an invalid size for a macaddr: %d", len(src)) 118 | } 119 | 120 | addr := make(net.HardwareAddr, 6) 121 | copy(addr, src) 122 | 123 | *dst = Macaddr{Addr: addr, Status: Present} 124 | 125 | return nil 126 | } 127 | 128 | func (src Macaddr) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 129 | switch src.Status { 130 | case Null: 131 | return nil, nil 132 | case Undefined: 133 | return nil, errUndefined 134 | } 135 | 136 | return append(buf, src.Addr.String()...), nil 137 | } 138 | 139 | // EncodeBinary encodes src into w. 140 | func (src Macaddr) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 141 | switch src.Status { 142 | case Null: 143 | return nil, nil 144 | case Undefined: 145 | return nil, errUndefined 146 | } 147 | 148 | return append(buf, src.Addr...), nil 149 | } 150 | 151 | // Scan implements the database/sql Scanner interface. 152 | func (dst *Macaddr) Scan(src interface{}) error { 153 | if src == nil { 154 | *dst = Macaddr{Status: Null} 155 | return nil 156 | } 157 | 158 | switch src := src.(type) { 159 | case string: 160 | return dst.DecodeText(nil, []byte(src)) 161 | case []byte: 162 | srcCopy := make([]byte, len(src)) 163 | copy(srcCopy, src) 164 | return dst.DecodeText(nil, srcCopy) 165 | } 166 | 167 | return fmt.Errorf("cannot scan %T", src) 168 | } 169 | 170 | // Value implements the database/sql/driver Valuer interface. 171 | func (src Macaddr) Value() (driver.Value, error) { 172 | return EncodeValueText(src) 173 | } 174 | -------------------------------------------------------------------------------- /macaddr_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/jackc/pgtype" 10 | "github.com/jackc/pgtype/testutil" 11 | ) 12 | 13 | func TestMacaddrTranscode(t *testing.T) { 14 | testutil.TestSuccessfulTranscode(t, "macaddr", []interface{}{ 15 | &pgtype.Macaddr{Addr: mustParseMacaddr(t, "01:23:45:67:89:ab"), Status: pgtype.Present}, 16 | &pgtype.Macaddr{Status: pgtype.Null}, 17 | }) 18 | } 19 | 20 | func TestMacaddrSet(t *testing.T) { 21 | successfulTests := []struct { 22 | source interface{} 23 | result pgtype.Macaddr 24 | }{ 25 | { 26 | source: mustParseMacaddr(t, "01:23:45:67:89:ab"), 27 | result: pgtype.Macaddr{Addr: mustParseMacaddr(t, "01:23:45:67:89:ab"), Status: pgtype.Present}, 28 | }, 29 | { 30 | source: "01:23:45:67:89:ab", 31 | result: pgtype.Macaddr{Addr: mustParseMacaddr(t, "01:23:45:67:89:ab"), Status: pgtype.Present}, 32 | }, 33 | } 34 | 35 | for i, tt := range successfulTests { 36 | var r pgtype.Macaddr 37 | err := r.Set(tt.source) 38 | if err != nil { 39 | t.Errorf("%d: %v", i, err) 40 | } 41 | 42 | if !reflect.DeepEqual(r, tt.result) { 43 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) 44 | } 45 | } 46 | } 47 | 48 | func TestMacaddrAssignTo(t *testing.T) { 49 | { 50 | src := pgtype.Macaddr{Addr: mustParseMacaddr(t, "01:23:45:67:89:ab"), Status: pgtype.Present} 51 | var dst net.HardwareAddr 52 | expected := mustParseMacaddr(t, "01:23:45:67:89:ab") 53 | 54 | err := src.AssignTo(&dst) 55 | if err != nil { 56 | t.Error(err) 57 | } 58 | 59 | if !bytes.Equal([]byte(dst), []byte(expected)) { 60 | t.Errorf("expected %v to assign %v, but result was %v", src, expected, dst) 61 | } 62 | } 63 | 64 | { 65 | src := pgtype.Macaddr{Addr: mustParseMacaddr(t, "01:23:45:67:89:ab"), Status: pgtype.Present} 66 | var dst string 67 | expected := "01:23:45:67:89:ab" 68 | 69 | err := src.AssignTo(&dst) 70 | if err != nil { 71 | t.Error(err) 72 | } 73 | 74 | if dst != expected { 75 | t.Errorf("expected %v to assign %v, but result was %v", src, expected, dst) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /multirange.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | type UntypedTextMultirange struct { 9 | Elements []string 10 | } 11 | 12 | func ParseUntypedTextMultirange(src string) (*UntypedTextMultirange, error) { 13 | utmr := &UntypedTextMultirange{} 14 | utmr.Elements = make([]string, 0) 15 | 16 | buf := bytes.NewBufferString(src) 17 | 18 | skipWhitespace(buf) 19 | 20 | r, _, err := buf.ReadRune() 21 | if err != nil { 22 | return nil, fmt.Errorf("invalid array: %v", err) 23 | } 24 | 25 | if r != '{' { 26 | return nil, fmt.Errorf("invalid multirange, expected '{': %v", err) 27 | } 28 | 29 | parseValueLoop: 30 | for { 31 | r, _, err = buf.ReadRune() 32 | if err != nil { 33 | return nil, fmt.Errorf("invalid multirange: %v", err) 34 | } 35 | 36 | switch r { 37 | case ',': // skip range separator 38 | case '}': 39 | break parseValueLoop 40 | default: 41 | buf.UnreadRune() 42 | value, err := parseRange(buf) 43 | if err != nil { 44 | return nil, fmt.Errorf("invalid multirange value: %v", err) 45 | } 46 | utmr.Elements = append(utmr.Elements, value) 47 | } 48 | } 49 | 50 | skipWhitespace(buf) 51 | 52 | if buf.Len() > 0 { 53 | return nil, fmt.Errorf("unexpected trailing data: %v", buf.String()) 54 | } 55 | 56 | return utmr, nil 57 | 58 | } 59 | 60 | func parseRange(buf *bytes.Buffer) (string, error) { 61 | 62 | s := &bytes.Buffer{} 63 | 64 | boundSepRead := false 65 | for { 66 | r, _, err := buf.ReadRune() 67 | if err != nil { 68 | return "", err 69 | } 70 | 71 | switch r { 72 | case ',', '}': 73 | if r == ',' && !boundSepRead { 74 | boundSepRead = true 75 | break 76 | } 77 | buf.UnreadRune() 78 | return s.String(), nil 79 | } 80 | 81 | s.WriteRune(r) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /multirange_test.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestParseUntypedTextMultirange(t *testing.T) { 9 | tests := []struct { 10 | src string 11 | result UntypedTextMultirange 12 | err error 13 | }{ 14 | { 15 | src: `{[1,2)}`, 16 | result: UntypedTextMultirange{Elements: []string{`[1,2)`}}, 17 | err: nil, 18 | }, 19 | { 20 | src: `{[,),["foo", "bar"]}`, 21 | result: UntypedTextMultirange{Elements: []string{`[,)`, `["foo", "bar"]`}}, 22 | err: nil, 23 | }, 24 | { 25 | src: `{}`, 26 | result: UntypedTextMultirange{Elements: []string{}}, 27 | err: nil, 28 | }, 29 | { 30 | src: ` { (,) , [1,2] } `, 31 | result: UntypedTextMultirange{Elements: []string{` (,) `, ` [1,2] `}}, 32 | err: nil, 33 | }, 34 | { 35 | src: `{["f""oo","b""ar")}`, 36 | result: UntypedTextMultirange{Elements: []string{`["f""oo","b""ar")`}}, 37 | err: nil, 38 | }, 39 | } 40 | for i, tt := range tests { 41 | r, err := ParseUntypedTextMultirange(tt.src) 42 | if err != tt.err { 43 | t.Errorf("%d. `%v`: expected err %v, got %v", i, tt.src, tt.err, err) 44 | continue 45 | } 46 | 47 | if !reflect.DeepEqual(*r, tt.result) { 48 | t.Errorf("%d: expected %+v to be parsed to %+v, but it was %+v", i, tt.src, tt.result, *r) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /name.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | ) 6 | 7 | // Name is a type used for PostgreSQL's special 63-byte 8 | // name data type, used for identifiers like table names. 9 | // The pg_class.relname column is a good example of where the 10 | // name data type is used. 11 | // 12 | // Note that the underlying Go data type of pgx.Name is string, 13 | // so there is no way to enforce the 63-byte length. Inputting 14 | // a longer name into PostgreSQL will result in silent truncation 15 | // to 63 bytes. 16 | // 17 | // Also, if you have custom-compiled PostgreSQL and set 18 | // NAMEDATALEN to a different value, obviously that number of 19 | // bytes applies, rather than the default 63. 20 | type Name Text 21 | 22 | func (dst *Name) Set(src interface{}) error { 23 | return (*Text)(dst).Set(src) 24 | } 25 | 26 | func (dst Name) Get() interface{} { 27 | return (Text)(dst).Get() 28 | } 29 | 30 | func (src *Name) AssignTo(dst interface{}) error { 31 | return (*Text)(src).AssignTo(dst) 32 | } 33 | 34 | func (dst *Name) DecodeText(ci *ConnInfo, src []byte) error { 35 | return (*Text)(dst).DecodeText(ci, src) 36 | } 37 | 38 | func (dst *Name) DecodeBinary(ci *ConnInfo, src []byte) error { 39 | return (*Text)(dst).DecodeBinary(ci, src) 40 | } 41 | 42 | func (src Name) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 43 | return (Text)(src).EncodeText(ci, buf) 44 | } 45 | 46 | func (src Name) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 47 | return (Text)(src).EncodeBinary(ci, buf) 48 | } 49 | 50 | // Scan implements the database/sql Scanner interface. 51 | func (dst *Name) Scan(src interface{}) error { 52 | return (*Text)(dst).Scan(src) 53 | } 54 | 55 | // Value implements the database/sql/driver Valuer interface. 56 | func (src Name) Value() (driver.Value, error) { 57 | return (Text)(src).Value() 58 | } 59 | -------------------------------------------------------------------------------- /name_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | ) 10 | 11 | func TestNameTranscode(t *testing.T) { 12 | testutil.TestSuccessfulTranscode(t, "name", []interface{}{ 13 | &pgtype.Name{String: "", Status: pgtype.Present}, 14 | &pgtype.Name{String: "foo", Status: pgtype.Present}, 15 | &pgtype.Name{Status: pgtype.Null}, 16 | }) 17 | } 18 | 19 | func TestNameSet(t *testing.T) { 20 | successfulTests := []struct { 21 | source interface{} 22 | result pgtype.Name 23 | }{ 24 | {source: "foo", result: pgtype.Name{String: "foo", Status: pgtype.Present}}, 25 | {source: _string("bar"), result: pgtype.Name{String: "bar", Status: pgtype.Present}}, 26 | {source: (*string)(nil), result: pgtype.Name{Status: pgtype.Null}}, 27 | } 28 | 29 | for i, tt := range successfulTests { 30 | var d pgtype.Name 31 | err := d.Set(tt.source) 32 | if err != nil { 33 | t.Errorf("%d: %v", i, err) 34 | } 35 | 36 | if d != tt.result { 37 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, d) 38 | } 39 | } 40 | } 41 | 42 | func TestNameAssignTo(t *testing.T) { 43 | var s string 44 | var ps *string 45 | 46 | simpleTests := []struct { 47 | src pgtype.Name 48 | dst interface{} 49 | expected interface{} 50 | }{ 51 | {src: pgtype.Name{String: "foo", Status: pgtype.Present}, dst: &s, expected: "foo"}, 52 | {src: pgtype.Name{Status: pgtype.Null}, dst: &ps, expected: ((*string)(nil))}, 53 | } 54 | 55 | for i, tt := range simpleTests { 56 | err := tt.src.AssignTo(tt.dst) 57 | if err != nil { 58 | t.Errorf("%d: %v", i, err) 59 | } 60 | 61 | if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected { 62 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 63 | } 64 | } 65 | 66 | pointerAllocTests := []struct { 67 | src pgtype.Name 68 | dst interface{} 69 | expected interface{} 70 | }{ 71 | {src: pgtype.Name{String: "foo", Status: pgtype.Present}, dst: &ps, expected: "foo"}, 72 | } 73 | 74 | for i, tt := range pointerAllocTests { 75 | err := tt.src.AssignTo(tt.dst) 76 | if err != nil { 77 | t.Errorf("%d: %v", i, err) 78 | } 79 | 80 | if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected { 81 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 82 | } 83 | } 84 | 85 | errorTests := []struct { 86 | src pgtype.Name 87 | dst interface{} 88 | }{ 89 | {src: pgtype.Name{Status: pgtype.Null}, dst: &s}, 90 | } 91 | 92 | for i, tt := range errorTests { 93 | err := tt.src.AssignTo(tt.dst) 94 | if err == nil { 95 | t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /num_multirange_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | ) 10 | 11 | func TestNumericMultirangeTranscode(t *testing.T) { 12 | testutil.TestSuccessfulTranscode(t, "nummultirange", []interface{}{ 13 | &pgtype.Nummultirange{ 14 | Ranges: nil, 15 | Status: pgtype.Present, 16 | }, 17 | &pgtype.Nummultirange{ 18 | Ranges: []pgtype.Numrange{ 19 | { 20 | Lower: pgtype.Numeric{Int: big.NewInt(-543), Exp: 3, Status: pgtype.Present}, 21 | Upper: pgtype.Numeric{Int: big.NewInt(342), Exp: 1, Status: pgtype.Present}, 22 | LowerType: pgtype.Inclusive, 23 | UpperType: pgtype.Exclusive, 24 | Status: pgtype.Present, 25 | }, 26 | }, 27 | Status: pgtype.Present, 28 | }, 29 | &pgtype.Nummultirange{ 30 | Ranges: []pgtype.Numrange{ 31 | { 32 | Lower: pgtype.Numeric{Int: big.NewInt(-42), Exp: 1, Status: pgtype.Present}, 33 | Upper: pgtype.Numeric{Int: big.NewInt(-5), Exp: 0, Status: pgtype.Present}, 34 | LowerType: pgtype.Inclusive, 35 | UpperType: pgtype.Exclusive, 36 | Status: pgtype.Present, 37 | }, 38 | { 39 | Lower: pgtype.Numeric{Int: big.NewInt(5), Exp: 1, Status: pgtype.Present}, 40 | Upper: pgtype.Numeric{Int: big.NewInt(42), Exp: 1, Status: pgtype.Present}, 41 | LowerType: pgtype.Inclusive, 42 | UpperType: pgtype.Inclusive, 43 | Status: pgtype.Present, 44 | }, 45 | { 46 | Lower: pgtype.Numeric{Int: big.NewInt(42), Exp: 2, Status: pgtype.Present}, 47 | LowerType: pgtype.Exclusive, 48 | UpperType: pgtype.Unbounded, 49 | Status: pgtype.Present, 50 | }, 51 | }, 52 | Status: pgtype.Present, 53 | }, 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /numrange_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | ) 10 | 11 | func TestNumrangeTranscode(t *testing.T) { 12 | testutil.TestSuccessfulTranscode(t, "numrange", []interface{}{ 13 | &pgtype.Numrange{ 14 | LowerType: pgtype.Empty, 15 | UpperType: pgtype.Empty, 16 | Status: pgtype.Present, 17 | }, 18 | &pgtype.Numrange{ 19 | Lower: pgtype.Numeric{Int: big.NewInt(-543), Exp: 3, Status: pgtype.Present}, 20 | Upper: pgtype.Numeric{Int: big.NewInt(342), Exp: 1, Status: pgtype.Present}, 21 | LowerType: pgtype.Inclusive, 22 | UpperType: pgtype.Exclusive, 23 | Status: pgtype.Present, 24 | }, 25 | &pgtype.Numrange{ 26 | Lower: pgtype.Numeric{Int: big.NewInt(-42), Exp: 1, Status: pgtype.Present}, 27 | Upper: pgtype.Numeric{Int: big.NewInt(-5), Exp: 0, Status: pgtype.Present}, 28 | LowerType: pgtype.Inclusive, 29 | UpperType: pgtype.Exclusive, 30 | Status: pgtype.Present, 31 | }, 32 | &pgtype.Numrange{ 33 | Lower: pgtype.Numeric{Int: big.NewInt(-42), Exp: 1, Status: pgtype.Present}, 34 | LowerType: pgtype.Inclusive, 35 | UpperType: pgtype.Unbounded, 36 | Status: pgtype.Present, 37 | }, 38 | &pgtype.Numrange{ 39 | Upper: pgtype.Numeric{Int: big.NewInt(-42), Exp: 1, Status: pgtype.Present}, 40 | LowerType: pgtype.Unbounded, 41 | UpperType: pgtype.Exclusive, 42 | Status: pgtype.Present, 43 | }, 44 | &pgtype.Numrange{Status: pgtype.Null}, 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /oid.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/binary" 6 | "fmt" 7 | "strconv" 8 | 9 | "github.com/jackc/pgio" 10 | ) 11 | 12 | // OID (Object Identifier Type) is, according to 13 | // https://www.postgresql.org/docs/current/static/datatype-oid.html, used 14 | // internally by PostgreSQL as a primary key for various system tables. It is 15 | // currently implemented as an unsigned four-byte integer. Its definition can be 16 | // found in src/include/postgres_ext.h in the PostgreSQL sources. Because it is 17 | // so frequently required to be in a NOT NULL condition OID cannot be NULL. To 18 | // allow for NULL OIDs use OIDValue. 19 | type OID uint32 20 | 21 | func (dst *OID) DecodeText(ci *ConnInfo, src []byte) error { 22 | if src == nil { 23 | return fmt.Errorf("cannot decode nil into OID") 24 | } 25 | 26 | n, err := strconv.ParseUint(string(src), 10, 32) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | *dst = OID(n) 32 | return nil 33 | } 34 | 35 | func (dst *OID) DecodeBinary(ci *ConnInfo, src []byte) error { 36 | if src == nil { 37 | return fmt.Errorf("cannot decode nil into OID") 38 | } 39 | 40 | if len(src) != 4 { 41 | return fmt.Errorf("invalid length: %v", len(src)) 42 | } 43 | 44 | n := binary.BigEndian.Uint32(src) 45 | *dst = OID(n) 46 | return nil 47 | } 48 | 49 | func (src OID) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 50 | return append(buf, strconv.FormatUint(uint64(src), 10)...), nil 51 | } 52 | 53 | func (src OID) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 54 | return pgio.AppendUint32(buf, uint32(src)), nil 55 | } 56 | 57 | // Scan implements the database/sql Scanner interface. 58 | func (dst *OID) Scan(src interface{}) error { 59 | if src == nil { 60 | return fmt.Errorf("cannot scan NULL into %T", src) 61 | } 62 | 63 | switch src := src.(type) { 64 | case int64: 65 | *dst = OID(src) 66 | return nil 67 | case string: 68 | return dst.DecodeText(nil, []byte(src)) 69 | case []byte: 70 | srcCopy := make([]byte, len(src)) 71 | copy(srcCopy, src) 72 | return dst.DecodeText(nil, srcCopy) 73 | } 74 | 75 | return fmt.Errorf("cannot scan %T", src) 76 | } 77 | 78 | // Value implements the database/sql/driver Valuer interface. 79 | func (src OID) Value() (driver.Value, error) { 80 | return int64(src), nil 81 | } 82 | -------------------------------------------------------------------------------- /oid_value.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | ) 6 | 7 | // OIDValue (Object Identifier Type) is, according to 8 | // https://www.postgresql.org/docs/current/static/datatype-OIDValue.html, used 9 | // internally by PostgreSQL as a primary key for various system tables. It is 10 | // currently implemented as an unsigned four-byte integer. Its definition can be 11 | // found in src/include/postgres_ext.h in the PostgreSQL sources. 12 | type OIDValue pguint32 13 | 14 | // Set converts from src to dst. Note that as OIDValue is not a general 15 | // number type Set does not do automatic type conversion as other number 16 | // types do. 17 | func (dst *OIDValue) Set(src interface{}) error { 18 | return (*pguint32)(dst).Set(src) 19 | } 20 | 21 | func (dst OIDValue) Get() interface{} { 22 | return (pguint32)(dst).Get() 23 | } 24 | 25 | // AssignTo assigns from src to dst. Note that as OIDValue is not a general number 26 | // type AssignTo does not do automatic type conversion as other number types do. 27 | func (src *OIDValue) AssignTo(dst interface{}) error { 28 | return (*pguint32)(src).AssignTo(dst) 29 | } 30 | 31 | func (dst *OIDValue) DecodeText(ci *ConnInfo, src []byte) error { 32 | return (*pguint32)(dst).DecodeText(ci, src) 33 | } 34 | 35 | func (dst *OIDValue) DecodeBinary(ci *ConnInfo, src []byte) error { 36 | return (*pguint32)(dst).DecodeBinary(ci, src) 37 | } 38 | 39 | func (src OIDValue) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 40 | return (pguint32)(src).EncodeText(ci, buf) 41 | } 42 | 43 | func (src OIDValue) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 44 | return (pguint32)(src).EncodeBinary(ci, buf) 45 | } 46 | 47 | // Scan implements the database/sql Scanner interface. 48 | func (dst *OIDValue) Scan(src interface{}) error { 49 | return (*pguint32)(dst).Scan(src) 50 | } 51 | 52 | // Value implements the database/sql/driver Valuer interface. 53 | func (src OIDValue) Value() (driver.Value, error) { 54 | return (pguint32)(src).Value() 55 | } 56 | -------------------------------------------------------------------------------- /oid_value_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | ) 10 | 11 | func TestOIDValueTranscode(t *testing.T) { 12 | testutil.TestSuccessfulTranscode(t, "oid", []interface{}{ 13 | &pgtype.OIDValue{Uint: 42, Status: pgtype.Present}, 14 | &pgtype.OIDValue{Status: pgtype.Null}, 15 | }) 16 | } 17 | 18 | func TestOIDValueSet(t *testing.T) { 19 | successfulTests := []struct { 20 | source interface{} 21 | result pgtype.OIDValue 22 | }{ 23 | {source: uint32(1), result: pgtype.OIDValue{Uint: 1, Status: pgtype.Present}}, 24 | } 25 | 26 | for i, tt := range successfulTests { 27 | var r pgtype.OIDValue 28 | err := r.Set(tt.source) 29 | if err != nil { 30 | t.Errorf("%d: %v", i, err) 31 | } 32 | 33 | if r != tt.result { 34 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) 35 | } 36 | } 37 | } 38 | 39 | func TestOIDValueAssignTo(t *testing.T) { 40 | var ui32 uint32 41 | var pui32 *uint32 42 | 43 | simpleTests := []struct { 44 | src pgtype.OIDValue 45 | dst interface{} 46 | expected interface{} 47 | }{ 48 | {src: pgtype.OIDValue{Uint: 42, Status: pgtype.Present}, dst: &ui32, expected: uint32(42)}, 49 | {src: pgtype.OIDValue{Status: pgtype.Null}, dst: &pui32, expected: ((*uint32)(nil))}, 50 | } 51 | 52 | for i, tt := range simpleTests { 53 | err := tt.src.AssignTo(tt.dst) 54 | if err != nil { 55 | t.Errorf("%d: %v", i, err) 56 | } 57 | 58 | if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected { 59 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 60 | } 61 | } 62 | 63 | pointerAllocTests := []struct { 64 | src pgtype.OIDValue 65 | dst interface{} 66 | expected interface{} 67 | }{ 68 | {src: pgtype.OIDValue{Uint: 42, Status: pgtype.Present}, dst: &pui32, expected: uint32(42)}, 69 | } 70 | 71 | for i, tt := range pointerAllocTests { 72 | err := tt.src.AssignTo(tt.dst) 73 | if err != nil { 74 | t.Errorf("%d: %v", i, err) 75 | } 76 | 77 | if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected { 78 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 79 | } 80 | } 81 | 82 | errorTests := []struct { 83 | src pgtype.OIDValue 84 | dst interface{} 85 | }{ 86 | {src: pgtype.OIDValue{Status: pgtype.Null}, dst: &ui32}, 87 | } 88 | 89 | for i, tt := range errorTests { 90 | err := tt.src.AssignTo(tt.dst) 91 | if err == nil { 92 | t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst) 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /path.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/binary" 6 | "fmt" 7 | "math" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/jackc/pgio" 12 | ) 13 | 14 | type Path struct { 15 | P []Vec2 16 | Closed bool 17 | Status Status 18 | } 19 | 20 | func (dst *Path) Set(src interface{}) error { 21 | return fmt.Errorf("cannot convert %v to Path", src) 22 | } 23 | 24 | func (dst Path) Get() interface{} { 25 | switch dst.Status { 26 | case Present: 27 | return dst 28 | case Null: 29 | return nil 30 | default: 31 | return dst.Status 32 | } 33 | } 34 | 35 | func (src *Path) AssignTo(dst interface{}) error { 36 | return fmt.Errorf("cannot assign %v to %T", src, dst) 37 | } 38 | 39 | func (dst *Path) DecodeText(ci *ConnInfo, src []byte) error { 40 | if src == nil { 41 | *dst = Path{Status: Null} 42 | return nil 43 | } 44 | 45 | if len(src) < 7 { 46 | return fmt.Errorf("invalid length for Path: %v", len(src)) 47 | } 48 | 49 | closed := src[0] == '(' 50 | points := make([]Vec2, 0) 51 | 52 | str := string(src[2:]) 53 | 54 | for { 55 | end := strings.IndexByte(str, ',') 56 | x, err := strconv.ParseFloat(str[:end], 64) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | str = str[end+1:] 62 | end = strings.IndexByte(str, ')') 63 | 64 | y, err := strconv.ParseFloat(str[:end], 64) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | points = append(points, Vec2{x, y}) 70 | 71 | if end+3 < len(str) { 72 | str = str[end+3:] 73 | } else { 74 | break 75 | } 76 | } 77 | 78 | *dst = Path{P: points, Closed: closed, Status: Present} 79 | return nil 80 | } 81 | 82 | func (dst *Path) DecodeBinary(ci *ConnInfo, src []byte) error { 83 | if src == nil { 84 | *dst = Path{Status: Null} 85 | return nil 86 | } 87 | 88 | if len(src) < 5 { 89 | return fmt.Errorf("invalid length for Path: %v", len(src)) 90 | } 91 | 92 | closed := src[0] == 1 93 | pointCount := int(binary.BigEndian.Uint32(src[1:])) 94 | 95 | rp := 5 96 | 97 | if 5+pointCount*16 != len(src) { 98 | return fmt.Errorf("invalid length for Path with %d points: %v", pointCount, len(src)) 99 | } 100 | 101 | points := make([]Vec2, pointCount) 102 | for i := 0; i < len(points); i++ { 103 | x := binary.BigEndian.Uint64(src[rp:]) 104 | rp += 8 105 | y := binary.BigEndian.Uint64(src[rp:]) 106 | rp += 8 107 | points[i] = Vec2{math.Float64frombits(x), math.Float64frombits(y)} 108 | } 109 | 110 | *dst = Path{ 111 | P: points, 112 | Closed: closed, 113 | Status: Present, 114 | } 115 | return nil 116 | } 117 | 118 | func (src Path) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 119 | switch src.Status { 120 | case Null: 121 | return nil, nil 122 | case Undefined: 123 | return nil, errUndefined 124 | } 125 | 126 | var startByte, endByte byte 127 | if src.Closed { 128 | startByte = '(' 129 | endByte = ')' 130 | } else { 131 | startByte = '[' 132 | endByte = ']' 133 | } 134 | buf = append(buf, startByte) 135 | 136 | for i, p := range src.P { 137 | if i > 0 { 138 | buf = append(buf, ',') 139 | } 140 | buf = append(buf, fmt.Sprintf(`(%s,%s)`, 141 | strconv.FormatFloat(p.X, 'f', -1, 64), 142 | strconv.FormatFloat(p.Y, 'f', -1, 64), 143 | )...) 144 | } 145 | 146 | return append(buf, endByte), nil 147 | } 148 | 149 | func (src Path) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 150 | switch src.Status { 151 | case Null: 152 | return nil, nil 153 | case Undefined: 154 | return nil, errUndefined 155 | } 156 | 157 | var closeByte byte 158 | if src.Closed { 159 | closeByte = 1 160 | } 161 | buf = append(buf, closeByte) 162 | 163 | buf = pgio.AppendInt32(buf, int32(len(src.P))) 164 | 165 | for _, p := range src.P { 166 | buf = pgio.AppendUint64(buf, math.Float64bits(p.X)) 167 | buf = pgio.AppendUint64(buf, math.Float64bits(p.Y)) 168 | } 169 | 170 | return buf, nil 171 | } 172 | 173 | // Scan implements the database/sql Scanner interface. 174 | func (dst *Path) Scan(src interface{}) error { 175 | if src == nil { 176 | *dst = Path{Status: Null} 177 | return nil 178 | } 179 | 180 | switch src := src.(type) { 181 | case string: 182 | return dst.DecodeText(nil, []byte(src)) 183 | case []byte: 184 | srcCopy := make([]byte, len(src)) 185 | copy(srcCopy, src) 186 | return dst.DecodeText(nil, srcCopy) 187 | } 188 | 189 | return fmt.Errorf("cannot scan %T", src) 190 | } 191 | 192 | // Value implements the database/sql/driver Valuer interface. 193 | func (src Path) Value() (driver.Value, error) { 194 | return EncodeValueText(src) 195 | } 196 | -------------------------------------------------------------------------------- /path_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype" 7 | "github.com/jackc/pgtype/testutil" 8 | ) 9 | 10 | func TestPathTranscode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "path", []interface{}{ 12 | &pgtype.Path{ 13 | P: []pgtype.Vec2{{3.14, 1.678901234}, {7.1, 5.234}}, 14 | Closed: false, 15 | Status: pgtype.Present, 16 | }, 17 | &pgtype.Path{ 18 | P: []pgtype.Vec2{{3.14, 1.678}, {7.1, 5.234}, {23.1, 9.34}}, 19 | Closed: true, 20 | Status: pgtype.Present, 21 | }, 22 | &pgtype.Path{ 23 | P: []pgtype.Vec2{{7.1, 1.678}, {-13.14, -5.234}}, 24 | Closed: true, 25 | Status: pgtype.Present, 26 | }, 27 | &pgtype.Path{Status: pgtype.Null}, 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /pguint32.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/binary" 6 | "fmt" 7 | "math" 8 | "strconv" 9 | 10 | "github.com/jackc/pgio" 11 | ) 12 | 13 | // pguint32 is the core type that is used to implement PostgreSQL types such as 14 | // CID and XID. 15 | type pguint32 struct { 16 | Uint uint32 17 | Status Status 18 | } 19 | 20 | // Set converts from src to dst. Note that as pguint32 is not a general 21 | // number type Set does not do automatic type conversion as other number 22 | // types do. 23 | func (dst *pguint32) Set(src interface{}) error { 24 | switch value := src.(type) { 25 | case int64: 26 | if value < 0 { 27 | return fmt.Errorf("%d is less than minimum value for pguint32", value) 28 | } 29 | if value > math.MaxUint32 { 30 | return fmt.Errorf("%d is greater than maximum value for pguint32", value) 31 | } 32 | *dst = pguint32{Uint: uint32(value), Status: Present} 33 | case uint32: 34 | *dst = pguint32{Uint: value, Status: Present} 35 | default: 36 | return fmt.Errorf("cannot convert %v to pguint32", value) 37 | } 38 | 39 | return nil 40 | } 41 | 42 | func (dst pguint32) Get() interface{} { 43 | switch dst.Status { 44 | case Present: 45 | return dst.Uint 46 | case Null: 47 | return nil 48 | default: 49 | return dst.Status 50 | } 51 | } 52 | 53 | // AssignTo assigns from src to dst. Note that as pguint32 is not a general number 54 | // type AssignTo does not do automatic type conversion as other number types do. 55 | func (src *pguint32) AssignTo(dst interface{}) error { 56 | switch v := dst.(type) { 57 | case *uint32: 58 | if src.Status == Present { 59 | *v = src.Uint 60 | } else { 61 | return fmt.Errorf("cannot assign %v into %T", src, dst) 62 | } 63 | case **uint32: 64 | if src.Status == Present { 65 | n := src.Uint 66 | *v = &n 67 | } else { 68 | *v = nil 69 | } 70 | } 71 | 72 | return nil 73 | } 74 | 75 | func (dst *pguint32) DecodeText(ci *ConnInfo, src []byte) error { 76 | if src == nil { 77 | *dst = pguint32{Status: Null} 78 | return nil 79 | } 80 | 81 | n, err := strconv.ParseUint(string(src), 10, 32) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | *dst = pguint32{Uint: uint32(n), Status: Present} 87 | return nil 88 | } 89 | 90 | func (dst *pguint32) DecodeBinary(ci *ConnInfo, src []byte) error { 91 | if src == nil { 92 | *dst = pguint32{Status: Null} 93 | return nil 94 | } 95 | 96 | if len(src) != 4 { 97 | return fmt.Errorf("invalid length: %v", len(src)) 98 | } 99 | 100 | n := binary.BigEndian.Uint32(src) 101 | *dst = pguint32{Uint: n, Status: Present} 102 | return nil 103 | } 104 | 105 | func (src pguint32) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 106 | switch src.Status { 107 | case Null: 108 | return nil, nil 109 | case Undefined: 110 | return nil, errUndefined 111 | } 112 | 113 | return append(buf, strconv.FormatUint(uint64(src.Uint), 10)...), nil 114 | } 115 | 116 | func (src pguint32) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 117 | switch src.Status { 118 | case Null: 119 | return nil, nil 120 | case Undefined: 121 | return nil, errUndefined 122 | } 123 | 124 | return pgio.AppendUint32(buf, src.Uint), nil 125 | } 126 | 127 | // Scan implements the database/sql Scanner interface. 128 | func (dst *pguint32) Scan(src interface{}) error { 129 | if src == nil { 130 | *dst = pguint32{Status: Null} 131 | return nil 132 | } 133 | 134 | switch src := src.(type) { 135 | case uint32: 136 | *dst = pguint32{Uint: src, Status: Present} 137 | return nil 138 | case int64: 139 | *dst = pguint32{Uint: uint32(src), Status: Present} 140 | return nil 141 | case string: 142 | return dst.DecodeText(nil, []byte(src)) 143 | case []byte: 144 | srcCopy := make([]byte, len(src)) 145 | copy(srcCopy, src) 146 | return dst.DecodeText(nil, srcCopy) 147 | } 148 | 149 | return fmt.Errorf("cannot scan %T", src) 150 | } 151 | 152 | // Value implements the database/sql/driver Valuer interface. 153 | func (src pguint32) Value() (driver.Value, error) { 154 | switch src.Status { 155 | case Present: 156 | return int64(src.Uint), nil 157 | case Null: 158 | return nil, nil 159 | default: 160 | return nil, errUndefined 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /pgxtype/README.md: -------------------------------------------------------------------------------- 1 | # pgxtype 2 | 3 | pgxtype is a helper module that connects pgx and pgtype. This package is not currently covered by semantic version guarantees. i.e. The interfaces may change without a major version release of pgtype. 4 | -------------------------------------------------------------------------------- /pgxtype/pgxtype.go: -------------------------------------------------------------------------------- 1 | package pgxtype 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/jackc/pgconn" 8 | "github.com/jackc/pgtype" 9 | "github.com/jackc/pgx/v4" 10 | ) 11 | 12 | type Querier interface { 13 | Exec(ctx context.Context, sql string, arguments ...interface{}) (pgconn.CommandTag, error) 14 | Query(ctx context.Context, sql string, optionsAndArgs ...interface{}) (pgx.Rows, error) 15 | QueryRow(ctx context.Context, sql string, optionsAndArgs ...interface{}) pgx.Row 16 | } 17 | 18 | // LoadDataType uses conn to inspect the database for typeName and produces a pgtype.DataType suitable for 19 | // registration on ci. 20 | func LoadDataType(ctx context.Context, conn Querier, ci *pgtype.ConnInfo, typeName string) (pgtype.DataType, error) { 21 | var oid uint32 22 | 23 | err := conn.QueryRow(ctx, "select $1::text::regtype::oid;", typeName).Scan(&oid) 24 | if err != nil { 25 | return pgtype.DataType{}, err 26 | } 27 | 28 | var typtype string 29 | 30 | err = conn.QueryRow(ctx, "select typtype::text from pg_type where oid=$1", oid).Scan(&typtype) 31 | if err != nil { 32 | return pgtype.DataType{}, err 33 | } 34 | 35 | switch typtype { 36 | case "b": // array 37 | elementOID, err := GetArrayElementOID(ctx, conn, oid) 38 | if err != nil { 39 | return pgtype.DataType{}, err 40 | } 41 | 42 | var element pgtype.ValueTranscoder 43 | if dt, ok := ci.DataTypeForOID(elementOID); ok { 44 | if element, ok = dt.Value.(pgtype.ValueTranscoder); !ok { 45 | return pgtype.DataType{}, errors.New("array element OID not registered as ValueTranscoder") 46 | } 47 | } 48 | 49 | newElement := func() pgtype.ValueTranscoder { 50 | return pgtype.NewValue(element).(pgtype.ValueTranscoder) 51 | } 52 | 53 | at := pgtype.NewArrayType(typeName, elementOID, newElement) 54 | return pgtype.DataType{Value: at, Name: typeName, OID: oid}, nil 55 | case "c": // composite 56 | fields, err := GetCompositeFields(ctx, conn, oid) 57 | if err != nil { 58 | return pgtype.DataType{}, err 59 | } 60 | ct, err := pgtype.NewCompositeType(typeName, fields, ci) 61 | if err != nil { 62 | return pgtype.DataType{}, err 63 | } 64 | return pgtype.DataType{Value: ct, Name: typeName, OID: oid}, nil 65 | case "e": // enum 66 | members, err := GetEnumMembers(ctx, conn, oid) 67 | if err != nil { 68 | return pgtype.DataType{}, err 69 | } 70 | return pgtype.DataType{Value: pgtype.NewEnumType(typeName, members), Name: typeName, OID: oid}, nil 71 | default: 72 | return pgtype.DataType{}, errors.New("unknown typtype") 73 | } 74 | } 75 | 76 | func GetArrayElementOID(ctx context.Context, conn Querier, oid uint32) (uint32, error) { 77 | var typelem uint32 78 | 79 | err := conn.QueryRow(ctx, "select typelem from pg_type where oid=$1", oid).Scan(&typelem) 80 | if err != nil { 81 | return 0, err 82 | } 83 | 84 | return typelem, nil 85 | } 86 | 87 | // GetCompositeFields gets the fields of a composite type. 88 | func GetCompositeFields(ctx context.Context, conn Querier, oid uint32) ([]pgtype.CompositeTypeField, error) { 89 | var typrelid uint32 90 | 91 | err := conn.QueryRow(ctx, "select typrelid from pg_type where oid=$1", oid).Scan(&typrelid) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | var fields []pgtype.CompositeTypeField 97 | 98 | rows, err := conn.Query(ctx, `select attname, atttypid 99 | from pg_attribute 100 | where attrelid=$1 101 | order by attnum`, typrelid) 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | for rows.Next() { 107 | var f pgtype.CompositeTypeField 108 | err := rows.Scan(&f.Name, &f.OID) 109 | if err != nil { 110 | return nil, err 111 | } 112 | fields = append(fields, f) 113 | } 114 | 115 | if rows.Err() != nil { 116 | return nil, rows.Err() 117 | } 118 | 119 | return fields, nil 120 | } 121 | 122 | // GetEnumMembers gets the possible values of the enum by oid. 123 | func GetEnumMembers(ctx context.Context, conn Querier, oid uint32) ([]string, error) { 124 | members := []string{} 125 | 126 | rows, err := conn.Query(ctx, "select enumlabel from pg_enum where enumtypid=$1 order by enumsortorder", oid) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | for rows.Next() { 132 | var m string 133 | err := rows.Scan(&m) 134 | if err != nil { 135 | return nil, err 136 | } 137 | members = append(members, m) 138 | } 139 | 140 | if rows.Err() != nil { 141 | return nil, rows.Err() 142 | } 143 | 144 | return members, nil 145 | } 146 | -------------------------------------------------------------------------------- /point_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | ) 10 | 11 | func TestPointTranscode(t *testing.T) { 12 | testutil.TestSuccessfulTranscode(t, "point", []interface{}{ 13 | &pgtype.Point{P: pgtype.Vec2{1.234, 5.6789012345}, Status: pgtype.Present}, 14 | &pgtype.Point{P: pgtype.Vec2{-1.234, -5.6789}, Status: pgtype.Present}, 15 | &pgtype.Point{Status: pgtype.Null}, 16 | }) 17 | } 18 | 19 | func TestPoint_Set(t *testing.T) { 20 | tests := []struct { 21 | name string 22 | arg interface{} 23 | status pgtype.Status 24 | wantErr bool 25 | }{ 26 | { 27 | name: "first", 28 | arg: "(12312.123123,123123.123123)", 29 | status: pgtype.Present, 30 | wantErr: false, 31 | }, 32 | { 33 | name: "second", 34 | arg: "(1231s2.123123,123123.123123)", 35 | status: pgtype.Undefined, 36 | wantErr: true, 37 | }, 38 | { 39 | name: "third", 40 | arg: []byte("(122.123123,123.123123)"), 41 | status: pgtype.Present, 42 | wantErr: false, 43 | }, 44 | { 45 | name: "third", 46 | arg: nil, 47 | status: pgtype.Null, 48 | wantErr: false, 49 | }, 50 | } 51 | for _, tt := range tests { 52 | t.Run(tt.name, func(t *testing.T) { 53 | dst := &pgtype.Point{} 54 | if err := dst.Set(tt.arg); (err != nil) != tt.wantErr { 55 | t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr) 56 | } 57 | if dst.Status != tt.status { 58 | t.Errorf("Expected status: %v; got: %v", tt.status, dst.Status) 59 | } 60 | }) 61 | } 62 | } 63 | 64 | func TestPoint_MarshalJSON(t *testing.T) { 65 | tests := []struct { 66 | name string 67 | point pgtype.Point 68 | want []byte 69 | wantErr bool 70 | }{ 71 | { 72 | name: "first", 73 | point: pgtype.Point{ 74 | P: pgtype.Vec2{}, 75 | Status: pgtype.Undefined, 76 | }, 77 | want: nil, 78 | wantErr: true, 79 | }, 80 | { 81 | name: "second", 82 | point: pgtype.Point{ 83 | P: pgtype.Vec2{X: 12.245, Y: 432.12}, 84 | Status: pgtype.Present, 85 | }, 86 | want: []byte(`"(12.245,432.12)"`), 87 | wantErr: false, 88 | }, 89 | { 90 | name: "third", 91 | point: pgtype.Point{ 92 | P: pgtype.Vec2{}, 93 | Status: pgtype.Null, 94 | }, 95 | want: []byte("null"), 96 | wantErr: false, 97 | }, 98 | } 99 | for _, tt := range tests { 100 | t.Run(tt.name, func(t *testing.T) { 101 | got, err := tt.point.MarshalJSON() 102 | if (err != nil) != tt.wantErr { 103 | t.Errorf("MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) 104 | return 105 | } 106 | if !reflect.DeepEqual(got, tt.want) { 107 | t.Errorf("MarshalJSON() got = %v, want %v", got, tt.want) 108 | } 109 | }) 110 | } 111 | } 112 | 113 | func TestPoint_UnmarshalJSON(t *testing.T) { 114 | tests := []struct { 115 | name string 116 | status pgtype.Status 117 | arg []byte 118 | wantErr bool 119 | }{ 120 | { 121 | name: "first", 122 | status: pgtype.Present, 123 | arg: []byte(`"(123.123,54.12)"`), 124 | wantErr: false, 125 | }, 126 | { 127 | name: "second", 128 | status: pgtype.Undefined, 129 | arg: []byte(`"(123.123,54.1sad2)"`), 130 | wantErr: true, 131 | }, 132 | { 133 | name: "third", 134 | status: pgtype.Null, 135 | arg: []byte("null"), 136 | wantErr: false, 137 | }, 138 | } 139 | for _, tt := range tests { 140 | t.Run(tt.name, func(t *testing.T) { 141 | dst := &pgtype.Point{} 142 | if err := dst.UnmarshalJSON(tt.arg); (err != nil) != tt.wantErr { 143 | t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) 144 | } 145 | if dst.Status != tt.status { 146 | t.Errorf("Status mismatch: %v != %v", dst.Status, tt.status) 147 | } 148 | }) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /polygon_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype" 7 | "github.com/jackc/pgtype/testutil" 8 | ) 9 | 10 | func TestPolygonTranscode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "polygon", []interface{}{ 12 | &pgtype.Polygon{ 13 | P: []pgtype.Vec2{{3.14, 1.678901234}, {7.1, 5.234}, {5.0, 3.234}}, 14 | Status: pgtype.Present, 15 | }, 16 | &pgtype.Polygon{ 17 | P: []pgtype.Vec2{{3.14, -1.678}, {7.1, -5.234}, {23.1, 9.34}}, 18 | Status: pgtype.Present, 19 | }, 20 | &pgtype.Polygon{Status: pgtype.Null}, 21 | }) 22 | } 23 | 24 | func TestPolygon_Set(t *testing.T) { 25 | tests := []struct { 26 | name string 27 | arg interface{} 28 | status pgtype.Status 29 | wantErr bool 30 | }{ 31 | { 32 | name: "string", 33 | arg: "((3.14,1.678901234),(7.1,5.234),(5.0,3.234))", 34 | status: pgtype.Present, 35 | wantErr: false, 36 | }, { 37 | name: "[]float64", 38 | arg: []float64{1, 2, 3.45, 6.78, 1.23, 4.567, 8.9, 1.0}, 39 | status: pgtype.Present, 40 | wantErr: false, 41 | }, { 42 | name: "[]Vec2", 43 | arg: []pgtype.Vec2{{1, 2}, {2.3, 4.5}, {6.78, 9.123}}, 44 | status: pgtype.Present, 45 | wantErr: false, 46 | }, { 47 | name: "null", 48 | arg: nil, 49 | status: pgtype.Null, 50 | wantErr: false, 51 | }, { 52 | name: "invalid_string_1", 53 | arg: "((3.14,1.678901234),(7.1,5.234),(5.0,3.234x))", 54 | status: pgtype.Undefined, 55 | wantErr: true, 56 | }, { 57 | name: "invalid_string_2", 58 | arg: "(3,4)", 59 | status: pgtype.Undefined, 60 | wantErr: true, 61 | }, { 62 | name: "invalid_[]float64", 63 | arg: []float64{1, 2, 3.45, 6.78, 1.23, 4.567, 8.9}, 64 | status: pgtype.Undefined, 65 | wantErr: true, 66 | }, { 67 | name: "invalid_type", 68 | arg: []int{1, 2, 3, 6}, 69 | status: pgtype.Undefined, 70 | wantErr: true, 71 | }, { 72 | name: "empty_[]float64", 73 | arg: []float64{}, 74 | status: pgtype.Null, 75 | wantErr: false, 76 | }, 77 | } 78 | for _, tt := range tests { 79 | t.Run(tt.name, func(t *testing.T) { 80 | dst := &pgtype.Polygon{} 81 | if err := dst.Set(tt.arg); (err != nil) != tt.wantErr { 82 | t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr) 83 | } 84 | if dst.Status != tt.status { 85 | t.Errorf("Expected status: %v; got: %v", tt.status, dst.Status) 86 | } 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /qchar.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "strconv" 7 | ) 8 | 9 | // QChar is for PostgreSQL's special 8-bit-only "char" type more akin to the C 10 | // language's char type, or Go's byte type. (Note that the name in PostgreSQL 11 | // itself is "char", in double-quotes, and not char.) It gets used a lot in 12 | // PostgreSQL's system tables to hold a single ASCII character value (eg 13 | // pg_class.relkind). It is named Qchar for quoted char to disambiguate from SQL 14 | // standard type char. 15 | // 16 | // Not all possible values of QChar are representable in the text format. 17 | // Therefore, QChar does not implement TextEncoder and TextDecoder. In 18 | // addition, database/sql Scanner and database/sql/driver Value are not 19 | // implemented. 20 | type QChar struct { 21 | Int int8 22 | Status Status 23 | } 24 | 25 | func (dst *QChar) Set(src interface{}) error { 26 | if src == nil { 27 | *dst = QChar{Status: Null} 28 | return nil 29 | } 30 | 31 | if value, ok := src.(interface{ Get() interface{} }); ok { 32 | value2 := value.Get() 33 | if value2 != value { 34 | return dst.Set(value2) 35 | } 36 | } 37 | 38 | switch value := src.(type) { 39 | case int8: 40 | *dst = QChar{Int: value, Status: Present} 41 | case uint8: 42 | if value > math.MaxInt8 { 43 | return fmt.Errorf("%d is greater than maximum value for QChar", value) 44 | } 45 | *dst = QChar{Int: int8(value), Status: Present} 46 | case int16: 47 | if value < math.MinInt8 { 48 | return fmt.Errorf("%d is greater than maximum value for QChar", value) 49 | } 50 | if value > math.MaxInt8 { 51 | return fmt.Errorf("%d is greater than maximum value for QChar", value) 52 | } 53 | *dst = QChar{Int: int8(value), Status: Present} 54 | case uint16: 55 | if value > math.MaxInt8 { 56 | return fmt.Errorf("%d is greater than maximum value for QChar", value) 57 | } 58 | *dst = QChar{Int: int8(value), Status: Present} 59 | case int32: 60 | if value < math.MinInt8 { 61 | return fmt.Errorf("%d is greater than maximum value for QChar", value) 62 | } 63 | if value > math.MaxInt8 { 64 | return fmt.Errorf("%d is greater than maximum value for QChar", value) 65 | } 66 | *dst = QChar{Int: int8(value), Status: Present} 67 | case uint32: 68 | if value > math.MaxInt8 { 69 | return fmt.Errorf("%d is greater than maximum value for QChar", value) 70 | } 71 | *dst = QChar{Int: int8(value), Status: Present} 72 | case int64: 73 | if value < math.MinInt8 { 74 | return fmt.Errorf("%d is greater than maximum value for QChar", value) 75 | } 76 | if value > math.MaxInt8 { 77 | return fmt.Errorf("%d is greater than maximum value for QChar", value) 78 | } 79 | *dst = QChar{Int: int8(value), Status: Present} 80 | case uint64: 81 | if value > math.MaxInt8 { 82 | return fmt.Errorf("%d is greater than maximum value for QChar", value) 83 | } 84 | *dst = QChar{Int: int8(value), Status: Present} 85 | case int: 86 | if value < math.MinInt8 { 87 | return fmt.Errorf("%d is greater than maximum value for QChar", value) 88 | } 89 | if value > math.MaxInt8 { 90 | return fmt.Errorf("%d is greater than maximum value for QChar", value) 91 | } 92 | *dst = QChar{Int: int8(value), Status: Present} 93 | case uint: 94 | if value > math.MaxInt8 { 95 | return fmt.Errorf("%d is greater than maximum value for QChar", value) 96 | } 97 | *dst = QChar{Int: int8(value), Status: Present} 98 | case string: 99 | num, err := strconv.ParseInt(value, 10, 8) 100 | if err != nil { 101 | return err 102 | } 103 | *dst = QChar{Int: int8(num), Status: Present} 104 | default: 105 | if originalSrc, ok := underlyingNumberType(src); ok { 106 | return dst.Set(originalSrc) 107 | } 108 | return fmt.Errorf("cannot convert %v to QChar", value) 109 | } 110 | 111 | return nil 112 | } 113 | 114 | func (dst QChar) Get() interface{} { 115 | switch dst.Status { 116 | case Present: 117 | return dst.Int 118 | case Null: 119 | return nil 120 | default: 121 | return dst.Status 122 | } 123 | } 124 | 125 | func (src *QChar) AssignTo(dst interface{}) error { 126 | return int64AssignTo(int64(src.Int), src.Status, dst) 127 | } 128 | 129 | func (dst *QChar) DecodeBinary(ci *ConnInfo, src []byte) error { 130 | if src == nil { 131 | *dst = QChar{Status: Null} 132 | return nil 133 | } 134 | 135 | if len(src) != 1 { 136 | return fmt.Errorf(`invalid length for "char": %v`, len(src)) 137 | } 138 | 139 | *dst = QChar{Int: int8(src[0]), Status: Present} 140 | return nil 141 | } 142 | 143 | func (src QChar) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 144 | switch src.Status { 145 | case Null: 146 | return nil, nil 147 | case Undefined: 148 | return nil, errUndefined 149 | } 150 | 151 | return append(buf, byte(src.Int)), nil 152 | } 153 | -------------------------------------------------------------------------------- /record.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Record is the generic PostgreSQL record type such as is created with the 9 | // "row" function. Record only implements BinaryDecoder and Value. The text 10 | // format output format from PostgreSQL does not include type information and is 11 | // therefore impossible to decode. No encoders are implemented because 12 | // PostgreSQL does not support input of generic records. 13 | type Record struct { 14 | Fields []Value 15 | Status Status 16 | } 17 | 18 | func (dst *Record) Set(src interface{}) error { 19 | if src == nil { 20 | *dst = Record{Status: Null} 21 | return nil 22 | } 23 | 24 | if value, ok := src.(interface{ Get() interface{} }); ok { 25 | value2 := value.Get() 26 | if value2 != value { 27 | return dst.Set(value2) 28 | } 29 | } 30 | 31 | switch value := src.(type) { 32 | case []Value: 33 | *dst = Record{Fields: value, Status: Present} 34 | default: 35 | return fmt.Errorf("cannot convert %v to Record", src) 36 | } 37 | 38 | return nil 39 | } 40 | 41 | func (dst Record) Get() interface{} { 42 | switch dst.Status { 43 | case Present: 44 | return dst.Fields 45 | case Null: 46 | return nil 47 | default: 48 | return dst.Status 49 | } 50 | } 51 | 52 | func (src *Record) AssignTo(dst interface{}) error { 53 | switch src.Status { 54 | case Present: 55 | switch v := dst.(type) { 56 | case *[]Value: 57 | *v = make([]Value, len(src.Fields)) 58 | copy(*v, src.Fields) 59 | return nil 60 | case *[]interface{}: 61 | *v = make([]interface{}, len(src.Fields)) 62 | for i := range *v { 63 | (*v)[i] = src.Fields[i].Get() 64 | } 65 | return nil 66 | default: 67 | if nextDst, retry := GetAssignToDstType(dst); retry { 68 | return src.AssignTo(nextDst) 69 | } 70 | return fmt.Errorf("unable to assign to %T", dst) 71 | } 72 | case Null: 73 | return NullAssignTo(dst) 74 | } 75 | 76 | return fmt.Errorf("cannot decode %#v into %T", src, dst) 77 | } 78 | 79 | func prepareNewBinaryDecoder(ci *ConnInfo, fieldOID uint32, v *Value) (BinaryDecoder, error) { 80 | var binaryDecoder BinaryDecoder 81 | 82 | if dt, ok := ci.DataTypeForOID(fieldOID); ok { 83 | binaryDecoder, _ = dt.Value.(BinaryDecoder) 84 | } else { 85 | return nil, fmt.Errorf("unknown oid while decoding record: %v", fieldOID) 86 | } 87 | 88 | if binaryDecoder == nil { 89 | return nil, fmt.Errorf("no binary decoder registered for: %v", fieldOID) 90 | } 91 | 92 | // Duplicate struct to scan into 93 | binaryDecoder = reflect.New(reflect.ValueOf(binaryDecoder).Elem().Type()).Interface().(BinaryDecoder) 94 | *v = binaryDecoder.(Value) 95 | return binaryDecoder, nil 96 | } 97 | 98 | func (dst *Record) DecodeBinary(ci *ConnInfo, src []byte) error { 99 | if src == nil { 100 | *dst = Record{Status: Null} 101 | return nil 102 | } 103 | 104 | scanner := NewCompositeBinaryScanner(ci, src) 105 | 106 | fields := make([]Value, scanner.FieldCount()) 107 | 108 | for i := 0; scanner.Next(); i++ { 109 | binaryDecoder, err := prepareNewBinaryDecoder(ci, scanner.OID(), &fields[i]) 110 | if err != nil { 111 | return err 112 | } 113 | 114 | if err = binaryDecoder.DecodeBinary(ci, scanner.Bytes()); err != nil { 115 | return err 116 | } 117 | } 118 | 119 | if scanner.Err() != nil { 120 | return scanner.Err() 121 | } 122 | 123 | *dst = Record{Fields: fields, Status: Present} 124 | 125 | return nil 126 | } 127 | -------------------------------------------------------------------------------- /record_array_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | 10 | "github.com/jackc/pgtype" 11 | "github.com/jackc/pgtype/testutil" 12 | "github.com/jackc/pgx/v4" 13 | ) 14 | 15 | var recordArrayTests = []struct { 16 | sql string 17 | expected pgtype.RecordArray 18 | }{ 19 | { 20 | sql: `select array_agg((x::int4, x+100::int8)) from generate_series(0, 1) x;`, 21 | expected: pgtype.RecordArray{ 22 | Dimensions: []pgtype.ArrayDimension{ 23 | {LowerBound: 1, Length: 2}, 24 | }, 25 | Elements: []pgtype.Record{ 26 | { 27 | Fields: []pgtype.Value{ 28 | &pgtype.Int4{Int: 0, Status: pgtype.Present}, 29 | &pgtype.Int8{Int: 100, Status: pgtype.Present}, 30 | }, 31 | Status: pgtype.Present, 32 | }, 33 | { 34 | Fields: []pgtype.Value{ 35 | &pgtype.Int4{Int: 1, Status: pgtype.Present}, 36 | &pgtype.Int8{Int: 101, Status: pgtype.Present}, 37 | }, 38 | Status: pgtype.Present, 39 | }, 40 | }, 41 | Status: pgtype.Present, 42 | }, 43 | }, 44 | } 45 | 46 | func TestRecordArrayTranscode(t *testing.T) { 47 | conn := testutil.MustConnectPgx(t) 48 | defer testutil.MustCloseContext(t, conn) 49 | 50 | for i, tt := range recordArrayTests { 51 | psName := fmt.Sprintf("test%d", i) 52 | _, err := conn.Prepare(context.Background(), psName, tt.sql) 53 | require.NoError(t, err) 54 | 55 | t.Run(tt.sql, func(t *testing.T) { 56 | var result pgtype.RecordArray 57 | err := conn.QueryRow(context.Background(), psName, pgx.QueryResultFormats{pgx.BinaryFormatCode}).Scan(&result) 58 | require.NoError(t, err) 59 | 60 | require.Equal(t, tt.expected, result) 61 | }) 62 | 63 | } 64 | } 65 | 66 | func TestRecordArrayAssignTo(t *testing.T) { 67 | src := pgtype.RecordArray{ 68 | Dimensions: []pgtype.ArrayDimension{ 69 | {LowerBound: 1, Length: 2}, 70 | }, 71 | Elements: []pgtype.Record{ 72 | { 73 | Fields: []pgtype.Value{ 74 | &pgtype.Int4{Int: 0, Status: pgtype.Present}, 75 | &pgtype.Int8{Int: 100, Status: pgtype.Present}, 76 | }, 77 | Status: pgtype.Present, 78 | }, 79 | { 80 | Fields: []pgtype.Value{ 81 | &pgtype.Int4{Int: 1, Status: pgtype.Present}, 82 | &pgtype.Int8{Int: 101, Status: pgtype.Present}, 83 | }, 84 | Status: pgtype.Present, 85 | }, 86 | }, 87 | Status: pgtype.Present, 88 | } 89 | dst := [][]pgtype.Value{} 90 | err := src.AssignTo(&dst) 91 | require.NoError(t, err) 92 | 93 | expected := [][]pgtype.Value{ 94 | { 95 | &pgtype.Int4{Int: 0, Status: pgtype.Present}, 96 | &pgtype.Int8{Int: 100, Status: pgtype.Present}, 97 | }, 98 | { 99 | &pgtype.Int4{Int: 1, Status: pgtype.Present}, 100 | &pgtype.Int8{Int: 101, Status: pgtype.Present}, 101 | }, 102 | } 103 | require.Equal(t, expected, dst) 104 | } 105 | -------------------------------------------------------------------------------- /text_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/jackc/pgtype" 9 | "github.com/jackc/pgtype/testutil" 10 | ) 11 | 12 | func TestTextTranscode(t *testing.T) { 13 | for _, pgTypeName := range []string{"text", "varchar"} { 14 | testutil.TestSuccessfulTranscode(t, pgTypeName, []interface{}{ 15 | &pgtype.Text{String: "", Status: pgtype.Present}, 16 | &pgtype.Text{String: "foo", Status: pgtype.Present}, 17 | &pgtype.Text{Status: pgtype.Null}, 18 | }) 19 | } 20 | } 21 | 22 | func TestTextSet(t *testing.T) { 23 | successfulTests := []struct { 24 | source interface{} 25 | result pgtype.Text 26 | }{ 27 | {source: "foo", result: pgtype.Text{String: "foo", Status: pgtype.Present}}, 28 | {source: _string("bar"), result: pgtype.Text{String: "bar", Status: pgtype.Present}}, 29 | {source: (*string)(nil), result: pgtype.Text{Status: pgtype.Null}}, 30 | } 31 | 32 | for i, tt := range successfulTests { 33 | var d pgtype.Text 34 | err := d.Set(tt.source) 35 | if err != nil { 36 | t.Errorf("%d: %v", i, err) 37 | } 38 | 39 | if d != tt.result { 40 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, d) 41 | } 42 | } 43 | } 44 | 45 | func TestTextAssignTo(t *testing.T) { 46 | var s string 47 | var ps *string 48 | 49 | stringTests := []struct { 50 | src pgtype.Text 51 | dst interface{} 52 | expected interface{} 53 | }{ 54 | {src: pgtype.Text{String: "foo", Status: pgtype.Present}, dst: &s, expected: "foo"}, 55 | {src: pgtype.Text{Status: pgtype.Null}, dst: &ps, expected: ((*string)(nil))}, 56 | } 57 | 58 | for i, tt := range stringTests { 59 | err := tt.src.AssignTo(tt.dst) 60 | if err != nil { 61 | t.Errorf("%d: %v", i, err) 62 | } 63 | 64 | if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected { 65 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 66 | } 67 | } 68 | 69 | var buf []byte 70 | 71 | bytesTests := []struct { 72 | src pgtype.Text 73 | dst *[]byte 74 | expected []byte 75 | }{ 76 | {src: pgtype.Text{String: "foo", Status: pgtype.Present}, dst: &buf, expected: []byte("foo")}, 77 | {src: pgtype.Text{Status: pgtype.Null}, dst: &buf, expected: nil}, 78 | } 79 | 80 | for i, tt := range bytesTests { 81 | err := tt.src.AssignTo(tt.dst) 82 | if err != nil { 83 | t.Errorf("%d: %v", i, err) 84 | } 85 | 86 | if !bytes.Equal(*tt.dst, tt.expected) { 87 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, tt.dst) 88 | } 89 | } 90 | 91 | pointerAllocTests := []struct { 92 | src pgtype.Text 93 | dst interface{} 94 | expected interface{} 95 | }{ 96 | {src: pgtype.Text{String: "foo", Status: pgtype.Present}, dst: &ps, expected: "foo"}, 97 | } 98 | 99 | for i, tt := range pointerAllocTests { 100 | err := tt.src.AssignTo(tt.dst) 101 | if err != nil { 102 | t.Errorf("%d: %v", i, err) 103 | } 104 | 105 | if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected { 106 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 107 | } 108 | } 109 | 110 | errorTests := []struct { 111 | src pgtype.Text 112 | dst interface{} 113 | }{ 114 | {src: pgtype.Text{Status: pgtype.Null}, dst: &s}, 115 | } 116 | 117 | for i, tt := range errorTests { 118 | err := tt.src.AssignTo(tt.dst) 119 | if err == nil { 120 | t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst) 121 | } 122 | } 123 | } 124 | 125 | func TestTextMarshalJSON(t *testing.T) { 126 | successfulTests := []struct { 127 | source pgtype.Text 128 | result string 129 | }{ 130 | {source: pgtype.Text{String: "", Status: pgtype.Null}, result: "null"}, 131 | {source: pgtype.Text{String: "a", Status: pgtype.Present}, result: "\"a\""}, 132 | } 133 | for i, tt := range successfulTests { 134 | r, err := tt.source.MarshalJSON() 135 | if err != nil { 136 | t.Errorf("%d: %v", i, err) 137 | } 138 | 139 | if string(r) != tt.result { 140 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r)) 141 | } 142 | } 143 | } 144 | 145 | func TestTextUnmarshalJSON(t *testing.T) { 146 | successfulTests := []struct { 147 | source string 148 | result pgtype.Text 149 | }{ 150 | {source: "null", result: pgtype.Text{String: "", Status: pgtype.Null}}, 151 | {source: "\"a\"", result: pgtype.Text{String: "a", Status: pgtype.Present}}, 152 | } 153 | for i, tt := range successfulTests { 154 | var r pgtype.Text 155 | err := r.UnmarshalJSON([]byte(tt.source)) 156 | if err != nil { 157 | t.Errorf("%d: %v", i, err) 158 | } 159 | 160 | if r != tt.result { 161 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /tid.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/binary" 6 | "fmt" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/jackc/pgio" 11 | ) 12 | 13 | // TID is PostgreSQL's Tuple Identifier type. 14 | // 15 | // When one does 16 | // 17 | // select ctid, * from some_table; 18 | // 19 | // it is the data type of the ctid hidden system column. 20 | // 21 | // It is currently implemented as a pair unsigned two byte integers. 22 | // Its conversion functions can be found in src/backend/utils/adt/tid.c 23 | // in the PostgreSQL sources. 24 | type TID struct { 25 | BlockNumber uint32 26 | OffsetNumber uint16 27 | Status Status 28 | } 29 | 30 | func (dst *TID) Set(src interface{}) error { 31 | return fmt.Errorf("cannot convert %v to TID", src) 32 | } 33 | 34 | func (dst TID) Get() interface{} { 35 | switch dst.Status { 36 | case Present: 37 | return dst 38 | case Null: 39 | return nil 40 | default: 41 | return dst.Status 42 | } 43 | } 44 | 45 | func (src *TID) AssignTo(dst interface{}) error { 46 | if src.Status == Present { 47 | switch v := dst.(type) { 48 | case *string: 49 | *v = fmt.Sprintf(`(%d,%d)`, src.BlockNumber, src.OffsetNumber) 50 | return nil 51 | default: 52 | if nextDst, retry := GetAssignToDstType(dst); retry { 53 | return src.AssignTo(nextDst) 54 | } 55 | return fmt.Errorf("unable to assign to %T", dst) 56 | } 57 | } 58 | 59 | return fmt.Errorf("cannot assign %v to %T", src, dst) 60 | } 61 | 62 | func (dst *TID) DecodeText(ci *ConnInfo, src []byte) error { 63 | if src == nil { 64 | *dst = TID{Status: Null} 65 | return nil 66 | } 67 | 68 | if len(src) < 5 { 69 | return fmt.Errorf("invalid length for tid: %v", len(src)) 70 | } 71 | 72 | parts := strings.SplitN(string(src[1:len(src)-1]), ",", 2) 73 | if len(parts) < 2 { 74 | return fmt.Errorf("invalid format for tid") 75 | } 76 | 77 | blockNumber, err := strconv.ParseUint(parts[0], 10, 32) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | offsetNumber, err := strconv.ParseUint(parts[1], 10, 16) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | *dst = TID{BlockNumber: uint32(blockNumber), OffsetNumber: uint16(offsetNumber), Status: Present} 88 | return nil 89 | } 90 | 91 | func (dst *TID) DecodeBinary(ci *ConnInfo, src []byte) error { 92 | if src == nil { 93 | *dst = TID{Status: Null} 94 | return nil 95 | } 96 | 97 | if len(src) != 6 { 98 | return fmt.Errorf("invalid length for tid: %v", len(src)) 99 | } 100 | 101 | *dst = TID{ 102 | BlockNumber: binary.BigEndian.Uint32(src), 103 | OffsetNumber: binary.BigEndian.Uint16(src[4:]), 104 | Status: Present, 105 | } 106 | return nil 107 | } 108 | 109 | func (src TID) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 110 | switch src.Status { 111 | case Null: 112 | return nil, nil 113 | case Undefined: 114 | return nil, errUndefined 115 | } 116 | 117 | buf = append(buf, fmt.Sprintf(`(%d,%d)`, src.BlockNumber, src.OffsetNumber)...) 118 | return buf, nil 119 | } 120 | 121 | func (src TID) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 122 | switch src.Status { 123 | case Null: 124 | return nil, nil 125 | case Undefined: 126 | return nil, errUndefined 127 | } 128 | 129 | buf = pgio.AppendUint32(buf, src.BlockNumber) 130 | buf = pgio.AppendUint16(buf, src.OffsetNumber) 131 | return buf, nil 132 | } 133 | 134 | // Scan implements the database/sql Scanner interface. 135 | func (dst *TID) Scan(src interface{}) error { 136 | if src == nil { 137 | *dst = TID{Status: Null} 138 | return nil 139 | } 140 | 141 | switch src := src.(type) { 142 | case string: 143 | return dst.DecodeText(nil, []byte(src)) 144 | case []byte: 145 | srcCopy := make([]byte, len(src)) 146 | copy(srcCopy, src) 147 | return dst.DecodeText(nil, srcCopy) 148 | } 149 | 150 | return fmt.Errorf("cannot scan %T", src) 151 | } 152 | 153 | // Value implements the database/sql/driver Valuer interface. 154 | func (src TID) Value() (driver.Value, error) { 155 | return EncodeValueText(src) 156 | } 157 | -------------------------------------------------------------------------------- /tid_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | ) 10 | 11 | func TestTIDTranscode(t *testing.T) { 12 | testutil.TestSuccessfulTranscode(t, "tid", []interface{}{ 13 | &pgtype.TID{BlockNumber: 42, OffsetNumber: 43, Status: pgtype.Present}, 14 | &pgtype.TID{BlockNumber: 4294967295, OffsetNumber: 65535, Status: pgtype.Present}, 15 | &pgtype.TID{Status: pgtype.Null}, 16 | }) 17 | } 18 | 19 | func TestTIDAssignTo(t *testing.T) { 20 | var s string 21 | var sp *string 22 | 23 | simpleTests := []struct { 24 | src pgtype.TID 25 | dst interface{} 26 | expected interface{} 27 | }{ 28 | {src: pgtype.TID{BlockNumber: 42, OffsetNumber: 43, Status: pgtype.Present}, dst: &s, expected: "(42,43)"}, 29 | {src: pgtype.TID{BlockNumber: 4294967295, OffsetNumber: 65535, Status: pgtype.Present}, dst: &s, expected: "(4294967295,65535)"}, 30 | } 31 | 32 | for i, tt := range simpleTests { 33 | err := tt.src.AssignTo(tt.dst) 34 | if err != nil { 35 | t.Errorf("%d: %v", i, err) 36 | } 37 | 38 | if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected { 39 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 40 | } 41 | } 42 | 43 | pointerAllocTests := []struct { 44 | src pgtype.TID 45 | dst interface{} 46 | expected interface{} 47 | }{ 48 | {src: pgtype.TID{BlockNumber: 42, OffsetNumber: 43, Status: pgtype.Present}, dst: &sp, expected: "(42,43)"}, 49 | {src: pgtype.TID{BlockNumber: 4294967295, OffsetNumber: 65535, Status: pgtype.Present}, dst: &sp, expected: "(4294967295,65535)"}, 50 | } 51 | 52 | for i, tt := range pointerAllocTests { 53 | err := tt.src.AssignTo(tt.dst) 54 | if err != nil { 55 | t.Errorf("%d: %v", i, err) 56 | } 57 | 58 | if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected { 59 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 60 | } 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /tsrange_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | ) 10 | 11 | func TestTsrangeTranscode(t *testing.T) { 12 | testutil.TestSuccessfulTranscodeEqFunc(t, "tsrange", []interface{}{ 13 | &pgtype.Tsrange{LowerType: pgtype.Empty, UpperType: pgtype.Empty, Status: pgtype.Present}, 14 | &pgtype.Tsrange{ 15 | Lower: pgtype.Timestamp{Time: time.Date(1990, 12, 31, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}, 16 | Upper: pgtype.Timestamp{Time: time.Date(2028, 1, 1, 0, 23, 12, 0, time.UTC), Status: pgtype.Present}, 17 | LowerType: pgtype.Inclusive, 18 | UpperType: pgtype.Exclusive, 19 | Status: pgtype.Present, 20 | }, 21 | &pgtype.Tsrange{ 22 | Lower: pgtype.Timestamp{Time: time.Date(1800, 12, 31, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}, 23 | Upper: pgtype.Timestamp{Time: time.Date(2200, 1, 1, 0, 23, 12, 0, time.UTC), Status: pgtype.Present}, 24 | LowerType: pgtype.Inclusive, 25 | UpperType: pgtype.Exclusive, 26 | Status: pgtype.Present, 27 | }, 28 | &pgtype.Tsrange{Status: pgtype.Null}, 29 | }, func(aa, bb interface{}) bool { 30 | a := aa.(pgtype.Tsrange) 31 | b := bb.(pgtype.Tsrange) 32 | 33 | return a.Status == b.Status && 34 | a.Lower.Time.Equal(b.Lower.Time) && 35 | a.Lower.Status == b.Lower.Status && 36 | a.Lower.InfinityModifier == b.Lower.InfinityModifier && 37 | a.Upper.Time.Equal(b.Upper.Time) && 38 | a.Upper.Status == b.Upper.Status && 39 | a.Upper.InfinityModifier == b.Upper.InfinityModifier 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /tstzrange_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestTstzrangeTranscode(t *testing.T) { 13 | testutil.TestSuccessfulTranscodeEqFunc(t, "tstzrange", []interface{}{ 14 | &pgtype.Tstzrange{LowerType: pgtype.Empty, UpperType: pgtype.Empty, Status: pgtype.Present}, 15 | &pgtype.Tstzrange{ 16 | Lower: pgtype.Timestamptz{Time: time.Date(1990, 12, 31, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}, 17 | Upper: pgtype.Timestamptz{Time: time.Date(2028, 1, 1, 0, 23, 12, 0, time.UTC), Status: pgtype.Present}, 18 | LowerType: pgtype.Inclusive, 19 | UpperType: pgtype.Exclusive, 20 | Status: pgtype.Present, 21 | }, 22 | &pgtype.Tstzrange{ 23 | Lower: pgtype.Timestamptz{Time: time.Date(1800, 12, 31, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}, 24 | Upper: pgtype.Timestamptz{Time: time.Date(2200, 1, 1, 0, 23, 12, 0, time.UTC), Status: pgtype.Present}, 25 | LowerType: pgtype.Inclusive, 26 | UpperType: pgtype.Exclusive, 27 | Status: pgtype.Present, 28 | }, 29 | &pgtype.Tstzrange{Status: pgtype.Null}, 30 | }, func(aa, bb interface{}) bool { 31 | a := aa.(pgtype.Tstzrange) 32 | b := bb.(pgtype.Tstzrange) 33 | 34 | return a.Status == b.Status && 35 | a.Lower.Time.Equal(b.Lower.Time) && 36 | a.Lower.Status == b.Lower.Status && 37 | a.Lower.InfinityModifier == b.Lower.InfinityModifier && 38 | a.Upper.Time.Equal(b.Upper.Time) && 39 | a.Upper.Status == b.Upper.Status && 40 | a.Upper.InfinityModifier == b.Upper.InfinityModifier 41 | }) 42 | } 43 | 44 | // https://github.com/jackc/pgtype/issues/74 45 | func TestTstzRangeDecodeTextInvalid(t *testing.T) { 46 | tstzrange := &pgtype.Tstzrange{} 47 | err := tstzrange.DecodeText(nil, []byte(`[eeee,)`)) 48 | require.Error(t, err) 49 | } 50 | -------------------------------------------------------------------------------- /typed_array_gen.sh: -------------------------------------------------------------------------------- 1 | erb pgtype_array_type=Int2Array pgtype_element_type=Int2 go_array_types=[]int16,[]*int16,[]uint16,[]*uint16,[]int32,[]*int32,[]uint32,[]*uint32,[]int64,[]*int64,[]uint64,[]*uint64,[]int,[]*int,[]uint,[]*uint element_type_name=int2 typed_array.go.erb > int2_array.go 2 | erb pgtype_array_type=Int4Array pgtype_element_type=Int4 go_array_types=[]int16,[]*int16,[]uint16,[]*uint16,[]int32,[]*int32,[]uint32,[]*uint32,[]int64,[]*int64,[]uint64,[]*uint64,[]int,[]*int,[]uint,[]*uint element_type_name=int4 typed_array.go.erb > int4_array.go 3 | erb pgtype_array_type=Int8Array pgtype_element_type=Int8 go_array_types=[]int16,[]*int16,[]uint16,[]*uint16,[]int32,[]*int32,[]uint32,[]*uint32,[]int64,[]*int64,[]uint64,[]*uint64,[]int,[]*int,[]uint,[]*uint element_type_name=int8 typed_array.go.erb > int8_array.go 4 | erb pgtype_array_type=BoolArray pgtype_element_type=Bool go_array_types=[]bool,[]*bool element_type_name=bool typed_array.go.erb > bool_array.go 5 | erb pgtype_array_type=DateArray pgtype_element_type=Date go_array_types=[]time.Time,[]*time.Time element_type_name=date typed_array.go.erb > date_array.go 6 | erb pgtype_array_type=TimestamptzArray pgtype_element_type=Timestamptz go_array_types=[]time.Time,[]*time.Time element_type_name=timestamptz typed_array.go.erb > timestamptz_array.go 7 | erb pgtype_array_type=TstzrangeArray pgtype_element_type=Tstzrange go_array_types=[]Tstzrange element_type_name=tstzrange typed_array.go.erb > tstzrange_array.go 8 | erb pgtype_array_type=TsrangeArray pgtype_element_type=Tsrange go_array_types=[]Tsrange element_type_name=tsrange typed_array.go.erb > tsrange_array.go 9 | erb pgtype_array_type=TimestampArray pgtype_element_type=Timestamp go_array_types=[]time.Time,[]*time.Time element_type_name=timestamp typed_array.go.erb > timestamp_array.go 10 | erb pgtype_array_type=Float4Array pgtype_element_type=Float4 go_array_types=[]float32,[]*float32 element_type_name=float4 typed_array.go.erb > float4_array.go 11 | erb pgtype_array_type=Float8Array pgtype_element_type=Float8 go_array_types=[]float64,[]*float64 element_type_name=float8 typed_array.go.erb > float8_array.go 12 | erb pgtype_array_type=InetArray pgtype_element_type=Inet go_array_types=[]*net.IPNet,[]net.IP,[]*net.IP element_type_name=inet typed_array.go.erb > inet_array.go 13 | erb pgtype_array_type=MacaddrArray pgtype_element_type=Macaddr go_array_types=[]net.HardwareAddr,[]*net.HardwareAddr element_type_name=macaddr typed_array.go.erb > macaddr_array.go 14 | erb pgtype_array_type=CIDRArray pgtype_element_type=CIDR go_array_types=[]*net.IPNet,[]net.IP,[]*net.IP element_type_name=cidr typed_array.go.erb > cidr_array.go 15 | erb pgtype_array_type=TextArray pgtype_element_type=Text go_array_types=[]string,[]*string element_type_name=text typed_array.go.erb > text_array.go 16 | erb pgtype_array_type=VarcharArray pgtype_element_type=Varchar go_array_types=[]string,[]*string element_type_name=varchar typed_array.go.erb > varchar_array.go 17 | erb pgtype_array_type=BPCharArray pgtype_element_type=BPChar go_array_types=[]string,[]*string element_type_name=bpchar typed_array.go.erb > bpchar_array.go 18 | erb pgtype_array_type=ByteaArray pgtype_element_type=Bytea go_array_types=[][]byte element_type_name=bytea typed_array.go.erb > bytea_array.go 19 | erb pgtype_array_type=ACLItemArray pgtype_element_type=ACLItem go_array_types=[]string,[]*string element_type_name=aclitem binary_format=false typed_array.go.erb > aclitem_array.go 20 | erb pgtype_array_type=HstoreArray pgtype_element_type=Hstore go_array_types=[]map[string]string element_type_name=hstore typed_array.go.erb > hstore_array.go 21 | erb pgtype_array_type=NumericArray pgtype_element_type=Numeric go_array_types=[]float32,[]*float32,[]float64,[]*float64,[]int64,[]*int64,[]uint64,[]*uint64 element_type_name=numeric typed_array.go.erb > numeric_array.go 22 | erb pgtype_array_type=UUIDArray pgtype_element_type=UUID go_array_types=[][16]byte,[][]byte,[]string,[]*string element_type_name=uuid typed_array.go.erb > uuid_array.go 23 | erb pgtype_array_type=JSONArray pgtype_element_type=JSON go_array_types=[]string,[][]byte,[]json.RawMessage element_type_name=json typed_array.go.erb > json_array.go 24 | erb pgtype_array_type=JSONBArray pgtype_element_type=JSONB go_array_types=[]string,[][]byte,[]json.RawMessage element_type_name=jsonb typed_array.go.erb > jsonb_array.go 25 | 26 | # While the binary format is theoretically possible it is only practical to use the text format. 27 | erb pgtype_array_type=EnumArray pgtype_element_type=GenericText go_array_types=[]string,[]*string binary_format=false typed_array.go.erb > enum_array.go 28 | 29 | erb pgtype_array_type=RecordArray pgtype_element_type=Record go_array_types=[][]Value element_type_name=record text_null=NULL encode_binary=false text_format=false typed_array.go.erb > record_array.go 30 | 31 | goimports -w *_array.go 32 | -------------------------------------------------------------------------------- /typed_multirange_gen.sh: -------------------------------------------------------------------------------- 1 | erb range_type=Numrange multirange_type=Nummultirange typed_multirange.go.erb > num_multirange.go 2 | erb range_type=Int4range multirange_type=Int4multirange typed_multirange.go.erb > int4_multirange.go 3 | erb range_type=Int8range multirange_type=Int8multirange typed_multirange.go.erb > int8_multirange.go 4 | # TODO 5 | # erb range_type=Tsrange multirange_type=Tsmultirange typed_multirange.go.erb > ts_multirange.go 6 | # erb range_type=Tstzrange multirange_type=Tstzmultirange typed_multirange.go.erb > tstz_multirange.go 7 | # erb range_type=Daterange multirange_type=Datemultirange typed_multirange.go.erb > date_multirange.go 8 | goimports -w *multirange.go -------------------------------------------------------------------------------- /typed_range_gen.sh: -------------------------------------------------------------------------------- 1 | erb range_type=Int4range element_type=Int4 typed_range.go.erb > int4range.go 2 | erb range_type=Int8range element_type=Int8 typed_range.go.erb > int8range.go 3 | erb range_type=Tsrange element_type=Timestamp typed_range.go.erb > tsrange.go 4 | erb range_type=Tstzrange element_type=Timestamptz typed_range.go.erb > tstzrange.go 5 | erb range_type=Daterange element_type=Date typed_range.go.erb > daterange.go 6 | erb range_type=Numrange element_type=Numeric typed_range.go.erb > numrange.go 7 | goimports -w *range.go 8 | -------------------------------------------------------------------------------- /unknown.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import "database/sql/driver" 4 | 5 | // Unknown represents the PostgreSQL unknown type. It is either a string literal 6 | // or NULL. It is used when PostgreSQL does not know the type of a value. In 7 | // general, this will only be used in pgx when selecting a null value without 8 | // type information. e.g. SELECT NULL; 9 | type Unknown struct { 10 | String string 11 | Status Status 12 | } 13 | 14 | func (dst *Unknown) Set(src interface{}) error { 15 | return (*Text)(dst).Set(src) 16 | } 17 | 18 | func (dst Unknown) Get() interface{} { 19 | return (Text)(dst).Get() 20 | } 21 | 22 | // AssignTo assigns from src to dst. Note that as Unknown is not a general number 23 | // type AssignTo does not do automatic type conversion as other number types do. 24 | func (src *Unknown) AssignTo(dst interface{}) error { 25 | return (*Text)(src).AssignTo(dst) 26 | } 27 | 28 | func (dst *Unknown) DecodeText(ci *ConnInfo, src []byte) error { 29 | return (*Text)(dst).DecodeText(ci, src) 30 | } 31 | 32 | func (dst *Unknown) DecodeBinary(ci *ConnInfo, src []byte) error { 33 | return (*Text)(dst).DecodeBinary(ci, src) 34 | } 35 | 36 | // Scan implements the database/sql Scanner interface. 37 | func (dst *Unknown) Scan(src interface{}) error { 38 | return (*Text)(dst).Scan(src) 39 | } 40 | 41 | // Value implements the database/sql/driver Valuer interface. 42 | func (src Unknown) Value() (driver.Value, error) { 43 | return (Text)(src).Value() 44 | } 45 | -------------------------------------------------------------------------------- /varbit.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/binary" 6 | "fmt" 7 | 8 | "github.com/jackc/pgio" 9 | ) 10 | 11 | type Varbit struct { 12 | Bytes []byte 13 | Len int32 // Number of bits 14 | Status Status 15 | } 16 | 17 | func (dst *Varbit) Set(src interface{}) error { 18 | return fmt.Errorf("cannot convert %v to Varbit", src) 19 | } 20 | 21 | func (dst Varbit) Get() interface{} { 22 | switch dst.Status { 23 | case Present: 24 | return dst 25 | case Null: 26 | return nil 27 | default: 28 | return dst.Status 29 | } 30 | } 31 | 32 | func (src *Varbit) AssignTo(dst interface{}) error { 33 | return fmt.Errorf("cannot assign %v to %T", src, dst) 34 | } 35 | 36 | func (dst *Varbit) DecodeText(ci *ConnInfo, src []byte) error { 37 | if src == nil { 38 | *dst = Varbit{Status: Null} 39 | return nil 40 | } 41 | 42 | bitLen := len(src) 43 | byteLen := bitLen / 8 44 | if bitLen%8 > 0 { 45 | byteLen++ 46 | } 47 | buf := make([]byte, byteLen) 48 | 49 | for i, b := range src { 50 | if b == '1' { 51 | byteIdx := i / 8 52 | bitIdx := uint(i % 8) 53 | buf[byteIdx] = buf[byteIdx] | (128 >> bitIdx) 54 | } 55 | } 56 | 57 | *dst = Varbit{Bytes: buf, Len: int32(bitLen), Status: Present} 58 | return nil 59 | } 60 | 61 | func (dst *Varbit) DecodeBinary(ci *ConnInfo, src []byte) error { 62 | if src == nil { 63 | *dst = Varbit{Status: Null} 64 | return nil 65 | } 66 | 67 | if len(src) < 4 { 68 | return fmt.Errorf("invalid length for varbit: %v", len(src)) 69 | } 70 | 71 | bitLen := int32(binary.BigEndian.Uint32(src)) 72 | rp := 4 73 | 74 | *dst = Varbit{Bytes: src[rp:], Len: bitLen, Status: Present} 75 | return nil 76 | } 77 | 78 | func (src Varbit) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 79 | switch src.Status { 80 | case Null: 81 | return nil, nil 82 | case Undefined: 83 | return nil, errUndefined 84 | } 85 | 86 | for i := int32(0); i < src.Len; i++ { 87 | byteIdx := i / 8 88 | bitMask := byte(128 >> byte(i%8)) 89 | char := byte('0') 90 | if src.Bytes[byteIdx]&bitMask > 0 { 91 | char = '1' 92 | } 93 | buf = append(buf, char) 94 | } 95 | 96 | return buf, nil 97 | } 98 | 99 | func (src Varbit) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 100 | switch src.Status { 101 | case Null: 102 | return nil, nil 103 | case Undefined: 104 | return nil, errUndefined 105 | } 106 | 107 | buf = pgio.AppendInt32(buf, src.Len) 108 | return append(buf, src.Bytes...), nil 109 | } 110 | 111 | // Scan implements the database/sql Scanner interface. 112 | func (dst *Varbit) Scan(src interface{}) error { 113 | if src == nil { 114 | *dst = Varbit{Status: Null} 115 | return nil 116 | } 117 | 118 | switch src := src.(type) { 119 | case string: 120 | return dst.DecodeText(nil, []byte(src)) 121 | case []byte: 122 | srcCopy := make([]byte, len(src)) 123 | copy(srcCopy, src) 124 | return dst.DecodeText(nil, srcCopy) 125 | } 126 | 127 | return fmt.Errorf("cannot scan %T", src) 128 | } 129 | 130 | // Value implements the database/sql/driver Valuer interface. 131 | func (src Varbit) Value() (driver.Value, error) { 132 | return EncodeValueText(src) 133 | } 134 | -------------------------------------------------------------------------------- /varbit_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype" 7 | "github.com/jackc/pgtype/testutil" 8 | ) 9 | 10 | func TestVarbitTranscode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "varbit", []interface{}{ 12 | &pgtype.Varbit{Bytes: []byte{}, Len: 0, Status: pgtype.Present}, 13 | &pgtype.Varbit{Bytes: []byte{0, 1, 128, 254, 255}, Len: 40, Status: pgtype.Present}, 14 | &pgtype.Varbit{Bytes: []byte{0, 1, 128, 254, 128}, Len: 33, Status: pgtype.Present}, 15 | &pgtype.Varbit{Status: pgtype.Null}, 16 | }) 17 | } 18 | 19 | func TestVarbitNormalize(t *testing.T) { 20 | testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{ 21 | { 22 | SQL: "select B'111111111'", 23 | Value: &pgtype.Varbit{Bytes: []byte{255, 128}, Len: 9, Status: pgtype.Present}, 24 | }, 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /varchar.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | ) 6 | 7 | type Varchar Text 8 | 9 | // Set converts from src to dst. Note that as Varchar is not a general 10 | // number type Set does not do automatic type conversion as other number 11 | // types do. 12 | func (dst *Varchar) Set(src interface{}) error { 13 | return (*Text)(dst).Set(src) 14 | } 15 | 16 | func (dst Varchar) Get() interface{} { 17 | return (Text)(dst).Get() 18 | } 19 | 20 | // AssignTo assigns from src to dst. Note that as Varchar is not a general number 21 | // type AssignTo does not do automatic type conversion as other number types do. 22 | func (src *Varchar) AssignTo(dst interface{}) error { 23 | return (*Text)(src).AssignTo(dst) 24 | } 25 | 26 | func (Varchar) PreferredResultFormat() int16 { 27 | return TextFormatCode 28 | } 29 | 30 | func (dst *Varchar) DecodeText(ci *ConnInfo, src []byte) error { 31 | return (*Text)(dst).DecodeText(ci, src) 32 | } 33 | 34 | func (dst *Varchar) DecodeBinary(ci *ConnInfo, src []byte) error { 35 | return (*Text)(dst).DecodeBinary(ci, src) 36 | } 37 | 38 | func (Varchar) PreferredParamFormat() int16 { 39 | return TextFormatCode 40 | } 41 | 42 | func (src Varchar) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 43 | return (Text)(src).EncodeText(ci, buf) 44 | } 45 | 46 | func (src Varchar) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 47 | return (Text)(src).EncodeBinary(ci, buf) 48 | } 49 | 50 | // Scan implements the database/sql Scanner interface. 51 | func (dst *Varchar) Scan(src interface{}) error { 52 | return (*Text)(dst).Scan(src) 53 | } 54 | 55 | // Value implements the database/sql/driver Valuer interface. 56 | func (src Varchar) Value() (driver.Value, error) { 57 | return (Text)(src).Value() 58 | } 59 | 60 | func (src Varchar) MarshalJSON() ([]byte, error) { 61 | return (Text)(src).MarshalJSON() 62 | } 63 | 64 | func (dst *Varchar) UnmarshalJSON(b []byte) error { 65 | return (*Text)(dst).UnmarshalJSON(b) 66 | } 67 | -------------------------------------------------------------------------------- /xid.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | import ( 4 | "database/sql/driver" 5 | ) 6 | 7 | // XID is PostgreSQL's Transaction ID type. 8 | // 9 | // In later versions of PostgreSQL, it is the type used for the backend_xid 10 | // and backend_xmin columns of the pg_stat_activity system view. 11 | // 12 | // Also, when one does 13 | // 14 | // select xmin, xmax, * from some_table; 15 | // 16 | // it is the data type of the xmin and xmax hidden system columns. 17 | // 18 | // It is currently implemented as an unsigned four byte integer. 19 | // Its definition can be found in src/include/postgres_ext.h as TransactionId 20 | // in the PostgreSQL sources. 21 | type XID pguint32 22 | 23 | // Set converts from src to dst. Note that as XID is not a general 24 | // number type Set does not do automatic type conversion as other number 25 | // types do. 26 | func (dst *XID) Set(src interface{}) error { 27 | return (*pguint32)(dst).Set(src) 28 | } 29 | 30 | func (dst XID) Get() interface{} { 31 | return (pguint32)(dst).Get() 32 | } 33 | 34 | // AssignTo assigns from src to dst. Note that as XID is not a general number 35 | // type AssignTo does not do automatic type conversion as other number types do. 36 | func (src *XID) AssignTo(dst interface{}) error { 37 | return (*pguint32)(src).AssignTo(dst) 38 | } 39 | 40 | func (dst *XID) DecodeText(ci *ConnInfo, src []byte) error { 41 | return (*pguint32)(dst).DecodeText(ci, src) 42 | } 43 | 44 | func (dst *XID) DecodeBinary(ci *ConnInfo, src []byte) error { 45 | return (*pguint32)(dst).DecodeBinary(ci, src) 46 | } 47 | 48 | func (src XID) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { 49 | return (pguint32)(src).EncodeText(ci, buf) 50 | } 51 | 52 | func (src XID) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { 53 | return (pguint32)(src).EncodeBinary(ci, buf) 54 | } 55 | 56 | // Scan implements the database/sql Scanner interface. 57 | func (dst *XID) Scan(src interface{}) error { 58 | return (*pguint32)(dst).Scan(src) 59 | } 60 | 61 | // Value implements the database/sql/driver Valuer interface. 62 | func (src XID) Value() (driver.Value, error) { 63 | return (pguint32)(src).Value() 64 | } 65 | -------------------------------------------------------------------------------- /xid_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/jackc/pgtype" 8 | "github.com/jackc/pgtype/testutil" 9 | ) 10 | 11 | func TestXIDTranscode(t *testing.T) { 12 | pgTypeName := "xid" 13 | values := []interface{}{ 14 | &pgtype.XID{Uint: 42, Status: pgtype.Present}, 15 | &pgtype.XID{Status: pgtype.Null}, 16 | } 17 | eqFunc := func(a, b interface{}) bool { 18 | return reflect.DeepEqual(a, b) 19 | } 20 | 21 | testutil.TestPgxSuccessfulTranscodeEqFunc(t, pgTypeName, values, eqFunc) 22 | 23 | for _, driverName := range []string{"github.com/lib/pq", "github.com/jackc/pgx/stdlib"} { 24 | testutil.TestDatabaseSQLSuccessfulTranscodeEqFunc(t, driverName, pgTypeName, values, eqFunc) 25 | } 26 | } 27 | 28 | func TestXIDSet(t *testing.T) { 29 | successfulTests := []struct { 30 | source interface{} 31 | result pgtype.XID 32 | }{ 33 | {source: uint32(1), result: pgtype.XID{Uint: 1, Status: pgtype.Present}}, 34 | } 35 | 36 | for i, tt := range successfulTests { 37 | var r pgtype.XID 38 | err := r.Set(tt.source) 39 | if err != nil { 40 | t.Errorf("%d: %v", i, err) 41 | } 42 | 43 | if r != tt.result { 44 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) 45 | } 46 | } 47 | } 48 | 49 | func TestXIDAssignTo(t *testing.T) { 50 | var ui32 uint32 51 | var pui32 *uint32 52 | 53 | simpleTests := []struct { 54 | src pgtype.XID 55 | dst interface{} 56 | expected interface{} 57 | }{ 58 | {src: pgtype.XID{Uint: 42, Status: pgtype.Present}, dst: &ui32, expected: uint32(42)}, 59 | {src: pgtype.XID{Status: pgtype.Null}, dst: &pui32, expected: ((*uint32)(nil))}, 60 | } 61 | 62 | for i, tt := range simpleTests { 63 | err := tt.src.AssignTo(tt.dst) 64 | if err != nil { 65 | t.Errorf("%d: %v", i, err) 66 | } 67 | 68 | if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected { 69 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 70 | } 71 | } 72 | 73 | pointerAllocTests := []struct { 74 | src pgtype.XID 75 | dst interface{} 76 | expected interface{} 77 | }{ 78 | {src: pgtype.XID{Uint: 42, Status: pgtype.Present}, dst: &pui32, expected: uint32(42)}, 79 | } 80 | 81 | for i, tt := range pointerAllocTests { 82 | err := tt.src.AssignTo(tt.dst) 83 | if err != nil { 84 | t.Errorf("%d: %v", i, err) 85 | } 86 | 87 | if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected { 88 | t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 89 | } 90 | } 91 | 92 | errorTests := []struct { 93 | src pgtype.XID 94 | dst interface{} 95 | }{ 96 | {src: pgtype.XID{Status: pgtype.Null}, dst: &ui32}, 97 | } 98 | 99 | for i, tt := range errorTests { 100 | err := tt.src.AssignTo(tt.dst) 101 | if err == nil { 102 | t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /zeronull/doc.go: -------------------------------------------------------------------------------- 1 | // Package zeronull contains types that automatically convert between database NULLs and Go zero values. 2 | /* 3 | Sometimes the distinction between a zero value and a NULL value is not useful at the application level. For example, 4 | in PostgreSQL an empty string may be stored as NULL. There is usually no application level distinction between an 5 | empty string and a NULL string. Package zeronull implements types that seamlessly convert between PostgreSQL NULL and 6 | the zero value. 7 | 8 | It is recommended to convert types at usage time rather than instantiate these types directly. In the example below, 9 | middlename would be stored as a NULL. 10 | 11 | firstname := "John" 12 | middlename := "" 13 | lastname := "Smith" 14 | _, err := conn.Exec( 15 | ctx, 16 | "insert into people(firstname, middlename, lastname) values($1, $2, $3)", 17 | zeronull.Text(firstname), 18 | zeronull.Text(middlename), 19 | zeronull.Text(lastname), 20 | ) 21 | */ 22 | package zeronull 23 | -------------------------------------------------------------------------------- /zeronull/float8.go: -------------------------------------------------------------------------------- 1 | package zeronull 2 | 3 | import ( 4 | "database/sql/driver" 5 | 6 | "github.com/jackc/pgtype" 7 | ) 8 | 9 | type Float8 float64 10 | 11 | func (dst *Float8) DecodeText(ci *pgtype.ConnInfo, src []byte) error { 12 | var nullable pgtype.Float8 13 | err := nullable.DecodeText(ci, src) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | if nullable.Status == pgtype.Present { 19 | *dst = Float8(nullable.Float) 20 | } else { 21 | *dst = 0 22 | } 23 | 24 | return nil 25 | } 26 | 27 | func (dst *Float8) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error { 28 | var nullable pgtype.Float8 29 | err := nullable.DecodeBinary(ci, src) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | if nullable.Status == pgtype.Present { 35 | *dst = Float8(nullable.Float) 36 | } else { 37 | *dst = 0 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func (src Float8) EncodeText(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 44 | if src == 0 { 45 | return nil, nil 46 | } 47 | 48 | nullable := pgtype.Float8{ 49 | Float: float64(src), 50 | Status: pgtype.Present, 51 | } 52 | 53 | return nullable.EncodeText(ci, buf) 54 | } 55 | 56 | func (src Float8) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 57 | if src == 0 { 58 | return nil, nil 59 | } 60 | 61 | nullable := pgtype.Float8{ 62 | Float: float64(src), 63 | Status: pgtype.Present, 64 | } 65 | 66 | return nullable.EncodeBinary(ci, buf) 67 | } 68 | 69 | // Scan implements the database/sql Scanner interface. 70 | func (dst *Float8) Scan(src interface{}) error { 71 | if src == nil { 72 | *dst = 0 73 | return nil 74 | } 75 | 76 | var nullable pgtype.Float8 77 | err := nullable.Scan(src) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | *dst = Float8(nullable.Float) 83 | 84 | return nil 85 | } 86 | 87 | // Value implements the database/sql/driver Valuer interface. 88 | func (src Float8) Value() (driver.Value, error) { 89 | return pgtype.EncodeValueText(src) 90 | } 91 | -------------------------------------------------------------------------------- /zeronull/float8_test.go: -------------------------------------------------------------------------------- 1 | package zeronull_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype/testutil" 7 | "github.com/jackc/pgtype/zeronull" 8 | ) 9 | 10 | func TestFloat8Transcode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "float8", []interface{}{ 12 | (zeronull.Float8)(1), 13 | (zeronull.Float8)(0), 14 | }) 15 | } 16 | 17 | func TestFloat8ConvertsGoZeroToNull(t *testing.T) { 18 | testutil.TestGoZeroToNullConversion(t, "float8", (zeronull.Float8)(0)) 19 | } 20 | 21 | func TestFloat8ConvertsNullToGoZero(t *testing.T) { 22 | testutil.TestNullToGoZeroConversion(t, "float8", (zeronull.Float8)(0)) 23 | } 24 | -------------------------------------------------------------------------------- /zeronull/int2.go: -------------------------------------------------------------------------------- 1 | package zeronull 2 | 3 | import ( 4 | "database/sql/driver" 5 | 6 | "github.com/jackc/pgtype" 7 | ) 8 | 9 | type Int2 int16 10 | 11 | func (dst *Int2) DecodeText(ci *pgtype.ConnInfo, src []byte) error { 12 | var nullable pgtype.Int2 13 | err := nullable.DecodeText(ci, src) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | if nullable.Status == pgtype.Present { 19 | *dst = Int2(nullable.Int) 20 | } else { 21 | *dst = 0 22 | } 23 | 24 | return nil 25 | } 26 | 27 | func (dst *Int2) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error { 28 | var nullable pgtype.Int2 29 | err := nullable.DecodeBinary(ci, src) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | if nullable.Status == pgtype.Present { 35 | *dst = Int2(nullable.Int) 36 | } else { 37 | *dst = 0 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func (src Int2) EncodeText(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 44 | if src == 0 { 45 | return nil, nil 46 | } 47 | 48 | nullable := pgtype.Int2{ 49 | Int: int16(src), 50 | Status: pgtype.Present, 51 | } 52 | 53 | return nullable.EncodeText(ci, buf) 54 | } 55 | 56 | func (src Int2) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 57 | if src == 0 { 58 | return nil, nil 59 | } 60 | 61 | nullable := pgtype.Int2{ 62 | Int: int16(src), 63 | Status: pgtype.Present, 64 | } 65 | 66 | return nullable.EncodeBinary(ci, buf) 67 | } 68 | 69 | // Scan implements the database/sql Scanner interface. 70 | func (dst *Int2) Scan(src interface{}) error { 71 | if src == nil { 72 | *dst = 0 73 | return nil 74 | } 75 | 76 | var nullable pgtype.Int2 77 | err := nullable.Scan(src) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | *dst = Int2(nullable.Int) 83 | 84 | return nil 85 | } 86 | 87 | // Value implements the database/sql/driver Valuer interface. 88 | func (src Int2) Value() (driver.Value, error) { 89 | return pgtype.EncodeValueText(src) 90 | } 91 | -------------------------------------------------------------------------------- /zeronull/int2_test.go: -------------------------------------------------------------------------------- 1 | package zeronull_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype/testutil" 7 | "github.com/jackc/pgtype/zeronull" 8 | ) 9 | 10 | func TestInt2Transcode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "int2", []interface{}{ 12 | (zeronull.Int2)(1), 13 | (zeronull.Int2)(0), 14 | }) 15 | } 16 | 17 | func TestInt2ConvertsGoZeroToNull(t *testing.T) { 18 | testutil.TestGoZeroToNullConversion(t, "int2", (zeronull.Int2)(0)) 19 | } 20 | 21 | func TestInt2ConvertsNullToGoZero(t *testing.T) { 22 | testutil.TestNullToGoZeroConversion(t, "int2", (zeronull.Int2)(0)) 23 | } 24 | -------------------------------------------------------------------------------- /zeronull/int4.go: -------------------------------------------------------------------------------- 1 | package zeronull 2 | 3 | import ( 4 | "database/sql/driver" 5 | 6 | "github.com/jackc/pgtype" 7 | ) 8 | 9 | type Int4 int32 10 | 11 | func (dst *Int4) DecodeText(ci *pgtype.ConnInfo, src []byte) error { 12 | var nullable pgtype.Int4 13 | err := nullable.DecodeText(ci, src) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | if nullable.Status == pgtype.Present { 19 | *dst = Int4(nullable.Int) 20 | } else { 21 | *dst = 0 22 | } 23 | 24 | return nil 25 | } 26 | 27 | func (dst *Int4) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error { 28 | var nullable pgtype.Int4 29 | err := nullable.DecodeBinary(ci, src) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | if nullable.Status == pgtype.Present { 35 | *dst = Int4(nullable.Int) 36 | } else { 37 | *dst = 0 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func (src Int4) EncodeText(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 44 | if src == 0 { 45 | return nil, nil 46 | } 47 | 48 | nullable := pgtype.Int4{ 49 | Int: int32(src), 50 | Status: pgtype.Present, 51 | } 52 | 53 | return nullable.EncodeText(ci, buf) 54 | } 55 | 56 | func (src Int4) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 57 | if src == 0 { 58 | return nil, nil 59 | } 60 | 61 | nullable := pgtype.Int4{ 62 | Int: int32(src), 63 | Status: pgtype.Present, 64 | } 65 | 66 | return nullable.EncodeBinary(ci, buf) 67 | } 68 | 69 | // Scan implements the database/sql Scanner interface. 70 | func (dst *Int4) Scan(src interface{}) error { 71 | if src == nil { 72 | *dst = 0 73 | return nil 74 | } 75 | 76 | var nullable pgtype.Int4 77 | err := nullable.Scan(src) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | *dst = Int4(nullable.Int) 83 | 84 | return nil 85 | } 86 | 87 | // Value implements the database/sql/driver Valuer interface. 88 | func (src Int4) Value() (driver.Value, error) { 89 | return pgtype.EncodeValueText(src) 90 | } 91 | -------------------------------------------------------------------------------- /zeronull/int4_test.go: -------------------------------------------------------------------------------- 1 | package zeronull_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype/testutil" 7 | "github.com/jackc/pgtype/zeronull" 8 | ) 9 | 10 | func TestInt4Transcode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "int4", []interface{}{ 12 | (zeronull.Int4)(1), 13 | (zeronull.Int4)(0), 14 | }) 15 | } 16 | 17 | func TestInt4ConvertsGoZeroToNull(t *testing.T) { 18 | testutil.TestGoZeroToNullConversion(t, "int4", (zeronull.Int4)(0)) 19 | } 20 | 21 | func TestInt4ConvertsNullToGoZero(t *testing.T) { 22 | testutil.TestNullToGoZeroConversion(t, "int4", (zeronull.Int4)(0)) 23 | } 24 | -------------------------------------------------------------------------------- /zeronull/int8.go: -------------------------------------------------------------------------------- 1 | package zeronull 2 | 3 | import ( 4 | "database/sql/driver" 5 | 6 | "github.com/jackc/pgtype" 7 | ) 8 | 9 | type Int8 int64 10 | 11 | func (dst *Int8) DecodeText(ci *pgtype.ConnInfo, src []byte) error { 12 | var nullable pgtype.Int8 13 | err := nullable.DecodeText(ci, src) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | if nullable.Status == pgtype.Present { 19 | *dst = Int8(nullable.Int) 20 | } else { 21 | *dst = 0 22 | } 23 | 24 | return nil 25 | } 26 | 27 | func (dst *Int8) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error { 28 | var nullable pgtype.Int8 29 | err := nullable.DecodeBinary(ci, src) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | if nullable.Status == pgtype.Present { 35 | *dst = Int8(nullable.Int) 36 | } else { 37 | *dst = 0 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func (src Int8) EncodeText(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 44 | if src == 0 { 45 | return nil, nil 46 | } 47 | 48 | nullable := pgtype.Int8{ 49 | Int: int64(src), 50 | Status: pgtype.Present, 51 | } 52 | 53 | return nullable.EncodeText(ci, buf) 54 | } 55 | 56 | func (src Int8) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 57 | if src == 0 { 58 | return nil, nil 59 | } 60 | 61 | nullable := pgtype.Int8{ 62 | Int: int64(src), 63 | Status: pgtype.Present, 64 | } 65 | 66 | return nullable.EncodeBinary(ci, buf) 67 | } 68 | 69 | // Scan implements the database/sql Scanner interface. 70 | func (dst *Int8) Scan(src interface{}) error { 71 | if src == nil { 72 | *dst = 0 73 | return nil 74 | } 75 | 76 | var nullable pgtype.Int8 77 | err := nullable.Scan(src) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | *dst = Int8(nullable.Int) 83 | 84 | return nil 85 | } 86 | 87 | // Value implements the database/sql/driver Valuer interface. 88 | func (src Int8) Value() (driver.Value, error) { 89 | return pgtype.EncodeValueText(src) 90 | } 91 | -------------------------------------------------------------------------------- /zeronull/int8_test.go: -------------------------------------------------------------------------------- 1 | package zeronull_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype/testutil" 7 | "github.com/jackc/pgtype/zeronull" 8 | ) 9 | 10 | func TestInt8Transcode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "int8", []interface{}{ 12 | (zeronull.Int8)(1), 13 | (zeronull.Int8)(0), 14 | }) 15 | } 16 | 17 | func TestInt8ConvertsGoZeroToNull(t *testing.T) { 18 | testutil.TestGoZeroToNullConversion(t, "int8", (zeronull.Int8)(0)) 19 | } 20 | 21 | func TestInt8ConvertsNullToGoZero(t *testing.T) { 22 | testutil.TestNullToGoZeroConversion(t, "int8", (zeronull.Int8)(0)) 23 | } 24 | -------------------------------------------------------------------------------- /zeronull/text.go: -------------------------------------------------------------------------------- 1 | package zeronull 2 | 3 | import ( 4 | "database/sql/driver" 5 | 6 | "github.com/jackc/pgtype" 7 | ) 8 | 9 | type Text string 10 | 11 | func (dst *Text) DecodeText(ci *pgtype.ConnInfo, src []byte) error { 12 | var nullable pgtype.Text 13 | err := nullable.DecodeText(ci, src) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | if nullable.Status == pgtype.Present { 19 | *dst = Text(nullable.String) 20 | } else { 21 | *dst = Text("") 22 | } 23 | 24 | return nil 25 | } 26 | 27 | func (dst *Text) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error { 28 | var nullable pgtype.Text 29 | err := nullable.DecodeBinary(ci, src) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | if nullable.Status == pgtype.Present { 35 | *dst = Text(nullable.String) 36 | } else { 37 | *dst = Text("") 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func (src Text) EncodeText(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 44 | if src == Text("") { 45 | return nil, nil 46 | } 47 | 48 | nullable := pgtype.Text{ 49 | String: string(src), 50 | Status: pgtype.Present, 51 | } 52 | 53 | return nullable.EncodeText(ci, buf) 54 | } 55 | 56 | func (src Text) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 57 | if src == Text("") { 58 | return nil, nil 59 | } 60 | 61 | nullable := pgtype.Text{ 62 | String: string(src), 63 | Status: pgtype.Present, 64 | } 65 | 66 | return nullable.EncodeBinary(ci, buf) 67 | } 68 | 69 | // Scan implements the database/sql Scanner interface. 70 | func (dst *Text) Scan(src interface{}) error { 71 | if src == nil { 72 | *dst = Text("") 73 | return nil 74 | } 75 | 76 | var nullable pgtype.Text 77 | err := nullable.Scan(src) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | *dst = Text(nullable.String) 83 | 84 | return nil 85 | } 86 | 87 | // Value implements the database/sql/driver Valuer interface. 88 | func (src Text) Value() (driver.Value, error) { 89 | return pgtype.EncodeValueText(src) 90 | } 91 | -------------------------------------------------------------------------------- /zeronull/text_test.go: -------------------------------------------------------------------------------- 1 | package zeronull_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype/testutil" 7 | "github.com/jackc/pgtype/zeronull" 8 | ) 9 | 10 | func TestTextTranscode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "text", []interface{}{ 12 | (zeronull.Text)("foo"), 13 | (zeronull.Text)(""), 14 | }) 15 | } 16 | 17 | func TestTextConvertsGoZeroToNull(t *testing.T) { 18 | testutil.TestGoZeroToNullConversion(t, "text", (zeronull.Text)("")) 19 | } 20 | 21 | func TestTextConvertsNullToGoZero(t *testing.T) { 22 | testutil.TestNullToGoZeroConversion(t, "text", (zeronull.Text)("")) 23 | } 24 | -------------------------------------------------------------------------------- /zeronull/timestamp.go: -------------------------------------------------------------------------------- 1 | package zeronull 2 | 3 | import ( 4 | "database/sql/driver" 5 | "time" 6 | 7 | "github.com/jackc/pgtype" 8 | ) 9 | 10 | type Timestamp time.Time 11 | 12 | func (dst *Timestamp) DecodeText(ci *pgtype.ConnInfo, src []byte) error { 13 | var nullable pgtype.Timestamp 14 | err := nullable.DecodeText(ci, src) 15 | if err != nil { 16 | return err 17 | } 18 | 19 | if nullable.Status == pgtype.Present { 20 | *dst = Timestamp(nullable.Time) 21 | } else { 22 | *dst = Timestamp{} 23 | } 24 | 25 | return nil 26 | } 27 | 28 | func (dst *Timestamp) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error { 29 | var nullable pgtype.Timestamp 30 | err := nullable.DecodeBinary(ci, src) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | if nullable.Status == pgtype.Present { 36 | *dst = Timestamp(nullable.Time) 37 | } else { 38 | *dst = Timestamp{} 39 | } 40 | 41 | return nil 42 | } 43 | 44 | func (src Timestamp) EncodeText(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 45 | if (src == Timestamp{}) { 46 | return nil, nil 47 | } 48 | 49 | nullable := pgtype.Timestamp{ 50 | Time: time.Time(src), 51 | Status: pgtype.Present, 52 | } 53 | 54 | return nullable.EncodeText(ci, buf) 55 | } 56 | 57 | func (src Timestamp) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 58 | if (src == Timestamp{}) { 59 | return nil, nil 60 | } 61 | 62 | nullable := pgtype.Timestamp{ 63 | Time: time.Time(src), 64 | Status: pgtype.Present, 65 | } 66 | 67 | return nullable.EncodeBinary(ci, buf) 68 | } 69 | 70 | // Scan implements the database/sql Scanner interface. 71 | func (dst *Timestamp) Scan(src interface{}) error { 72 | if src == nil { 73 | *dst = Timestamp{} 74 | return nil 75 | } 76 | 77 | var nullable pgtype.Timestamp 78 | err := nullable.Scan(src) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | *dst = Timestamp(nullable.Time) 84 | 85 | return nil 86 | } 87 | 88 | // Value implements the database/sql/driver Valuer interface. 89 | func (src Timestamp) Value() (driver.Value, error) { 90 | return pgtype.EncodeValueText(src) 91 | } 92 | -------------------------------------------------------------------------------- /zeronull/timestamp_test.go: -------------------------------------------------------------------------------- 1 | package zeronull_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/jackc/pgtype/testutil" 8 | "github.com/jackc/pgtype/zeronull" 9 | ) 10 | 11 | func TestTimestampTranscode(t *testing.T) { 12 | testutil.TestSuccessfulTranscodeEqFunc(t, "timestamp", []interface{}{ 13 | (zeronull.Timestamp)(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)), 14 | (zeronull.Timestamp)(time.Time{}), 15 | }, func(a, b interface{}) bool { 16 | at := a.(zeronull.Timestamp) 17 | bt := b.(zeronull.Timestamp) 18 | 19 | return time.Time(at).Equal(time.Time(bt)) 20 | }) 21 | } 22 | 23 | func TestTimestampConvertsGoZeroToNull(t *testing.T) { 24 | testutil.TestGoZeroToNullConversion(t, "timestamp", (zeronull.Timestamp)(time.Time{})) 25 | } 26 | 27 | func TestTimestampConvertsNullToGoZero(t *testing.T) { 28 | testutil.TestNullToGoZeroConversion(t, "timestamp", (zeronull.Timestamp)(time.Time{})) 29 | } 30 | -------------------------------------------------------------------------------- /zeronull/timestamptz.go: -------------------------------------------------------------------------------- 1 | package zeronull 2 | 3 | import ( 4 | "database/sql/driver" 5 | "time" 6 | 7 | "github.com/jackc/pgtype" 8 | ) 9 | 10 | type Timestamptz time.Time 11 | 12 | func (dst *Timestamptz) DecodeText(ci *pgtype.ConnInfo, src []byte) error { 13 | var nullable pgtype.Timestamptz 14 | err := nullable.DecodeText(ci, src) 15 | if err != nil { 16 | return err 17 | } 18 | 19 | if nullable.Status == pgtype.Present { 20 | *dst = Timestamptz(nullable.Time) 21 | } else { 22 | *dst = Timestamptz{} 23 | } 24 | 25 | return nil 26 | } 27 | 28 | func (dst *Timestamptz) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error { 29 | var nullable pgtype.Timestamptz 30 | err := nullable.DecodeBinary(ci, src) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | if nullable.Status == pgtype.Present { 36 | *dst = Timestamptz(nullable.Time) 37 | } else { 38 | *dst = Timestamptz{} 39 | } 40 | 41 | return nil 42 | } 43 | 44 | func (src Timestamptz) EncodeText(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 45 | if (src == Timestamptz{}) { 46 | return nil, nil 47 | } 48 | 49 | nullable := pgtype.Timestamptz{ 50 | Time: time.Time(src), 51 | Status: pgtype.Present, 52 | } 53 | 54 | return nullable.EncodeText(ci, buf) 55 | } 56 | 57 | func (src Timestamptz) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 58 | if (src == Timestamptz{}) { 59 | return nil, nil 60 | } 61 | 62 | nullable := pgtype.Timestamptz{ 63 | Time: time.Time(src), 64 | Status: pgtype.Present, 65 | } 66 | 67 | return nullable.EncodeBinary(ci, buf) 68 | } 69 | 70 | // Scan implements the database/sql Scanner interface. 71 | func (dst *Timestamptz) Scan(src interface{}) error { 72 | if src == nil { 73 | *dst = Timestamptz{} 74 | return nil 75 | } 76 | 77 | var nullable pgtype.Timestamptz 78 | err := nullable.Scan(src) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | *dst = Timestamptz(nullable.Time) 84 | 85 | return nil 86 | } 87 | 88 | // Value implements the database/sql/driver Valuer interface. 89 | func (src Timestamptz) Value() (driver.Value, error) { 90 | return pgtype.EncodeValueText(src) 91 | } 92 | -------------------------------------------------------------------------------- /zeronull/timestamptz_test.go: -------------------------------------------------------------------------------- 1 | package zeronull_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/jackc/pgtype/testutil" 8 | "github.com/jackc/pgtype/zeronull" 9 | ) 10 | 11 | func TestTimestamptzTranscode(t *testing.T) { 12 | testutil.TestSuccessfulTranscodeEqFunc(t, "timestamptz", []interface{}{ 13 | (zeronull.Timestamptz)(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)), 14 | (zeronull.Timestamptz)(time.Time{}), 15 | }, func(a, b interface{}) bool { 16 | at := a.(zeronull.Timestamptz) 17 | bt := b.(zeronull.Timestamptz) 18 | 19 | return time.Time(at).Equal(time.Time(bt)) 20 | }) 21 | } 22 | 23 | func TestTimestamptzConvertsGoZeroToNull(t *testing.T) { 24 | testutil.TestGoZeroToNullConversion(t, "timestamptz", (zeronull.Timestamptz)(time.Time{})) 25 | } 26 | 27 | func TestTimestamptzConvertsNullToGoZero(t *testing.T) { 28 | testutil.TestNullToGoZeroConversion(t, "timestamptz", (zeronull.Timestamptz)(time.Time{})) 29 | } 30 | -------------------------------------------------------------------------------- /zeronull/uuid.go: -------------------------------------------------------------------------------- 1 | package zeronull 2 | 3 | import ( 4 | "database/sql/driver" 5 | 6 | "github.com/jackc/pgtype" 7 | ) 8 | 9 | type UUID [16]byte 10 | 11 | func (dst *UUID) DecodeText(ci *pgtype.ConnInfo, src []byte) error { 12 | var nullable pgtype.UUID 13 | err := nullable.DecodeText(ci, src) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | if nullable.Status == pgtype.Present { 19 | *dst = UUID(nullable.Bytes) 20 | } else { 21 | *dst = UUID{} 22 | } 23 | 24 | return nil 25 | } 26 | 27 | func (dst *UUID) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error { 28 | var nullable pgtype.UUID 29 | err := nullable.DecodeBinary(ci, src) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | if nullable.Status == pgtype.Present { 35 | *dst = UUID(nullable.Bytes) 36 | } else { 37 | *dst = UUID{} 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func (src UUID) EncodeText(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 44 | if (src == UUID{}) { 45 | return nil, nil 46 | } 47 | 48 | nullable := pgtype.UUID{ 49 | Bytes: [16]byte(src), 50 | Status: pgtype.Present, 51 | } 52 | 53 | return nullable.EncodeText(ci, buf) 54 | } 55 | 56 | func (src UUID) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { 57 | if (src == UUID{}) { 58 | return nil, nil 59 | } 60 | 61 | nullable := pgtype.UUID{ 62 | Bytes: [16]byte(src), 63 | Status: pgtype.Present, 64 | } 65 | 66 | return nullable.EncodeBinary(ci, buf) 67 | } 68 | 69 | // Scan implements the database/sql Scanner interface. 70 | func (dst *UUID) Scan(src interface{}) error { 71 | if src == nil { 72 | *dst = UUID{} 73 | return nil 74 | } 75 | 76 | var nullable pgtype.UUID 77 | err := nullable.Scan(src) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | *dst = UUID(nullable.Bytes) 83 | 84 | return nil 85 | } 86 | 87 | // Value implements the database/sql/driver Valuer interface. 88 | func (src UUID) Value() (driver.Value, error) { 89 | return pgtype.EncodeValueText(src) 90 | } 91 | -------------------------------------------------------------------------------- /zeronull/uuid_test.go: -------------------------------------------------------------------------------- 1 | package zeronull_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgtype/testutil" 7 | "github.com/jackc/pgtype/zeronull" 8 | ) 9 | 10 | func TestUUIDTranscode(t *testing.T) { 11 | testutil.TestSuccessfulTranscode(t, "uuid", []interface{}{ 12 | (*zeronull.UUID)(&[16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}), 13 | (*zeronull.UUID)(&[16]byte{}), 14 | }) 15 | } 16 | 17 | func TestUUIDConvertsGoZeroToNull(t *testing.T) { 18 | testutil.TestGoZeroToNullConversion(t, "uuid", (*zeronull.UUID)(&[16]byte{})) 19 | } 20 | 21 | func TestUUIDConvertsNullToGoZero(t *testing.T) { 22 | testutil.TestNullToGoZeroConversion(t, "uuid", (*zeronull.UUID)(&[16]byte{})) 23 | } 24 | --------------------------------------------------------------------------------