Skip to content

Commit

Permalink
pgtype.JSON(B).Value now returns []byte
Browse files Browse the repository at this point in the history
Allows scanning jsonb column into *json.RawMessage.

fixes #409
  • Loading branch information
jackc committed Apr 14, 2018
1 parent 6556ef6 commit 5297846
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 1 deletion.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
* Fix StartReplication() syntax (steampunkcoder)
* Fix precision loss for test format geometric types

## Changes

* pgtype.JSON(B).Value now returns []byte instead of string. This allows
database/sql to scan json(b) into \*json.RawMessage. This is a tiny behavior
change, but database/sql Scan should automatically convert []byte to string, so
there shouldn't be any incompatibility.

# 3.1.0 (January 15, 2018)

## Features
Expand Down
2 changes: 1 addition & 1 deletion pgtype/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func (dst *JSON) Scan(src interface{}) error {
func (src *JSON) Value() (driver.Value, error) {
switch src.Status {
case Present:
return string(src.Bytes), nil
return src.Bytes, nil
case Null:
return nil, nil
default:
Expand Down
4 changes: 4 additions & 0 deletions stdlib/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,10 @@ func (r *Rows) Next(dest []driver.Value) error {
r.values[i] = &pgtype.Int4{}
case pgtype.Int8OID:
r.values[i] = &pgtype.Int8{}
case pgtype.JSONOID:
r.values[i] = &pgtype.JSON{}
case pgtype.JSONBOID:
r.values[i] = &pgtype.JSONB{}
case pgtype.OIDOID:
r.values[i] = &pgtype.OIDValue{}
case pgtype.TimestampOID:
Expand Down
20 changes: 20 additions & 0 deletions stdlib/sql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"database/sql"
"encoding/json"
"fmt"
"math"
"reflect"
Expand Down Expand Up @@ -1466,3 +1467,22 @@ func TestSimpleQueryLifeCycle(t *testing.T) {

ensureConnValid(t, db)
}

// https://github.com/jackc/pgx/issues/409
func TestScanJSONIntoJSONRawMessage(t *testing.T) {
db := openDB(t)
defer closeDB(t, db)

var msg json.RawMessage

err := db.QueryRow("select '{}'::jsonb").Scan(&msg)
if err != nil {
t.Fatalf("QueryRow / Scan failed: %v", err)
}

if bytes.Compare([]byte("{}"), []byte(msg)) != 0 {
t.Fatalf("Expected %v, got %v", []byte("{}"), msg)
}

ensureConnValid(t, db)
}
26 changes: 26 additions & 0 deletions values.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,32 @@ func convertSimpleArgument(ci *pgtype.ConnInfo, arg interface{}) (interface{}, e
}

switch arg := arg.(type) {

// https://github.com/jackc/pgx/issues/409 Changed JSON and JSONB to surface
// []byte to database/sql instead of string. But that caused problems with the
// simple protocol because the driver.Valuer case got taken before the
// pgtype.TextEncoder case. And driver.Valuer needed to be first in the usual
// case because of https://github.com/jackc/pgx/issues/339. So instead we
// special case JSON and JSONB.
case *pgtype.JSON:
buf, err := arg.EncodeText(ci, nil)
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return string(buf), nil
case *pgtype.JSONB:
buf, err := arg.EncodeText(ci, nil)
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return string(buf), nil

case driver.Valuer:
return callValuerValue(arg)
case pgtype.TextEncoder:
Expand Down

0 comments on commit 5297846

Please sign in to comment.