pgtype icon indicating copy to clipboard operation
pgtype copied to clipboard

`pgtype.JSON ERROR: invalid input syntax for type json` with `PreferSimpleProtocol = true`

Open buildpeak opened this issue 5 years ago • 5 comments
trafficstars

Error message

pgtype.JSON ERROR: invalid input syntax for type json (SQLSTATE 22P02)

Description

When use pgx/v4/stdlib and sqlx to store pgtype.JSON to database, will get an error like above(SQLSTATE 22P02). I also tried database/sql and got the same errors. However, with pgx.ConnectConfig(ctx, connConfig), pgtype.JSON worked but []byte not.

Code to reproduce

package main

import (
    "log"

    "github.com/jackc/pgtype"
    "github.com/jackc/pgx/v4"
    "github.com/jackc/pgx/v4/stdlib"
    "github.com/jmoiron/sqlx"
)

func main() {
    dns := "user=tester host=localhost dbname=tester sslmode=disable"
    connConfig, _ := pgx.ParseConfig(dns)
    connConfig.PreferSimpleProtocol = true
    conn, err := sqlx.Open("pgx", stdlib.RegisterConnConfig(connConfig))
    //conn, err := pgx.ConnectConfig(ctx, connConfig)
    if err != nil {
        log.Fatal(err)
    }

    _, err = conn.Exec(`drop table if exists pgx514;`)
    if err != nil {
        log.Fatal(err)
    }

    _, err = conn.Exec(`create table pgx514 (id serial primary key, data jsonb not null);`)
    if err != nil {
        log.Fatal(err)
    }

    dataJSON := &pgtype.JSON{Bytes: []byte(`{"foo": "bar"}`), Status: pgtype.Present}
    commandTag, err := conn.Exec("insert into pgx514(data) values($1)", dataJSON)
    if err == nil {
        log.Println("pgtype.JSON", commandTag)
    } else {
        log.Println("pgtype.JSON", err)
    }

    dataBytes := []byte(`{"foo": "bar"}`)
    commandTag, err = conn.Exec("insert into pgx514(data) values($1)", dataBytes)
    if err == nil {
        log.Println("[]byte", commandTag)
    } else {
        log.Println("[]byte", err)
    }
}

buildpeak avatar Jun 30 '20 16:06 buildpeak

This is an edge case with the simple protocol and database/sql.

One problem is that when using the simple protocol is the destination PostgreSQL type is unknown. The type has to be determined by completely by the Go side. What is a []byte encoded as? pgx considers it bytea which means it is hex encoded. Use a string for raw JSON data instead of []byte.

The pgtype.JSON case is more complicated. (As an aside, use pgtype.JSONB for PostgreSQL jsonb.) When using the pgx native interface there is a special case to handle pgtype.JSON.

https://github.com/jackc/pgx/blob/eeda0368e66fafed0a3db500108bdb87b657a88a/values.go#L40

However, when using database/sql, pgx can't see that it is a pgtype.JSON, database/sql has already called Value() and converted it to a []byte. And Value() returns []byte for compatibility with lib/pq and json.RawMessage (https://github.com/jackc/pgx/issues/409).

So I'm not sure if this exact case can be solved without breaking something else. But you can use string, sql.NullString, or pgtype.Text instead.

jackc avatar Jul 01 '20 12:07 jackc

@jackc do you know if this issue is impacted / resolved with the removal of the JSONB type? https://github.com/jackc/pgx/blob/master/CHANGELOG.md#other-changes

aethanol avatar Oct 13 '22 21:10 aethanol

@aethanol It's still an issue with v5. Same fundamental problem though the internal implementation details differ.

One problem is that when using the simple protocol is the destination PostgreSQL type is unknown. The type has to be determined by completely by the Go side. What is a []byte encoded as? pgx considers it bytea which means it is hex encoded. Use a string for raw JSON data instead of []byte.

jackc avatar Oct 14 '22 23:10 jackc

So I'm not sure if this exact case can be solved without breaking something else. But you can use string, sql.NullString, or pgtype.Text instead.

Thanks for the explanation @jackc.

I solved my issue by doing something like this:

-- schema.sql
CREATE TABLE IF NOT EXISTS my_table(
    id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    my_data jsonb
);

-- query.sql
-- name: InsertMyTable :exec
INSERT INTO my_table (data)
VALUES ((@my_data::text)::jsonb);

Please let me know if I missed something.

asendia avatar Mar 04 '24 17:03 asendia