package pgx_test import ( "fmt" "github.com/jackc/pgx" "net" "reflect" "strings" "testing" "time" ) func TestDateTranscode(t *testing.T) { t.Parallel() conn := mustConnect(t, *defaultConnConfig) defer closeConn(t, conn) mustPrepare(t, conn, "testTranscode", "select $1::date") defer func() { if err := conn.Deallocate("testTranscode"); err != nil { t.Fatalf("Unable to deallocate prepared statement: %v", err) } }() dates := []time.Time{ time.Date(1990, 1, 1, 0, 0, 0, 0, time.Local), time.Date(1999, 12, 31, 0, 0, 0, 0, time.Local), time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local), time.Date(2001, 1, 2, 0, 0, 0, 0, time.Local), time.Date(2004, 2, 29, 0, 0, 0, 0, time.Local), time.Date(2013, 7, 4, 0, 0, 0, 0, time.Local), time.Date(2013, 12, 25, 0, 0, 0, 0, time.Local), } for _, actualDate := range dates { var d time.Time // Test text format err := conn.QueryRow("select $1::date", actualDate).Scan(&d) if err != nil { t.Fatalf("Unexpected failure on QueryRow Scan: %v", err) } if !actualDate.Equal(d) { t.Errorf("Did not transcode date successfully: %v is not %v", d, actualDate) } // Test binary format err = conn.QueryRow("testTranscode", actualDate).Scan(&d) if err != nil { t.Fatalf("Unexpected failure on QueryRow Scan: %v", err) } if !actualDate.Equal(d) { t.Errorf("Did not transcode date successfully: %v is not %v", d, actualDate) } } } func TestTimestampTzTranscode(t *testing.T) { t.Parallel() conn := mustConnect(t, *defaultConnConfig) defer closeConn(t, conn) inputTime := time.Date(2013, 1, 2, 3, 4, 5, 6000, time.Local) var outputTime time.Time err := conn.QueryRow("select $1::timestamptz", inputTime).Scan(&outputTime) if err != nil { t.Fatalf("QueryRow Scan failed: %v", err) } if !inputTime.Equal(outputTime) { t.Errorf("Did not transcode time successfully: %v is not %v", outputTime, inputTime) } mustPrepare(t, conn, "testTranscode", "select $1::timestamptz") defer func() { if err := conn.Deallocate("testTranscode"); err != nil { t.Fatalf("Unable to deallocate prepared statement: %v", err) } }() err = conn.QueryRow("testTranscode", inputTime).Scan(&outputTime) if err != nil { t.Fatalf("QueryRow Scan failed: %v", err) } if !inputTime.Equal(outputTime) { t.Errorf("Did not transcode time successfully: %v is not %v", outputTime, inputTime) } } func mustParseCIDR(t *testing.T, s string) net.IPNet { _, ipnet, err := net.ParseCIDR(s) if err != nil { t.Fatal(err) } return *ipnet } func TestInetCidrTranscode(t *testing.T) { t.Parallel() conn := mustConnect(t, *defaultConnConfig) defer closeConn(t, conn) tests := []struct { sql string value net.IPNet }{ {"select $1::inet", mustParseCIDR(t, "0.0.0.0/32")}, {"select $1::inet", mustParseCIDR(t, "127.0.0.1/32")}, {"select $1::inet", mustParseCIDR(t, "12.34.56.0/32")}, {"select $1::inet", mustParseCIDR(t, "192.168.1.0/24")}, {"select $1::inet", mustParseCIDR(t, "255.0.0.0/8")}, {"select $1::inet", mustParseCIDR(t, "255.255.255.255/32")}, {"select $1::inet", mustParseCIDR(t, "::/128")}, {"select $1::inet", mustParseCIDR(t, "::/0")}, {"select $1::inet", mustParseCIDR(t, "::1/128")}, {"select $1::inet", mustParseCIDR(t, "2607:f8b0:4009:80b::200e/128")}, {"select $1::cidr", mustParseCIDR(t, "0.0.0.0/32")}, {"select $1::cidr", mustParseCIDR(t, "127.0.0.1/32")}, {"select $1::cidr", mustParseCIDR(t, "12.34.56.0/32")}, {"select $1::cidr", mustParseCIDR(t, "192.168.1.0/24")}, {"select $1::cidr", mustParseCIDR(t, "255.0.0.0/8")}, {"select $1::cidr", mustParseCIDR(t, "255.255.255.255/32")}, {"select $1::cidr", mustParseCIDR(t, "::/128")}, {"select $1::cidr", mustParseCIDR(t, "::/0")}, {"select $1::cidr", mustParseCIDR(t, "::1/128")}, {"select $1::cidr", mustParseCIDR(t, "2607:f8b0:4009:80b::200e/128")}, } for i, tt := range tests { var actual net.IPNet err := conn.QueryRow(tt.sql, tt.value).Scan(&actual) if err != nil { t.Errorf("%d. Unexpected failure: %v (sql -> %v, value -> %v)", i, err, tt.sql, tt.value) continue } if actual.String() != tt.value.String() { t.Errorf("%d. Expected %v, got %v (sql -> %v)", i, tt.value, actual, tt.sql) } ensureConnValid(t, conn) } } func TestNullX(t *testing.T) { t.Parallel() conn := mustConnect(t, *defaultConnConfig) defer closeConn(t, conn) type allTypes struct { s pgx.NullString i16 pgx.NullInt16 i32 pgx.NullInt32 i64 pgx.NullInt64 f32 pgx.NullFloat32 f64 pgx.NullFloat64 b pgx.NullBool t pgx.NullTime } var actual, zero allTypes tests := []struct { sql string queryArgs []interface{} scanArgs []interface{} expected allTypes }{ {"select $1::text", []interface{}{pgx.NullString{String: "foo", Valid: true}}, []interface{}{&actual.s}, allTypes{s: pgx.NullString{String: "foo", Valid: true}}}, {"select $1::text", []interface{}{pgx.NullString{String: "foo", Valid: false}}, []interface{}{&actual.s}, allTypes{s: pgx.NullString{String: "", Valid: false}}}, {"select $1::int2", []interface{}{pgx.NullInt16{Int16: 1, Valid: true}}, []interface{}{&actual.i16}, allTypes{i16: pgx.NullInt16{Int16: 1, Valid: true}}}, {"select $1::int2", []interface{}{pgx.NullInt16{Int16: 1, Valid: false}}, []interface{}{&actual.i16}, allTypes{i16: pgx.NullInt16{Int16: 0, Valid: false}}}, {"select $1::int4", []interface{}{pgx.NullInt32{Int32: 1, Valid: true}}, []interface{}{&actual.i32}, allTypes{i32: pgx.NullInt32{Int32: 1, Valid: true}}}, {"select $1::int4", []interface{}{pgx.NullInt32{Int32: 1, Valid: false}}, []interface{}{&actual.i32}, allTypes{i32: pgx.NullInt32{Int32: 0, Valid: false}}}, {"select $1::int8", []interface{}{pgx.NullInt64{Int64: 1, Valid: true}}, []interface{}{&actual.i64}, allTypes{i64: pgx.NullInt64{Int64: 1, Valid: true}}}, {"select $1::int8", []interface{}{pgx.NullInt64{Int64: 1, Valid: false}}, []interface{}{&actual.i64}, allTypes{i64: pgx.NullInt64{Int64: 0, Valid: false}}}, {"select $1::float4", []interface{}{pgx.NullFloat32{Float32: 1.23, Valid: true}}, []interface{}{&actual.f32}, allTypes{f32: pgx.NullFloat32{Float32: 1.23, Valid: true}}}, {"select $1::float4", []interface{}{pgx.NullFloat32{Float32: 1.23, Valid: false}}, []interface{}{&actual.f32}, allTypes{f32: pgx.NullFloat32{Float32: 0, Valid: false}}}, {"select $1::float8", []interface{}{pgx.NullFloat64{Float64: 1.23, Valid: true}}, []interface{}{&actual.f64}, allTypes{f64: pgx.NullFloat64{Float64: 1.23, Valid: true}}}, {"select $1::float8", []interface{}{pgx.NullFloat64{Float64: 1.23, Valid: false}}, []interface{}{&actual.f64}, allTypes{f64: pgx.NullFloat64{Float64: 0, Valid: false}}}, {"select $1::bool", []interface{}{pgx.NullBool{Bool: true, Valid: true}}, []interface{}{&actual.b}, allTypes{b: pgx.NullBool{Bool: true, Valid: true}}}, {"select $1::bool", []interface{}{pgx.NullBool{Bool: true, Valid: false}}, []interface{}{&actual.b}, allTypes{b: pgx.NullBool{Bool: false, Valid: false}}}, {"select $1::timestamptz", []interface{}{pgx.NullTime{Time: time.Unix(123, 5000), Valid: true}}, []interface{}{&actual.t}, allTypes{t: pgx.NullTime{Time: time.Unix(123, 5000), Valid: true}}}, {"select $1::timestamptz", []interface{}{pgx.NullTime{Time: time.Unix(123, 5000), Valid: false}}, []interface{}{&actual.t}, allTypes{t: pgx.NullTime{Time: time.Time{}, Valid: false}}}, {"select $1::timestamp", []interface{}{pgx.NullTime{Time: time.Unix(123, 5000), Valid: true}}, []interface{}{&actual.t}, allTypes{t: pgx.NullTime{Time: time.Unix(123, 5000), Valid: true}}}, {"select $1::timestamp", []interface{}{pgx.NullTime{Time: time.Unix(123, 5000), Valid: false}}, []interface{}{&actual.t}, allTypes{t: pgx.NullTime{Time: time.Time{}, Valid: false}}}, {"select 42::int4, $1::float8", []interface{}{pgx.NullFloat64{Float64: 1.23, Valid: true}}, []interface{}{&actual.i32, &actual.f64}, allTypes{i32: pgx.NullInt32{Int32: 42, Valid: true}, f64: pgx.NullFloat64{Float64: 1.23, Valid: true}}}, } for i, tt := range tests { psName := fmt.Sprintf("success%d", i) mustPrepare(t, conn, psName, tt.sql) for _, sql := range []string{tt.sql, psName} { actual = zero err := conn.QueryRow(sql, tt.queryArgs...).Scan(tt.scanArgs...) if err != nil { t.Errorf("%d. Unexpected failure: %v (sql -> %v, queryArgs -> %v)", i, err, sql, tt.queryArgs) } if actual != tt.expected { t.Errorf("%d. Expected %v, got %v (sql -> %v, queryArgs -> %v)", i, tt.expected, actual, sql, tt.queryArgs) } ensureConnValid(t, conn) } } } func TestArrayDecoding(t *testing.T) { t.Parallel() conn := mustConnect(t, *defaultConnConfig) defer closeConn(t, conn) tests := []struct { sql string query interface{} scan interface{} assert func(*testing.T, interface{}, interface{}) }{ { "select $1::bool[]", []bool{true, false, true}, &[]bool{}, func(t *testing.T, query, scan interface{}) { if reflect.DeepEqual(query, *(scan.(*[]bool))) == false { t.Errorf("failed to encode bool[]") } }, }, { "select $1::int[]", []int32{2, 4, 484}, &[]int32{}, func(t *testing.T, query, scan interface{}) { if reflect.DeepEqual(query, *(scan.(*[]int32))) == false { t.Errorf("failed to encode int[]") } }, }, { "select $1::text[]", []string{"it's", "over", "9000!"}, &[]string{}, func(t *testing.T, query, scan interface{}) { if reflect.DeepEqual(query, *(scan.(*[]string))) == false { t.Errorf("failed to encode text[]") } }, }, { "select $1::timestamp[]", []time.Time{time.Unix(323232, 0), time.Unix(3239949334, 00)}, &[]time.Time{}, func(t *testing.T, query, scan interface{}) { if reflect.DeepEqual(query, *(scan.(*[]time.Time))) == false { t.Errorf("failed to encode time.Time[] to timestamp[]") } }, }, { "select $1::timestamptz[]", []time.Time{time.Unix(323232, 0), time.Unix(3239949334, 00)}, &[]time.Time{}, func(t *testing.T, query, scan interface{}) { if reflect.DeepEqual(query, *(scan.(*[]time.Time))) == false { t.Errorf("failed to encode time.Time[] to timestamptz[]") } }, }, } for i, tt := range tests { psName := fmt.Sprintf("ps%d", i) mustPrepare(t, conn, psName, tt.sql) err := conn.QueryRow(psName, tt.query).Scan(tt.scan) if err != nil { t.Errorf(`error reading array: %v`, err) } tt.assert(t, tt.query, tt.scan) ensureConnValid(t, conn) } } type shortScanner struct{} func (*shortScanner) Scan(r *pgx.ValueReader) error { r.ReadByte() return nil } func TestShortScanner(t *testing.T) { t.Parallel() conn := mustConnect(t, *defaultConnConfig) defer closeConn(t, conn) rows, err := conn.Query("select 'ab', 'cd' union select 'cd', 'ef'") if err != nil { t.Error(err) } defer rows.Close() for rows.Next() { var s1, s2 shortScanner err = rows.Scan(&s1, &s2) if err != nil { t.Error(err) } } ensureConnValid(t, conn) } func TestEmptyArrayDecoding(t *testing.T) { t.Parallel() conn := mustConnect(t, *defaultConnConfig) defer closeConn(t, conn) var val []string err := conn.QueryRow("select array[]::text[]").Scan(&val) if err != nil { t.Errorf(`error reading array: %v`, err) } if len(val) != 0 { t.Errorf("Expected 0 values, got %d", len(val)) } var n, m int32 err = conn.QueryRow("select 1::integer, array[]::text[], 42::integer").Scan(&n, &val, &m) if err != nil { t.Errorf(`error reading array: %v`, err) } if len(val) != 0 { t.Errorf("Expected 0 values, got %d", len(val)) } if n != 1 { t.Errorf("Expected n to be 1, but it was %d", n) } if m != 42 { t.Errorf("Expected n to be 42, but it was %d", n) } rows, err := conn.Query("select 1::integer, array['test']::text[] union select 2::integer, array[]::text[] union select 3::integer, array['test']::text[]") if err != nil { t.Errorf(`error retrieving rows with array: %v`, err) } defer rows.Close() for rows.Next() { err = rows.Scan(&n, &val) if err != nil { t.Errorf(`error reading array: %v`, err) } } ensureConnValid(t, conn) } func TestNullXMismatch(t *testing.T) { t.Parallel() conn := mustConnect(t, *defaultConnConfig) defer closeConn(t, conn) type allTypes struct { s pgx.NullString i16 pgx.NullInt16 i32 pgx.NullInt32 i64 pgx.NullInt64 f32 pgx.NullFloat32 f64 pgx.NullFloat64 b pgx.NullBool t pgx.NullTime } var actual, zero allTypes tests := []struct { sql string queryArgs []interface{} scanArgs []interface{} err string }{ {"select $1::date", []interface{}{pgx.NullString{String: "foo", Valid: true}}, []interface{}{&actual.s}, "invalid input syntax for type date"}, {"select $1::date", []interface{}{pgx.NullInt16{Int16: 1, Valid: true}}, []interface{}{&actual.i16}, "cannot encode into OID 1082"}, {"select $1::date", []interface{}{pgx.NullInt32{Int32: 1, Valid: true}}, []interface{}{&actual.i32}, "cannot encode into OID 1082"}, {"select $1::date", []interface{}{pgx.NullInt64{Int64: 1, Valid: true}}, []interface{}{&actual.i64}, "cannot encode into OID 1082"}, {"select $1::date", []interface{}{pgx.NullFloat32{Float32: 1.23, Valid: true}}, []interface{}{&actual.f32}, "cannot encode into OID 1082"}, {"select $1::date", []interface{}{pgx.NullFloat64{Float64: 1.23, Valid: true}}, []interface{}{&actual.f64}, "cannot encode into OID 1082"}, {"select $1::date", []interface{}{pgx.NullBool{Bool: true, Valid: true}}, []interface{}{&actual.b}, "cannot encode into OID 1082"}, {"select $1::date", []interface{}{pgx.NullTime{Time: time.Unix(123, 5000), Valid: true}}, []interface{}{&actual.t}, "cannot encode into OID 1082"}, } for i, tt := range tests { psName := fmt.Sprintf("ps%d", i) mustPrepare(t, conn, psName, tt.sql) actual = zero err := conn.QueryRow(psName, tt.queryArgs...).Scan(tt.scanArgs...) if err == nil || !strings.Contains(err.Error(), tt.err) { t.Errorf(`%d. Expected error to contain "%s", but it didn't: %v`, i, tt.err, err) } ensureConnValid(t, conn) } }