├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── other-issues.md │ ├── feature_request.md │ └── bug_report.md ├── pgproto3 ├── pgproto3_private_test.go ├── testdata │ └── fuzz │ │ └── FuzzFrontend │ │ ├── 39c5e864da4707fc15fea48f7062d6a07796fdc43b33e0ba9dbd7074a0211fa6 │ │ ├── a661fb98e802839f0a7361160fbc6e28794612a411d00bde104364ee281c4214 │ │ ├── 9b06792b1aaac8a907dbfa04d526ae14326c8573b7409032caac8461e83065f7 │ │ └── fc98dcd487a5173b38763a5f7dd023933f3a86ab566e3f2b091eb36248107eb4 ├── README.md ├── bind_test.go ├── query_test.go ├── copy_both_response_test.go ├── doc.go ├── notice_response.go ├── big_endian.go ├── sync.go ├── flush.go ├── no_data.go ├── terminate.go ├── bind_complete.go ├── close_complete.go ├── parse_complete.go ├── gss_response.go ├── portal_suspended.go ├── empty_query_response.go ├── copy_done.go ├── ssl_request.go ├── query.go ├── gss_enc_request.go ├── copy_fail.go ├── example │ └── pgfortune │ │ ├── main.go │ │ └── README.md ├── authentication_gss.go ├── fuzz_test.go ├── password_message.go ├── trace_test.go ├── cancel_request.go ├── sasl_response.go ├── backend_key_data.go ├── parameter_status.go ├── authentication_ok.go ├── authentication_gss_continue.go ├── execute.go ├── copy_data.go ├── authentication_cleartext_password.go ├── ready_for_query.go ├── chunkreader_test.go ├── command_complete.go ├── notification_response.go ├── parameter_description.go ├── function_call_test.go ├── close.go ├── describe.go ├── authentication_sasl_final.go ├── authentication_sasl_continue.go ├── authentication_md5_password.go ├── authentication_sasl.go ├── parse.go ├── sasl_initial_response.go ├── function_call_response.go └── startup_message.go ├── pgconn ├── export_test.go ├── README.md ├── helper_test.go ├── pgconn_private_test.go ├── benchmark_private_test.go ├── errors_test.go ├── doc.go ├── defaults.go ├── defaults_windows.go └── ctxwatch │ └── context_watcher.go ├── examples ├── todo │ ├── structure.sql │ └── README.md ├── url_shortener │ ├── structure.sql │ ├── README.md │ └── main.go ├── README.md └── chat │ ├── README.md │ └── main.go ├── testsetup ├── README.md ├── postgresql_ssl.conf ├── pg_hba.conf └── postgresql_setup.sql ├── pgtype ├── integration_benchmark_test_gen.sh ├── register_default_pg_types_disabled.go ├── text_format_only_codec.go ├── zeronull │ ├── zeronull.go │ ├── text_test.go │ ├── zeronull_test.go │ ├── float8_test.go │ ├── uuid_test.go │ ├── int_test.go.erb │ ├── doc.go │ ├── timestamp_test.go │ ├── text.go │ ├── timestamptz_test.go │ ├── float8.go │ ├── uuid.go │ ├── int_test.go │ ├── timestamp.go │ ├── timestamptz.go │ └── int.go.erb ├── ltree_test.go ├── uint32_test.go ├── example_json_test.go ├── qchar_test.go ├── circle_test.go ├── uint64_test.go ├── box_test.go ├── lseg_test.go ├── tid_test.go ├── register_default_pg_types.go ├── line_test.go ├── polygon_test.go ├── example_custom_type_test.go ├── bool_test.go ├── path_test.go ├── derived_types_test.go ├── bits_test.go ├── record_codec_test.go ├── macaddr_test.go ├── enum_codec_test.go ├── float4_test.go ├── float8_test.go ├── integration_benchmark_test.go.erb └── example_child_records_test.go ├── internal ├── pgio │ ├── README.md │ ├── doc.go │ ├── write.go │ └── write_test.go ├── iobufpool │ ├── iobufpool_internal_test.go │ └── iobufpool.go ├── sanitize │ ├── sanitize_fuzz_test.go │ ├── sanitize_bench_test.go │ └── benchmmark.sh └── stmtcache │ └── stmtcache.go ├── .gitignore ├── pgx_test.go ├── Rakefile ├── large_objects_private_test.go ├── go.mod ├── .golangci.yml ├── log └── testingadapter │ └── adapter.go ├── pgxpool ├── helper_test.go ├── tracer.go ├── doc.go ├── batch_results.go ├── bench_test.go ├── conn_test.go └── tx_test.go ├── LICENSE ├── derived_types_test.go ├── values.go ├── conn_internal_test.go ├── pgbouncer_test.go ├── pipeline_test.go └── ci └── setup_test.bash /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: jackc 2 | -------------------------------------------------------------------------------- /pgproto3/pgproto3_private_test.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | const MaxMessageBodyLen = maxMessageBodyLen 4 | -------------------------------------------------------------------------------- /pgconn/export_test.go: -------------------------------------------------------------------------------- 1 | // File export_test exports some methods for better testing. 2 | 3 | package pgconn 4 | -------------------------------------------------------------------------------- /examples/todo/structure.sql: -------------------------------------------------------------------------------- 1 | create table tasks ( 2 | id serial primary key, 3 | description text not null 4 | ); 5 | -------------------------------------------------------------------------------- /examples/url_shortener/structure.sql: -------------------------------------------------------------------------------- 1 | create table shortened_urls ( 2 | id text primary key, 3 | url text not null 4 | ); -------------------------------------------------------------------------------- /testsetup/README.md: -------------------------------------------------------------------------------- 1 | # Test Setup 2 | 3 | This directory contains miscellaneous files used to setup a test database. 4 | -------------------------------------------------------------------------------- /testsetup/postgresql_ssl.conf: -------------------------------------------------------------------------------- 1 | ssl = on 2 | ssl_cert_file = 'server.crt' 3 | ssl_key_file = 'server.key' 4 | ssl_ca_file = 'root.crt' 5 | -------------------------------------------------------------------------------- /pgtype/integration_benchmark_test_gen.sh: -------------------------------------------------------------------------------- 1 | erb integration_benchmark_test.go.erb > integration_benchmark_test.go 2 | goimports -w integration_benchmark_test.go 3 | -------------------------------------------------------------------------------- /pgproto3/testdata/fuzz/FuzzFrontend/39c5e864da4707fc15fea48f7062d6a07796fdc43b33e0ba9dbd7074a0211fa6: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | byte('A') 3 | uint32(5) 4 | []byte("0") 5 | -------------------------------------------------------------------------------- /pgproto3/testdata/fuzz/FuzzFrontend/a661fb98e802839f0a7361160fbc6e28794612a411d00bde104364ee281c4214: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | byte('C') 3 | uint32(4) 4 | []byte("0") 5 | -------------------------------------------------------------------------------- /pgproto3/testdata/fuzz/FuzzFrontend/9b06792b1aaac8a907dbfa04d526ae14326c8573b7409032caac8461e83065f7: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | byte('D') 3 | uint32(21) 4 | []byte("00\xb300000000000000") 5 | -------------------------------------------------------------------------------- /pgtype/register_default_pg_types_disabled.go: -------------------------------------------------------------------------------- 1 | //go:build nopgxregisterdefaulttypes 2 | 3 | package pgtype 4 | 5 | func registerDefaultPgTypeVariants[T any](m *Map, name string) { 6 | } 7 | -------------------------------------------------------------------------------- /internal/pgio/README.md: -------------------------------------------------------------------------------- 1 | # pgio 2 | 3 | Package pgio is a low-level toolkit building messages in the PostgreSQL wire protocol. 4 | 5 | pgio provides functions for appending integers to a []byte while doing byte 6 | order conversion. 7 | -------------------------------------------------------------------------------- /internal/pgio/doc.go: -------------------------------------------------------------------------------- 1 | // Package pgio is a low-level toolkit building messages in the PostgreSQL wire protocol. 2 | /* 3 | pgio provides functions for appending integers to a []byte while doing byte 4 | order conversion. 5 | */ 6 | package pgio 7 | -------------------------------------------------------------------------------- /pgproto3/testdata/fuzz/FuzzFrontend/fc98dcd487a5173b38763a5f7dd023933f3a86ab566e3f2b091eb36248107eb4: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | byte('R') 3 | uint32(13) 4 | []byte("\x00\x00\x00\n0\x12\xebG\x8dI']G\xdac\x95\xb7\x18\xb0\x02\xe8m\xc2\x00\xef\x03\x12\x1b\xbdj\x10\x9f\xf9\xeb\xb8") 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other-issues.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other issues 3 | about: Any issue that is not a bug or a feature request 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please describe the issue in detail. If this is a question about how to use pgx please use discussions instead. 11 | -------------------------------------------------------------------------------- /pgproto3/README.md: -------------------------------------------------------------------------------- 1 | # pgproto3 2 | 3 | Package pgproto3 is an encoder and decoder of the PostgreSQL wire protocol version 3. 4 | 5 | pgproto3 can be used as a foundation for PostgreSQL drivers, proxies, mock servers, load balancers and more. 6 | 7 | See example/pgfortune for a playful example of a fake PostgreSQL server. 8 | -------------------------------------------------------------------------------- /pgtype/text_format_only_codec.go: -------------------------------------------------------------------------------- 1 | package pgtype 2 | 3 | type TextFormatOnlyCodec struct { 4 | Codec 5 | } 6 | 7 | func (c *TextFormatOnlyCodec) FormatSupported(format int16) bool { 8 | return format == TextFormatCode && c.Codec.FormatSupported(format) 9 | } 10 | 11 | func (TextFormatOnlyCodec) PreferredFormat() int16 { 12 | return TextFormatCode 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | .envrc 25 | /.testdb 26 | 27 | .DS_Store 28 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | * chat is a command line chat program using listen/notify. 4 | * todo is a command line todo list that demonstrates basic CRUD actions. 5 | * url_shortener contains a simple example of using pgx in a web context. 6 | * [Tern](https://github.com/jackc/tern) is a migration tool that uses pgx. 7 | * [The Pithy Reader](https://github.com/jackc/tpr) is a RSS aggregator that uses pgx. 8 | -------------------------------------------------------------------------------- /testsetup/pg_hba.conf: -------------------------------------------------------------------------------- 1 | local all postgres trust 2 | local all all trust 3 | host all pgx_md5 127.0.0.1/32 md5 4 | host all pgx_scram 127.0.0.1/32 scram-sha-256 5 | host all pgx_pw 127.0.0.1/32 password 6 | hostssl all pgx_ssl 127.0.0.1/32 scram-sha-256 7 | hostssl all pgx_sslcert 127.0.0.1/32 cert 8 | -------------------------------------------------------------------------------- /pgx_test.go: -------------------------------------------------------------------------------- 1 | package pgx_test 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/jackc/pgx/v5" 9 | _ "github.com/jackc/pgx/v5/stdlib" 10 | ) 11 | 12 | func skipCockroachDB(t testing.TB, msg string) { 13 | conn, err := pgx.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE")) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | defer conn.Close(context.Background()) 18 | 19 | if conn.PgConn().ParameterStatus("crdb_version") != "" { 20 | t.Skip(msg) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "erb" 2 | 3 | rule '.go' => '.go.erb' do |task| 4 | erb = ERB.new(File.read(task.source)) 5 | File.write(task.name, "// Code generated from #{task.source}. DO NOT EDIT.\n\n" + erb.result(binding)) 6 | sh "goimports", "-w", task.name 7 | end 8 | 9 | generated_code_files = [ 10 | "pgtype/int.go", 11 | "pgtype/int_test.go", 12 | "pgtype/integration_benchmark_test.go", 13 | "pgtype/zeronull/int.go", 14 | "pgtype/zeronull/int_test.go" 15 | ] 16 | 17 | desc "Generate code" 18 | task generate: generated_code_files 19 | -------------------------------------------------------------------------------- /large_objects_private_test.go: -------------------------------------------------------------------------------- 1 | package pgx 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // SetMaxLargeObjectMessageLength sets internal maxLargeObjectMessageLength variable 8 | // to the given length for the duration of the test. 9 | // 10 | // Tests using this helper should not use t.Parallel(). 11 | func SetMaxLargeObjectMessageLength(t *testing.T, length int) { 12 | t.Helper() 13 | 14 | original := maxLargeObjectMessageLength 15 | t.Cleanup(func() { 16 | maxLargeObjectMessageLength = original 17 | }) 18 | 19 | maxLargeObjectMessageLength = length 20 | } 21 | -------------------------------------------------------------------------------- /pgproto3/bind_test.go: -------------------------------------------------------------------------------- 1 | package pgproto3_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgx/v5/pgproto3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestBindBiggerThanMaxMessageBodyLen(t *testing.T) { 11 | t.Parallel() 12 | 13 | // Maximum allowed size. 14 | _, err := (&pgproto3.Bind{Parameters: [][]byte{make([]byte, pgproto3.MaxMessageBodyLen-16)}}).Encode(nil) 15 | require.NoError(t, err) 16 | 17 | // 1 byte too big 18 | _, err = (&pgproto3.Bind{Parameters: [][]byte{make([]byte, pgproto3.MaxMessageBodyLen-15)}}).Encode(nil) 19 | require.Error(t, err) 20 | } 21 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jackc/pgx/v5 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/jackc/pgpassfile v1.0.0 7 | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 8 | github.com/jackc/puddle/v2 v2.2.2 9 | github.com/stretchr/testify v1.11.1 10 | golang.org/x/sync v0.17.0 11 | golang.org/x/text v0.29.0 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/kr/pretty v0.3.0 // indirect 17 | github.com/pmezard/go-difflib v1.0.0 // indirect 18 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 19 | gopkg.in/yaml.v3 v3.0.1 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /pgproto3/query_test.go: -------------------------------------------------------------------------------- 1 | package pgproto3_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgx/v5/pgproto3" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestQueryBiggerThanMaxMessageBodyLen(t *testing.T) { 11 | t.Parallel() 12 | 13 | // Maximum allowed size. 4 bytes for size and 1 byte for 0 terminated string. 14 | _, err := (&pgproto3.Query{String: string(make([]byte, pgproto3.MaxMessageBodyLen-5))}).Encode(nil) 15 | require.NoError(t, err) 16 | 17 | // 1 byte too big 18 | _, err = (&pgproto3.Query{String: string(make([]byte, pgproto3.MaxMessageBodyLen-4))}).Encode(nil) 19 | require.Error(t, err) 20 | } 21 | -------------------------------------------------------------------------------- /examples/chat/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This is a sample chat program implemented using PostgreSQL's listen/notify 4 | functionality with pgx. 5 | 6 | Start multiple instances of this program connected to the same database to chat 7 | between them. 8 | 9 | ## Connection configuration 10 | 11 | The database connection is configured via DATABASE_URL and standard PostgreSQL environment variables (PGHOST, PGUSER, etc.) 12 | 13 | You can either export them then run chat: 14 | 15 | export PGHOST=/private/tmp 16 | ./chat 17 | 18 | Or you can prefix the chat execution with the environment variables: 19 | 20 | PGHOST=/private/tmp ./chat 21 | -------------------------------------------------------------------------------- /pgtype/zeronull/zeronull.go: -------------------------------------------------------------------------------- 1 | package zeronull 2 | 3 | import ( 4 | "github.com/jackc/pgx/v5/pgtype" 5 | ) 6 | 7 | // Register registers the zeronull types so they can be used in query exec modes that do not know the server OIDs. 8 | func Register(m *pgtype.Map) { 9 | m.RegisterDefaultPgType(Float8(0), "float8") 10 | m.RegisterDefaultPgType(Int2(0), "int2") 11 | m.RegisterDefaultPgType(Int4(0), "int4") 12 | m.RegisterDefaultPgType(Int8(0), "int8") 13 | m.RegisterDefaultPgType(Text(""), "text") 14 | m.RegisterDefaultPgType(Timestamp{}, "timestamp") 15 | m.RegisterDefaultPgType(Timestamptz{}, "timestamptz") 16 | m.RegisterDefaultPgType(UUID{}, "uuid") 17 | } 18 | -------------------------------------------------------------------------------- /pgproto3/copy_both_response_test.go: -------------------------------------------------------------------------------- 1 | package pgproto3_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgx/v5/pgproto3" 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestEncodeDecode(t *testing.T) { 12 | srcBytes := []byte{'W', 0x00, 0x00, 0x00, 0x0b, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01} 13 | dstResp := pgproto3.CopyBothResponse{} 14 | err := dstResp.Decode(srcBytes[5:]) 15 | assert.NoError(t, err, "No errors on decode") 16 | dstBytes := []byte{} 17 | dstBytes, err = dstResp.Encode(dstBytes) 18 | require.NoError(t, err) 19 | assert.EqualValues(t, srcBytes, dstBytes, "Expecting src & dest bytes to match") 20 | } 21 | -------------------------------------------------------------------------------- /pgtype/zeronull/text_test.go: -------------------------------------------------------------------------------- 1 | package zeronull_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype/zeronull" 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func TestTextTranscode(t *testing.T) { 12 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "text", []pgxtest.ValueRoundTripTest{ 13 | { 14 | (zeronull.Text)("foo"), 15 | new(zeronull.Text), 16 | isExpectedEq((zeronull.Text)("foo")), 17 | }, 18 | { 19 | nil, 20 | new(zeronull.Text), 21 | isExpectedEq((zeronull.Text)("")), 22 | }, 23 | { 24 | (zeronull.Text)(""), 25 | new(any), 26 | isExpectedEq(nil), 27 | }, 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # See for configurations: https://golangci-lint.run/usage/configuration/ 2 | version: 2 3 | 4 | # See: https://golangci-lint.run/usage/formatters/ 5 | formatters: 6 | default: none 7 | enable: 8 | - gofmt # https://pkg.go.dev/cmd/gofmt 9 | - gofumpt # https://github.com/mvdan/gofumpt 10 | 11 | settings: 12 | gofmt: 13 | simplify: true # Simplify code: gofmt with `-s` option. 14 | 15 | gofumpt: 16 | # Module path which contains the source code being formatted. 17 | # Default: "" 18 | module-path: github.com/jackc/pgx/v5 # Should match with module in go.mod 19 | # Choose whether to use the extra rules. 20 | # Default: false 21 | extra-rules: true 22 | -------------------------------------------------------------------------------- /pgproto3/doc.go: -------------------------------------------------------------------------------- 1 | // Package pgproto3 is an encoder and decoder of the PostgreSQL wire protocol version 3. 2 | // 3 | // The primary interfaces are Frontend and Backend. They correspond to a client and server respectively. Messages are 4 | // sent with Send (or a specialized Send variant). Messages are automatically buffered to minimize small writes. Call 5 | // Flush to ensure a message has actually been sent. 6 | // 7 | // The Trace method of Frontend and Backend can be used to examine the wire-level message traffic. It outputs in a 8 | // similar format to the PQtrace function in libpq. 9 | // 10 | // See https://www.postgresql.org/docs/current/protocol-message-formats.html for meanings of the different messages. 11 | package pgproto3 12 | -------------------------------------------------------------------------------- /pgtype/ltree_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype" 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func TestLtreeCodec(t *testing.T) { 12 | skipCockroachDB(t, "Server does not support type ltree") 13 | 14 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, "ltree", []pgxtest.ValueRoundTripTest{ 15 | { 16 | Param: "A.B.C", 17 | Result: new(string), 18 | Test: isExpectedEq("A.B.C"), 19 | }, 20 | { 21 | Param: pgtype.Text{String: "", Valid: true}, 22 | Result: new(pgtype.Text), 23 | Test: isExpectedEq(pgtype.Text{String: "", Valid: true}), 24 | }, 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /pgtype/uint32_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype" 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func TestUint32Codec(t *testing.T) { 12 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, "oid", []pgxtest.ValueRoundTripTest{ 13 | { 14 | pgtype.Uint32{Uint32: pgtype.TextOID, Valid: true}, 15 | new(pgtype.Uint32), 16 | isExpectedEq(pgtype.Uint32{Uint32: pgtype.TextOID, Valid: true}), 17 | }, 18 | {pgtype.Uint32{}, new(pgtype.Uint32), isExpectedEq(pgtype.Uint32{})}, 19 | {nil, new(pgtype.Uint32), isExpectedEq(pgtype.Uint32{})}, 20 | {"1147", new(string), isExpectedEq("1147")}, 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /pgtype/zeronull/zeronull_test.go: -------------------------------------------------------------------------------- 1 | package zeronull_test 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/jackc/pgx/v5" 9 | "github.com/jackc/pgx/v5/pgtype/zeronull" 10 | "github.com/jackc/pgx/v5/pgxtest" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | var defaultConnTestRunner pgxtest.ConnTestRunner 15 | 16 | func init() { 17 | defaultConnTestRunner = pgxtest.DefaultConnTestRunner() 18 | defaultConnTestRunner.CreateConfig = func(ctx context.Context, t testing.TB) *pgx.ConnConfig { 19 | config, err := pgx.ParseConfig(os.Getenv("PGX_TEST_DATABASE")) 20 | require.NoError(t, err) 21 | return config 22 | } 23 | defaultConnTestRunner.AfterConnect = func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 24 | zeronull.Register(conn.TypeMap()) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/url_shortener/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This is a sample REST URL shortener service implemented using pgx as the connector to a PostgreSQL data store. 4 | 5 | # Usage 6 | 7 | Create a PostgreSQL database and run structure.sql into it to create the necessary data schema. 8 | 9 | Configure the database connection with `DATABASE_URL` or standard PostgreSQL (`PG*`) environment variables or 10 | 11 | Run main.go: 12 | 13 | ``` 14 | go run main.go 15 | ``` 16 | 17 | ## Create or Update a Shortened URL 18 | 19 | ``` 20 | curl -X PUT -d 'http://www.google.com' http://localhost:8080/google 21 | ``` 22 | 23 | ## Get a Shortened URL 24 | 25 | ``` 26 | curl http://localhost:8080/google 27 | ``` 28 | 29 | ## Delete a Shortened URL 30 | 31 | ``` 32 | curl -X DELETE http://localhost:8080/google 33 | ``` 34 | -------------------------------------------------------------------------------- /pgtype/example_json_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/jackc/pgx/v5" 9 | ) 10 | 11 | func Example_json() { 12 | conn, err := pgx.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE")) 13 | if err != nil { 14 | fmt.Printf("Unable to establish connection: %v", err) 15 | return 16 | } 17 | 18 | type person struct { 19 | Name string `json:"name"` 20 | Age int `json:"age"` 21 | } 22 | 23 | input := person{ 24 | Name: "John", 25 | Age: 42, 26 | } 27 | 28 | var output person 29 | 30 | err = conn.QueryRow(context.Background(), "select $1::json", input).Scan(&output) 31 | if err != nil { 32 | fmt.Println(err) 33 | return 34 | } 35 | 36 | fmt.Println(output.Name, output.Age) 37 | // Output: 38 | // John 42 39 | } 40 | -------------------------------------------------------------------------------- /pgproto3/notice_response.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | type NoticeResponse ErrorResponse 4 | 5 | // Backend identifies this message as sendable by the PostgreSQL backend. 6 | func (*NoticeResponse) Backend() {} 7 | 8 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 9 | // type identifier and 4 byte message length. 10 | func (dst *NoticeResponse) Decode(src []byte) error { 11 | return (*ErrorResponse)(dst).Decode(src) 12 | } 13 | 14 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 15 | func (src *NoticeResponse) Encode(dst []byte) ([]byte, error) { 16 | dst, sp := beginMessage(dst, 'N') 17 | dst = (*ErrorResponse)(src).appendFields(dst) 18 | return finishMessage(dst, sp) 19 | } 20 | -------------------------------------------------------------------------------- /pgtype/zeronull/float8_test.go: -------------------------------------------------------------------------------- 1 | package zeronull_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype/zeronull" 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func isExpectedEq(a any) func(any) bool { 12 | return func(v any) bool { 13 | return a == v 14 | } 15 | } 16 | 17 | func TestFloat8Transcode(t *testing.T) { 18 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "float8", []pgxtest.ValueRoundTripTest{ 19 | { 20 | (zeronull.Float8)(1), 21 | new(zeronull.Float8), 22 | isExpectedEq((zeronull.Float8)(1)), 23 | }, 24 | { 25 | nil, 26 | new(zeronull.Float8), 27 | isExpectedEq((zeronull.Float8)(0)), 28 | }, 29 | { 30 | (zeronull.Float8)(0), 31 | new(any), 32 | isExpectedEq(nil), 33 | }, 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /pgtype/zeronull/uuid_test.go: -------------------------------------------------------------------------------- 1 | package zeronull_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype/zeronull" 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func TestUUIDTranscode(t *testing.T) { 12 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "uuid", []pgxtest.ValueRoundTripTest{ 13 | { 14 | (zeronull.UUID)([16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}), 15 | new(zeronull.UUID), 16 | isExpectedEq((zeronull.UUID)([16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})), 17 | }, 18 | { 19 | nil, 20 | new(zeronull.UUID), 21 | isExpectedEq((zeronull.UUID)([16]byte{})), 22 | }, 23 | { 24 | (zeronull.UUID)([16]byte{}), 25 | new(any), 26 | isExpectedEq(nil), 27 | }, 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /pgproto3/big_endian.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/binary" 5 | ) 6 | 7 | type BigEndianBuf [8]byte 8 | 9 | func (b BigEndianBuf) Int16(n int16) []byte { 10 | buf := b[0:2] 11 | binary.BigEndian.PutUint16(buf, uint16(n)) 12 | return buf 13 | } 14 | 15 | func (b BigEndianBuf) Uint16(n uint16) []byte { 16 | buf := b[0:2] 17 | binary.BigEndian.PutUint16(buf, n) 18 | return buf 19 | } 20 | 21 | func (b BigEndianBuf) Int32(n int32) []byte { 22 | buf := b[0:4] 23 | binary.BigEndian.PutUint32(buf, uint32(n)) 24 | return buf 25 | } 26 | 27 | func (b BigEndianBuf) Uint32(n uint32) []byte { 28 | buf := b[0:4] 29 | binary.BigEndian.PutUint32(buf, n) 30 | return buf 31 | } 32 | 33 | func (b BigEndianBuf) Int64(n int64) []byte { 34 | buf := b[0:8] 35 | binary.BigEndian.PutUint64(buf, uint64(n)) 36 | return buf 37 | } 38 | -------------------------------------------------------------------------------- /log/testingadapter/adapter.go: -------------------------------------------------------------------------------- 1 | // Package testingadapter provides a logger that writes to a test or benchmark 2 | // log. 3 | package testingadapter 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | 9 | "github.com/jackc/pgx/v5/tracelog" 10 | ) 11 | 12 | // TestingLogger interface defines the subset of testing.TB methods used by this 13 | // adapter. 14 | type TestingLogger interface { 15 | Log(args ...any) 16 | } 17 | 18 | type Logger struct { 19 | l TestingLogger 20 | } 21 | 22 | func NewLogger(l TestingLogger) *Logger { 23 | return &Logger{l: l} 24 | } 25 | 26 | func (l *Logger) Log(ctx context.Context, level tracelog.LogLevel, msg string, data map[string]any) { 27 | logArgs := make([]any, 0, 2+len(data)) 28 | logArgs = append(logArgs, level, msg) 29 | for k, v := range data { 30 | logArgs = append(logArgs, fmt.Sprintf("%s=%v", k, v)) 31 | } 32 | l.l.Log(logArgs...) 33 | } 34 | -------------------------------------------------------------------------------- /pgtype/zeronull/int_test.go.erb: -------------------------------------------------------------------------------- 1 | package zeronull_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgx/v5/pgtype/testutil" 7 | "github.com/jackc/pgx/v5/pgtype/zeronull" 8 | ) 9 | 10 | <% [2, 4, 8].each do |pg_byte_size| %> 11 | <% pg_bit_size = pg_byte_size * 8 %> 12 | func TestInt<%= pg_byte_size %>Transcode(t *testing.T) { 13 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int<%= pg_byte_size %>", []pgxtest.ValueRoundTripTest{ 14 | { 15 | (zeronull.Int<%= pg_byte_size %>)(1), 16 | new(zeronull.Int<%= pg_byte_size %>), 17 | isExpectedEq((zeronull.Int<%= pg_byte_size %>)(1)), 18 | }, 19 | { 20 | nil, 21 | new(zeronull.Int<%= pg_byte_size %>), 22 | isExpectedEq((zeronull.Int<%= pg_byte_size %>)(0)), 23 | }, 24 | { 25 | (zeronull.Int<%= pg_byte_size %>)(0), 26 | new(any), 27 | isExpectedEq(nil), 28 | }, 29 | }) 30 | } 31 | <% end %> 32 | -------------------------------------------------------------------------------- /pgxpool/helper_test.go: -------------------------------------------------------------------------------- 1 | package pgxpool_test 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "time" 7 | 8 | "github.com/jackc/pgx/v5/pgconn" 9 | ) 10 | 11 | // delayProxy is a that introduces a configurable delay on reads from the database connection. 12 | type delayProxy struct { 13 | net.Conn 14 | readDelay time.Duration 15 | } 16 | 17 | func newDelayProxy(conn net.Conn, readDelay time.Duration) *delayProxy { 18 | p := &delayProxy{ 19 | Conn: conn, 20 | readDelay: readDelay, 21 | } 22 | 23 | return p 24 | } 25 | 26 | func (dp *delayProxy) Read(b []byte) (int, error) { 27 | if dp.readDelay > 0 { 28 | time.Sleep(dp.readDelay) 29 | } 30 | 31 | return dp.Conn.Read(b) 32 | } 33 | 34 | func newDelayProxyDialFunc(readDelay time.Duration) pgconn.DialFunc { 35 | return func(ctx context.Context, network, addr string) (net.Conn, error) { 36 | conn, err := net.Dial(network, addr) 37 | return newDelayProxy(conn, readDelay), err 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /testsetup/postgresql_setup.sql: -------------------------------------------------------------------------------- 1 | -- Create extensions and types. 2 | create extension hstore; 3 | create extension ltree; 4 | create domain uint64 as numeric(20,0); 5 | 6 | -- Create users for different types of connections and authentication. 7 | create user pgx_ssl with superuser PASSWORD 'secret'; 8 | create user pgx_sslcert with superuser PASSWORD 'secret'; 9 | set password_encryption = md5; 10 | create user pgx_md5 with superuser PASSWORD 'secret'; 11 | set password_encryption = 'scram-sha-256'; 12 | create user pgx_pw with superuser PASSWORD 'secret'; 13 | create user pgx_scram with superuser PASSWORD 'secret'; 14 | \set whoami `whoami` 15 | create user :whoami with superuser; -- unix domain socket user 16 | 17 | 18 | -- The tricky test user, below, has to actually exist so that it can be used in a test 19 | -- of aclitem formatting. It turns out aclitems cannot contain non-existing users/roles. 20 | create user " tricky, ' } "" \\ test user " superuser password 'secret'; 21 | -------------------------------------------------------------------------------- /internal/iobufpool/iobufpool_internal_test.go: -------------------------------------------------------------------------------- 1 | package iobufpool 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPoolIdx(t *testing.T) { 10 | tests := []struct { 11 | size int 12 | expected int 13 | }{ 14 | {size: 0, expected: 0}, 15 | {size: 1, expected: 0}, 16 | {size: 255, expected: 0}, 17 | {size: 256, expected: 0}, 18 | {size: 257, expected: 1}, 19 | {size: 511, expected: 1}, 20 | {size: 512, expected: 1}, 21 | {size: 513, expected: 2}, 22 | {size: 1023, expected: 2}, 23 | {size: 1024, expected: 2}, 24 | {size: 1025, expected: 3}, 25 | {size: 2047, expected: 3}, 26 | {size: 2048, expected: 3}, 27 | {size: 2049, expected: 4}, 28 | {size: 8388607, expected: 15}, 29 | {size: 8388608, expected: 15}, 30 | {size: 8388609, expected: 16}, 31 | } 32 | for _, tt := range tests { 33 | idx := getPoolIdx(tt.size) 34 | assert.Equalf(t, tt.expected, idx, "size: %d", tt.size) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pgtype/qchar_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "math" 6 | "testing" 7 | 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func TestQcharTranscode(t *testing.T) { 12 | skipCockroachDB(t, "Server does not support qchar") 13 | 14 | var tests []pgxtest.ValueRoundTripTest 15 | for i := 0; i <= math.MaxUint8; i++ { 16 | tests = append(tests, pgxtest.ValueRoundTripTest{rune(i), new(rune), isExpectedEq(rune(i))}) 17 | tests = append(tests, pgxtest.ValueRoundTripTest{byte(i), new(byte), isExpectedEq(byte(i))}) 18 | } 19 | tests = append(tests, pgxtest.ValueRoundTripTest{nil, new(*rune), isExpectedEq((*rune)(nil))}) 20 | tests = append(tests, pgxtest.ValueRoundTripTest{nil, new(*byte), isExpectedEq((*byte)(nil))}) 21 | 22 | // Can only test with known OIDs as rune and byte would be considered numbers. 23 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, `"char"`, tests) 24 | } 25 | -------------------------------------------------------------------------------- /pgtype/circle_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype" 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func TestCircleTranscode(t *testing.T) { 12 | skipCockroachDB(t, "Server does not support box type") 13 | 14 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "circle", []pgxtest.ValueRoundTripTest{ 15 | { 16 | pgtype.Circle{P: pgtype.Vec2{1.234, 5.67890123}, R: 3.5, Valid: true}, 17 | new(pgtype.Circle), 18 | isExpectedEq(pgtype.Circle{P: pgtype.Vec2{1.234, 5.67890123}, R: 3.5, Valid: true}), 19 | }, 20 | { 21 | pgtype.Circle{P: pgtype.Vec2{1.234, 5.67890123}, R: 3.5, Valid: true}, 22 | new(pgtype.Circle), 23 | isExpectedEq(pgtype.Circle{P: pgtype.Vec2{1.234, 5.67890123}, R: 3.5, Valid: true}), 24 | }, 25 | {pgtype.Circle{}, new(pgtype.Circle), isExpectedEq(pgtype.Circle{})}, 26 | {nil, new(pgtype.Circle), isExpectedEq(pgtype.Circle{})}, 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /pgtype/uint64_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype" 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func TestUint64Codec(t *testing.T) { 12 | skipCockroachDB(t, "Server does not support xid8 (https://github.com/cockroachdb/cockroach/issues/36815)") 13 | skipPostgreSQLVersionLessThan(t, 13) 14 | 15 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, "xid8", []pgxtest.ValueRoundTripTest{ 16 | { 17 | pgtype.Uint64{Uint64: 1 << 36, Valid: true}, 18 | new(pgtype.Uint64), 19 | isExpectedEq(pgtype.Uint64{Uint64: 1 << 36, Valid: true}), 20 | }, 21 | {pgtype.Uint64{}, new(pgtype.Uint64), isExpectedEq(pgtype.Uint64{})}, 22 | {nil, new(pgtype.Uint64), isExpectedEq(pgtype.Uint64{})}, 23 | { 24 | uint64(1 << 36), 25 | new(uint64), 26 | isExpectedEq(uint64(1 << 36)), 27 | }, 28 | {"1147", new(string), isExpectedEq("1147")}, 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /pgxpool/tracer.go: -------------------------------------------------------------------------------- 1 | package pgxpool 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/jackc/pgx/v5" 7 | ) 8 | 9 | // AcquireTracer traces Acquire. 10 | type AcquireTracer interface { 11 | // TraceAcquireStart is called at the beginning of Acquire. 12 | // The returned context is used for the rest of the call and will be passed to the TraceAcquireEnd. 13 | TraceAcquireStart(ctx context.Context, pool *Pool, data TraceAcquireStartData) context.Context 14 | // TraceAcquireEnd is called when a connection has been acquired. 15 | TraceAcquireEnd(ctx context.Context, pool *Pool, data TraceAcquireEndData) 16 | } 17 | 18 | type TraceAcquireStartData struct{} 19 | 20 | type TraceAcquireEndData struct { 21 | Conn *pgx.Conn 22 | Err error 23 | } 24 | 25 | // ReleaseTracer traces Release. 26 | type ReleaseTracer interface { 27 | // TraceRelease is called at the beginning of Release. 28 | TraceRelease(pool *Pool, data TraceReleaseData) 29 | } 30 | 31 | type TraceReleaseData struct { 32 | Conn *pgx.Conn 33 | } 34 | -------------------------------------------------------------------------------- /pgtype/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 | -------------------------------------------------------------------------------- /internal/pgio/write.go: -------------------------------------------------------------------------------- 1 | package pgio 2 | 3 | import "encoding/binary" 4 | 5 | func AppendUint16(buf []byte, n uint16) []byte { 6 | wp := len(buf) 7 | buf = append(buf, 0, 0) 8 | binary.BigEndian.PutUint16(buf[wp:], n) 9 | return buf 10 | } 11 | 12 | func AppendUint32(buf []byte, n uint32) []byte { 13 | wp := len(buf) 14 | buf = append(buf, 0, 0, 0, 0) 15 | binary.BigEndian.PutUint32(buf[wp:], n) 16 | return buf 17 | } 18 | 19 | func AppendUint64(buf []byte, n uint64) []byte { 20 | wp := len(buf) 21 | buf = append(buf, 0, 0, 0, 0, 0, 0, 0, 0) 22 | binary.BigEndian.PutUint64(buf[wp:], n) 23 | return buf 24 | } 25 | 26 | func AppendInt16(buf []byte, n int16) []byte { 27 | return AppendUint16(buf, uint16(n)) 28 | } 29 | 30 | func AppendInt32(buf []byte, n int32) []byte { 31 | return AppendUint32(buf, uint32(n)) 32 | } 33 | 34 | func AppendInt64(buf []byte, n int64) []byte { 35 | return AppendUint64(buf, uint64(n)) 36 | } 37 | 38 | func SetInt32(buf []byte, n int32) { 39 | binary.BigEndian.PutUint32(buf, uint32(n)) 40 | } 41 | -------------------------------------------------------------------------------- /pgproto3/sync.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type Sync struct{} 8 | 9 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 10 | func (*Sync) Frontend() {} 11 | 12 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 13 | // type identifier and 4 byte message length. 14 | func (dst *Sync) Decode(src []byte) error { 15 | if len(src) != 0 { 16 | return &invalidMessageLenErr{messageType: "Sync", expectedLen: 0, actualLen: len(src)} 17 | } 18 | 19 | return nil 20 | } 21 | 22 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 23 | func (src *Sync) Encode(dst []byte) ([]byte, error) { 24 | return append(dst, 'S', 0, 0, 0, 4), nil 25 | } 26 | 27 | // MarshalJSON implements encoding/json.Marshaler. 28 | func (src Sync) MarshalJSON() ([]byte, error) { 29 | return json.Marshal(struct { 30 | Type string 31 | }{ 32 | Type: "Sync", 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /pgproto3/flush.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type Flush struct{} 8 | 9 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 10 | func (*Flush) Frontend() {} 11 | 12 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 13 | // type identifier and 4 byte message length. 14 | func (dst *Flush) Decode(src []byte) error { 15 | if len(src) != 0 { 16 | return &invalidMessageLenErr{messageType: "Flush", expectedLen: 0, actualLen: len(src)} 17 | } 18 | 19 | return nil 20 | } 21 | 22 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 23 | func (src *Flush) Encode(dst []byte) ([]byte, error) { 24 | return append(dst, 'H', 0, 0, 0, 4), nil 25 | } 26 | 27 | // MarshalJSON implements encoding/json.Marshaler. 28 | func (src Flush) MarshalJSON() ([]byte, error) { 29 | return json.Marshal(struct { 30 | Type string 31 | }{ 32 | Type: "Flush", 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /pgproto3/no_data.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type NoData struct{} 8 | 9 | // Backend identifies this message as sendable by the PostgreSQL backend. 10 | func (*NoData) Backend() {} 11 | 12 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 13 | // type identifier and 4 byte message length. 14 | func (dst *NoData) Decode(src []byte) error { 15 | if len(src) != 0 { 16 | return &invalidMessageLenErr{messageType: "NoData", expectedLen: 0, actualLen: len(src)} 17 | } 18 | 19 | return nil 20 | } 21 | 22 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 23 | func (src *NoData) Encode(dst []byte) ([]byte, error) { 24 | return append(dst, 'n', 0, 0, 0, 4), nil 25 | } 26 | 27 | // MarshalJSON implements encoding/json.Marshaler. 28 | func (src NoData) MarshalJSON() ([]byte, error) { 29 | return json.Marshal(struct { 30 | Type string 31 | }{ 32 | Type: "NoData", 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /pgproto3/terminate.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type Terminate struct{} 8 | 9 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 10 | func (*Terminate) Frontend() {} 11 | 12 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 13 | // type identifier and 4 byte message length. 14 | func (dst *Terminate) Decode(src []byte) error { 15 | if len(src) != 0 { 16 | return &invalidMessageLenErr{messageType: "Terminate", expectedLen: 0, actualLen: len(src)} 17 | } 18 | 19 | return nil 20 | } 21 | 22 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 23 | func (src *Terminate) Encode(dst []byte) ([]byte, error) { 24 | return append(dst, 'X', 0, 0, 0, 4), nil 25 | } 26 | 27 | // MarshalJSON implements encoding/json.Marshaler. 28 | func (src Terminate) MarshalJSON() ([]byte, error) { 29 | return json.Marshal(struct { 30 | Type string 31 | }{ 32 | Type: "Terminate", 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /pgtype/zeronull/timestamp_test.go: -------------------------------------------------------------------------------- 1 | package zeronull_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/jackc/pgx/v5/pgtype/zeronull" 9 | "github.com/jackc/pgx/v5/pgxtest" 10 | ) 11 | 12 | func isExpectedEqTimestamp(a any) func(any) bool { 13 | return func(v any) bool { 14 | at := time.Time(a.(zeronull.Timestamp)) 15 | vt := time.Time(v.(zeronull.Timestamp)) 16 | 17 | return at.Equal(vt) 18 | } 19 | } 20 | 21 | func TestTimestampTranscode(t *testing.T) { 22 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "timestamp", []pgxtest.ValueRoundTripTest{ 23 | { 24 | (zeronull.Timestamp)(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)), 25 | new(zeronull.Timestamp), 26 | isExpectedEqTimestamp((zeronull.Timestamp)(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC))), 27 | }, 28 | { 29 | nil, 30 | new(zeronull.Timestamp), 31 | isExpectedEqTimestamp((zeronull.Timestamp)(time.Time{})), 32 | }, 33 | { 34 | (zeronull.Timestamp)(time.Time{}), 35 | new(any), 36 | isExpectedEq(nil), 37 | }, 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /pgproto3/bind_complete.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type BindComplete struct{} 8 | 9 | // Backend identifies this message as sendable by the PostgreSQL backend. 10 | func (*BindComplete) Backend() {} 11 | 12 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 13 | // type identifier and 4 byte message length. 14 | func (dst *BindComplete) Decode(src []byte) error { 15 | if len(src) != 0 { 16 | return &invalidMessageLenErr{messageType: "BindComplete", expectedLen: 0, actualLen: len(src)} 17 | } 18 | 19 | return nil 20 | } 21 | 22 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 23 | func (src *BindComplete) Encode(dst []byte) ([]byte, error) { 24 | return append(dst, '2', 0, 0, 0, 4), nil 25 | } 26 | 27 | // MarshalJSON implements encoding/json.Marshaler. 28 | func (src BindComplete) MarshalJSON() ([]byte, error) { 29 | return json.Marshal(struct { 30 | Type string 31 | }{ 32 | Type: "BindComplete", 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /pgconn/README.md: -------------------------------------------------------------------------------- 1 | # pgconn 2 | 3 | Package pgconn is a low-level PostgreSQL database driver. It operates at nearly the same level as the C library libpq. 4 | It is primarily intended to serve as the foundation for higher level libraries such as https://github.com/jackc/pgx. 5 | Applications should handle normal queries with a higher level library and only use pgconn directly when required for 6 | low-level access to PostgreSQL functionality. 7 | 8 | ## Example Usage 9 | 10 | ```go 11 | pgConn, err := pgconn.Connect(context.Background(), os.Getenv("DATABASE_URL")) 12 | if err != nil { 13 | log.Fatalln("pgconn failed to connect:", err) 14 | } 15 | defer pgConn.Close(context.Background()) 16 | 17 | result := pgConn.ExecParams(context.Background(), "SELECT email FROM users WHERE id=$1", [][]byte{[]byte("123")}, nil, nil, nil) 18 | for result.NextRow() { 19 | fmt.Println("User 123 has email:", string(result.Values()[0])) 20 | } 21 | _, err = result.Close() 22 | if err != nil { 23 | log.Fatalln("failed reading result:", err) 24 | } 25 | ``` 26 | 27 | ## Testing 28 | 29 | See CONTRIBUTING.md for setup instructions. 30 | -------------------------------------------------------------------------------- /pgproto3/close_complete.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type CloseComplete struct{} 8 | 9 | // Backend identifies this message as sendable by the PostgreSQL backend. 10 | func (*CloseComplete) Backend() {} 11 | 12 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 13 | // type identifier and 4 byte message length. 14 | func (dst *CloseComplete) Decode(src []byte) error { 15 | if len(src) != 0 { 16 | return &invalidMessageLenErr{messageType: "CloseComplete", expectedLen: 0, actualLen: len(src)} 17 | } 18 | 19 | return nil 20 | } 21 | 22 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 23 | func (src *CloseComplete) Encode(dst []byte) ([]byte, error) { 24 | return append(dst, '3', 0, 0, 0, 4), nil 25 | } 26 | 27 | // MarshalJSON implements encoding/json.Marshaler. 28 | func (src CloseComplete) MarshalJSON() ([]byte, error) { 29 | return json.Marshal(struct { 30 | Type string 31 | }{ 32 | Type: "CloseComplete", 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /pgproto3/parse_complete.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type ParseComplete struct{} 8 | 9 | // Backend identifies this message as sendable by the PostgreSQL backend. 10 | func (*ParseComplete) Backend() {} 11 | 12 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 13 | // type identifier and 4 byte message length. 14 | func (dst *ParseComplete) Decode(src []byte) error { 15 | if len(src) != 0 { 16 | return &invalidMessageLenErr{messageType: "ParseComplete", expectedLen: 0, actualLen: len(src)} 17 | } 18 | 19 | return nil 20 | } 21 | 22 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 23 | func (src *ParseComplete) Encode(dst []byte) ([]byte, error) { 24 | return append(dst, '1', 0, 0, 0, 4), nil 25 | } 26 | 27 | // MarshalJSON implements encoding/json.Marshaler. 28 | func (src ParseComplete) MarshalJSON() ([]byte, error) { 29 | return json.Marshal(struct { 30 | Type string 31 | }{ 32 | Type: "ParseComplete", 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /pgtype/zeronull/text.go: -------------------------------------------------------------------------------- 1 | package zeronull 2 | 3 | import ( 4 | "database/sql/driver" 5 | 6 | "github.com/jackc/pgx/v5/pgtype" 7 | ) 8 | 9 | type Text string 10 | 11 | // SkipUnderlyingTypePlan implements the [pgtype.SkipUnderlyingTypePlanner] interface. 12 | func (Text) SkipUnderlyingTypePlan() {} 13 | 14 | // ScanText implements the [pgtype.TextScanner] interface. 15 | func (dst *Text) ScanText(v pgtype.Text) error { 16 | if !v.Valid { 17 | *dst = "" 18 | return nil 19 | } 20 | 21 | *dst = Text(v.String) 22 | 23 | return nil 24 | } 25 | 26 | // Scan implements the [database/sql.Scanner] interface. 27 | func (dst *Text) Scan(src any) error { 28 | if src == nil { 29 | *dst = "" 30 | return nil 31 | } 32 | 33 | var nullable pgtype.Text 34 | err := nullable.Scan(src) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | *dst = Text(nullable.String) 40 | 41 | return nil 42 | } 43 | 44 | // Value implements the [database/sql/driver.Valuer] interface. 45 | func (src Text) Value() (driver.Value, error) { 46 | if src == "" { 47 | return nil, nil 48 | } 49 | return string(src), nil 50 | } 51 | -------------------------------------------------------------------------------- /pgproto3/gss_response.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type GSSResponse struct { 8 | Data []byte 9 | } 10 | 11 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 12 | func (g *GSSResponse) Frontend() {} 13 | 14 | func (g *GSSResponse) Decode(data []byte) error { 15 | g.Data = data 16 | return nil 17 | } 18 | 19 | func (g *GSSResponse) Encode(dst []byte) ([]byte, error) { 20 | dst, sp := beginMessage(dst, 'p') 21 | dst = append(dst, g.Data...) 22 | return finishMessage(dst, sp) 23 | } 24 | 25 | // MarshalJSON implements encoding/json.Marshaler. 26 | func (g *GSSResponse) MarshalJSON() ([]byte, error) { 27 | return json.Marshal(struct { 28 | Type string 29 | Data []byte 30 | }{ 31 | Type: "GSSResponse", 32 | Data: g.Data, 33 | }) 34 | } 35 | 36 | // UnmarshalJSON implements encoding/json.Unmarshaler. 37 | func (g *GSSResponse) UnmarshalJSON(data []byte) error { 38 | var msg struct { 39 | Data []byte 40 | } 41 | if err := json.Unmarshal(data, &msg); err != nil { 42 | return err 43 | } 44 | g.Data = msg.Data 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /pgtype/zeronull/timestamptz_test.go: -------------------------------------------------------------------------------- 1 | package zeronull_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/jackc/pgx/v5/pgtype/zeronull" 9 | "github.com/jackc/pgx/v5/pgxtest" 10 | ) 11 | 12 | func isExpectedEqTimestamptz(a any) func(any) bool { 13 | return func(v any) bool { 14 | at := time.Time(a.(zeronull.Timestamptz)) 15 | vt := time.Time(v.(zeronull.Timestamptz)) 16 | 17 | return at.Equal(vt) 18 | } 19 | } 20 | 21 | func TestTimestamptzTranscode(t *testing.T) { 22 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "timestamptz", []pgxtest.ValueRoundTripTest{ 23 | { 24 | (zeronull.Timestamptz)(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)), 25 | new(zeronull.Timestamptz), 26 | isExpectedEqTimestamptz((zeronull.Timestamptz)(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC))), 27 | }, 28 | { 29 | nil, 30 | new(zeronull.Timestamptz), 31 | isExpectedEqTimestamptz((zeronull.Timestamptz)(time.Time{})), 32 | }, 33 | { 34 | (zeronull.Timestamptz)(time.Time{}), 35 | new(any), 36 | isExpectedEq(nil), 37 | }, 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /pgproto3/portal_suspended.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type PortalSuspended struct{} 8 | 9 | // Backend identifies this message as sendable by the PostgreSQL backend. 10 | func (*PortalSuspended) Backend() {} 11 | 12 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 13 | // type identifier and 4 byte message length. 14 | func (dst *PortalSuspended) Decode(src []byte) error { 15 | if len(src) != 0 { 16 | return &invalidMessageLenErr{messageType: "PortalSuspended", expectedLen: 0, actualLen: len(src)} 17 | } 18 | 19 | return nil 20 | } 21 | 22 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 23 | func (src *PortalSuspended) Encode(dst []byte) ([]byte, error) { 24 | return append(dst, 's', 0, 0, 0, 4), nil 25 | } 26 | 27 | // MarshalJSON implements encoding/json.Marshaler. 28 | func (src PortalSuspended) MarshalJSON() ([]byte, error) { 29 | return json.Marshal(struct { 30 | Type string 31 | }{ 32 | Type: "PortalSuspended", 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /pgtype/box_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype" 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func TestBoxCodec(t *testing.T) { 12 | skipCockroachDB(t, "Server does not support box type") 13 | 14 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "box", []pgxtest.ValueRoundTripTest{ 15 | { 16 | pgtype.Box{ 17 | P: [2]pgtype.Vec2{{7.1, 5.2345678}, {3.14, 1.678}}, 18 | Valid: true, 19 | }, 20 | new(pgtype.Box), 21 | isExpectedEq(pgtype.Box{ 22 | P: [2]pgtype.Vec2{{7.1, 5.2345678}, {3.14, 1.678}}, 23 | Valid: true, 24 | }), 25 | }, 26 | { 27 | pgtype.Box{ 28 | P: [2]pgtype.Vec2{{7.1, 5.2345678}, {-13.14, -5.234}}, 29 | Valid: true, 30 | }, 31 | new(pgtype.Box), 32 | isExpectedEq(pgtype.Box{ 33 | P: [2]pgtype.Vec2{{7.1, 5.2345678}, {-13.14, -5.234}}, 34 | Valid: true, 35 | }), 36 | }, 37 | {pgtype.Box{}, new(pgtype.Box), isExpectedEq(pgtype.Box{})}, 38 | {nil, new(pgtype.Box), isExpectedEq(pgtype.Box{})}, 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /pgxpool/doc.go: -------------------------------------------------------------------------------- 1 | // Package pgxpool is a concurrency-safe connection pool for pgx. 2 | /* 3 | pgxpool implements a nearly identical interface to pgx connections. 4 | 5 | Creating a Pool 6 | 7 | The primary way of creating a pool is with [pgxpool.New]: 8 | 9 | pool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL")) 10 | 11 | The database connection string can be in URL or keyword/value format. PostgreSQL settings, pgx settings, and pool settings can be 12 | specified here. In addition, a config struct can be created by [ParseConfig]. 13 | 14 | config, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL")) 15 | if err != nil { 16 | // ... 17 | } 18 | config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error { 19 | // do something with every new connection 20 | } 21 | 22 | pool, err := pgxpool.NewWithConfig(context.Background(), config) 23 | 24 | A pool returns without waiting for any connections to be established. Acquire a connection immediately after creating 25 | the pool to check if a connection can successfully be established. 26 | */ 27 | package pgxpool 28 | -------------------------------------------------------------------------------- /pgtype/lseg_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype" 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func TestLsegTranscode(t *testing.T) { 12 | skipCockroachDB(t, "Server does not support type lseg") 13 | 14 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "lseg", []pgxtest.ValueRoundTripTest{ 15 | { 16 | pgtype.Lseg{ 17 | P: [2]pgtype.Vec2{{3.14, 1.678}, {7.1, 5.2345678901}}, 18 | Valid: true, 19 | }, 20 | new(pgtype.Lseg), 21 | isExpectedEq(pgtype.Lseg{ 22 | P: [2]pgtype.Vec2{{3.14, 1.678}, {7.1, 5.2345678901}}, 23 | Valid: true, 24 | }), 25 | }, 26 | { 27 | pgtype.Lseg{ 28 | P: [2]pgtype.Vec2{{7.1, 1.678}, {-13.14, -5.234}}, 29 | Valid: true, 30 | }, 31 | new(pgtype.Lseg), 32 | isExpectedEq(pgtype.Lseg{ 33 | P: [2]pgtype.Vec2{{7.1, 1.678}, {-13.14, -5.234}}, 34 | Valid: true, 35 | }), 36 | }, 37 | {pgtype.Lseg{}, new(pgtype.Lseg), isExpectedEq(pgtype.Lseg{})}, 38 | {nil, new(pgtype.Lseg), isExpectedEq(pgtype.Lseg{})}, 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /pgproto3/empty_query_response.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type EmptyQueryResponse struct{} 8 | 9 | // Backend identifies this message as sendable by the PostgreSQL backend. 10 | func (*EmptyQueryResponse) Backend() {} 11 | 12 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 13 | // type identifier and 4 byte message length. 14 | func (dst *EmptyQueryResponse) Decode(src []byte) error { 15 | if len(src) != 0 { 16 | return &invalidMessageLenErr{messageType: "EmptyQueryResponse", expectedLen: 0, actualLen: len(src)} 17 | } 18 | 19 | return nil 20 | } 21 | 22 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 23 | func (src *EmptyQueryResponse) Encode(dst []byte) ([]byte, error) { 24 | return append(dst, 'I', 0, 0, 0, 4), nil 25 | } 26 | 27 | // MarshalJSON implements encoding/json.Marshaler. 28 | func (src EmptyQueryResponse) MarshalJSON() ([]byte, error) { 29 | return json.Marshal(struct { 30 | Type string 31 | }{ 32 | Type: "EmptyQueryResponse", 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pgxpool/batch_results.go: -------------------------------------------------------------------------------- 1 | package pgxpool 2 | 3 | import ( 4 | "github.com/jackc/pgx/v5" 5 | "github.com/jackc/pgx/v5/pgconn" 6 | ) 7 | 8 | type errBatchResults struct { 9 | err error 10 | } 11 | 12 | func (br errBatchResults) Exec() (pgconn.CommandTag, error) { 13 | return pgconn.CommandTag{}, br.err 14 | } 15 | 16 | func (br errBatchResults) Query() (pgx.Rows, error) { 17 | return errRows{err: br.err}, br.err 18 | } 19 | 20 | func (br errBatchResults) QueryRow() pgx.Row { 21 | return errRow{err: br.err} 22 | } 23 | 24 | func (br errBatchResults) Close() error { 25 | return br.err 26 | } 27 | 28 | type poolBatchResults struct { 29 | br pgx.BatchResults 30 | c *Conn 31 | } 32 | 33 | func (br *poolBatchResults) Exec() (pgconn.CommandTag, error) { 34 | return br.br.Exec() 35 | } 36 | 37 | func (br *poolBatchResults) Query() (pgx.Rows, error) { 38 | return br.br.Query() 39 | } 40 | 41 | func (br *poolBatchResults) QueryRow() pgx.Row { 42 | return br.br.QueryRow() 43 | } 44 | 45 | func (br *poolBatchResults) Close() error { 46 | err := br.br.Close() 47 | if br.c != nil { 48 | br.c.Release() 49 | br.c = nil 50 | } 51 | return err 52 | } 53 | -------------------------------------------------------------------------------- /pgconn/helper_test.go: -------------------------------------------------------------------------------- 1 | package pgconn_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/jackc/pgx/v5/pgconn" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func closeConn(t testing.TB, conn *pgconn.PgConn) { 15 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 16 | defer cancel() 17 | require.NoError(t, conn.Close(ctx)) 18 | select { 19 | case <-conn.CleanupDone(): 20 | case <-time.After(30 * time.Second): 21 | t.Fatal("Connection cleanup exceeded maximum time") 22 | } 23 | } 24 | 25 | // Do a simple query to ensure the connection is still usable 26 | func ensureConnValid(t *testing.T, pgConn *pgconn.PgConn) { 27 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 28 | result := pgConn.ExecParams(ctx, "select generate_series(1,$1)", [][]byte{[]byte("3")}, nil, nil, nil).Read() 29 | cancel() 30 | 31 | require.Nil(t, result.Err) 32 | assert.Equal(t, 3, len(result.Rows)) 33 | assert.Equal(t, "1", string(result.Rows[0][0])) 34 | assert.Equal(t, "2", string(result.Rows[1][0])) 35 | assert.Equal(t, "3", string(result.Rows[2][0])) 36 | } 37 | -------------------------------------------------------------------------------- /pgproto3/copy_done.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type CopyDone struct{} 8 | 9 | // Backend identifies this message as sendable by the PostgreSQL backend. 10 | func (*CopyDone) Backend() {} 11 | 12 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 13 | func (*CopyDone) Frontend() {} 14 | 15 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 16 | // type identifier and 4 byte message length. 17 | func (dst *CopyDone) Decode(src []byte) error { 18 | if len(src) != 0 { 19 | return &invalidMessageLenErr{messageType: "CopyDone", expectedLen: 0, actualLen: len(src)} 20 | } 21 | 22 | return nil 23 | } 24 | 25 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 26 | func (src *CopyDone) Encode(dst []byte) ([]byte, error) { 27 | return append(dst, 'c', 0, 0, 0, 4), nil 28 | } 29 | 30 | // MarshalJSON implements encoding/json.Marshaler. 31 | func (src CopyDone) MarshalJSON() ([]byte, error) { 32 | return json.Marshal(struct { 33 | Type string 34 | }{ 35 | Type: "CopyDone", 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /pgtype/tid_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype" 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func TestTIDCodec(t *testing.T) { 12 | skipCockroachDB(t, "Server does not support type tid") 13 | 14 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "tid", []pgxtest.ValueRoundTripTest{ 15 | { 16 | pgtype.TID{BlockNumber: 42, OffsetNumber: 43, Valid: true}, 17 | new(pgtype.TID), 18 | isExpectedEq(pgtype.TID{BlockNumber: 42, OffsetNumber: 43, Valid: true}), 19 | }, 20 | { 21 | pgtype.TID{BlockNumber: 4294967295, OffsetNumber: 65535, Valid: true}, 22 | new(pgtype.TID), 23 | isExpectedEq(pgtype.TID{BlockNumber: 4294967295, OffsetNumber: 65535, Valid: true}), 24 | }, 25 | { 26 | pgtype.TID{BlockNumber: 42, OffsetNumber: 43, Valid: true}, 27 | new(string), 28 | isExpectedEq("(42,43)"), 29 | }, 30 | { 31 | pgtype.TID{BlockNumber: 4294967295, OffsetNumber: 65535, Valid: true}, 32 | new(string), 33 | isExpectedEq("(4294967295,65535)"), 34 | }, 35 | {pgtype.TID{}, new(pgtype.TID), isExpectedEq(pgtype.TID{})}, 36 | {nil, new(pgtype.TID), isExpectedEq(pgtype.TID{})}, 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /pgproto3/ssl_request.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/jackc/pgx/v5/internal/pgio" 9 | ) 10 | 11 | const sslRequestNumber = 80877103 12 | 13 | type SSLRequest struct{} 14 | 15 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 16 | func (*SSLRequest) Frontend() {} 17 | 18 | func (dst *SSLRequest) Decode(src []byte) error { 19 | if len(src) < 4 { 20 | return errors.New("ssl request too short") 21 | } 22 | 23 | requestCode := binary.BigEndian.Uint32(src) 24 | 25 | if requestCode != sslRequestNumber { 26 | return errors.New("bad ssl request code") 27 | } 28 | 29 | return nil 30 | } 31 | 32 | // Encode encodes src into dst. dst will include the 4 byte message length. 33 | func (src *SSLRequest) Encode(dst []byte) ([]byte, error) { 34 | dst = pgio.AppendInt32(dst, 8) 35 | dst = pgio.AppendInt32(dst, sslRequestNumber) 36 | return dst, nil 37 | } 38 | 39 | // MarshalJSON implements encoding/json.Marshaler. 40 | func (src SSLRequest) MarshalJSON() ([]byte, error) { 41 | return json.Marshal(struct { 42 | Type string 43 | ProtocolVersion uint32 44 | Parameters map[string]string 45 | }{ 46 | Type: "SSLRequest", 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /pgproto3/query.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | ) 7 | 8 | type Query struct { 9 | String string 10 | } 11 | 12 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 13 | func (*Query) Frontend() {} 14 | 15 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 16 | // type identifier and 4 byte message length. 17 | func (dst *Query) Decode(src []byte) error { 18 | i := bytes.IndexByte(src, 0) 19 | if i != len(src)-1 { 20 | return &invalidMessageFormatErr{messageType: "Query"} 21 | } 22 | 23 | dst.String = string(src[:i]) 24 | 25 | return nil 26 | } 27 | 28 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 29 | func (src *Query) Encode(dst []byte) ([]byte, error) { 30 | dst, sp := beginMessage(dst, 'Q') 31 | dst = append(dst, src.String...) 32 | dst = append(dst, 0) 33 | return finishMessage(dst, sp) 34 | } 35 | 36 | // MarshalJSON implements encoding/json.Marshaler. 37 | func (src Query) MarshalJSON() ([]byte, error) { 38 | return json.Marshal(struct { 39 | Type string 40 | String string 41 | }{ 42 | Type: "Query", 43 | String: src.String, 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /pgproto3/gss_enc_request.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/jackc/pgx/v5/internal/pgio" 9 | ) 10 | 11 | const gssEncReqNumber = 80877104 12 | 13 | type GSSEncRequest struct{} 14 | 15 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 16 | func (*GSSEncRequest) Frontend() {} 17 | 18 | func (dst *GSSEncRequest) Decode(src []byte) error { 19 | if len(src) < 4 { 20 | return errors.New("gss encoding request too short") 21 | } 22 | 23 | requestCode := binary.BigEndian.Uint32(src) 24 | 25 | if requestCode != gssEncReqNumber { 26 | return errors.New("bad gss encoding request code") 27 | } 28 | 29 | return nil 30 | } 31 | 32 | // Encode encodes src into dst. dst will include the 4 byte message length. 33 | func (src *GSSEncRequest) Encode(dst []byte) ([]byte, error) { 34 | dst = pgio.AppendInt32(dst, 8) 35 | dst = pgio.AppendInt32(dst, gssEncReqNumber) 36 | return dst, nil 37 | } 38 | 39 | // MarshalJSON implements encoding/json.Marshaler. 40 | func (src GSSEncRequest) MarshalJSON() ([]byte, error) { 41 | return json.Marshal(struct { 42 | Type string 43 | ProtocolVersion uint32 44 | Parameters map[string]string 45 | }{ 46 | Type: "GSSEncRequest", 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /pgproto3/copy_fail.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | ) 7 | 8 | type CopyFail struct { 9 | Message string 10 | } 11 | 12 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 13 | func (*CopyFail) Frontend() {} 14 | 15 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 16 | // type identifier and 4 byte message length. 17 | func (dst *CopyFail) Decode(src []byte) error { 18 | idx := bytes.IndexByte(src, 0) 19 | if idx != len(src)-1 { 20 | return &invalidMessageFormatErr{messageType: "CopyFail"} 21 | } 22 | 23 | dst.Message = string(src[:idx]) 24 | 25 | return nil 26 | } 27 | 28 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 29 | func (src *CopyFail) Encode(dst []byte) ([]byte, error) { 30 | dst, sp := beginMessage(dst, 'f') 31 | dst = append(dst, src.Message...) 32 | dst = append(dst, 0) 33 | return finishMessage(dst, sp) 34 | } 35 | 36 | // MarshalJSON implements encoding/json.Marshaler. 37 | func (src CopyFail) MarshalJSON() ([]byte, error) { 38 | return json.Marshal(struct { 39 | Type string 40 | Message string 41 | }{ 42 | Type: "CopyFail", 43 | Message: src.Message, 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /pgproto3/example/pgfortune/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net" 8 | "os" 9 | "os/exec" 10 | ) 11 | 12 | var options struct { 13 | listenAddress string 14 | responseCommand string 15 | } 16 | 17 | func main() { 18 | flag.Usage = func() { 19 | fmt.Fprintf(os.Stderr, "usage: %s [options]\n", os.Args[0]) 20 | flag.PrintDefaults() 21 | } 22 | 23 | flag.StringVar(&options.listenAddress, "listen", "127.0.0.1:15432", "Listen address") 24 | flag.StringVar(&options.responseCommand, "response-command", "fortune | cowsay -f elephant", "Command to execute to generate query response") 25 | flag.Parse() 26 | 27 | ln, err := net.Listen("tcp", options.listenAddress) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | log.Println("Listening on", ln.Addr()) 32 | 33 | for { 34 | conn, err := ln.Accept() 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | log.Println("Accepted connection from", conn.RemoteAddr()) 39 | 40 | b := NewPgFortuneBackend(conn, func() ([]byte, error) { 41 | return exec.Command("sh", "-c", options.responseCommand).CombinedOutput() 42 | }) 43 | go func() { 44 | err := b.Run() 45 | if err != nil { 46 | log.Println(err) 47 | } 48 | log.Println("Closed connection from", conn.RemoteAddr()) 49 | }() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pgtype/register_default_pg_types.go: -------------------------------------------------------------------------------- 1 | //go:build !nopgxregisterdefaulttypes 2 | 3 | package pgtype 4 | 5 | func registerDefaultPgTypeVariants[T any](m *Map, name string) { 6 | arrayName := "_" + name 7 | 8 | var value T 9 | m.RegisterDefaultPgType(value, name) // T 10 | m.RegisterDefaultPgType(&value, name) // *T 11 | 12 | var sliceT []T 13 | m.RegisterDefaultPgType(sliceT, arrayName) // []T 14 | m.RegisterDefaultPgType(&sliceT, arrayName) // *[]T 15 | 16 | var slicePtrT []*T 17 | m.RegisterDefaultPgType(slicePtrT, arrayName) // []*T 18 | m.RegisterDefaultPgType(&slicePtrT, arrayName) // *[]*T 19 | 20 | var arrayOfT Array[T] 21 | m.RegisterDefaultPgType(arrayOfT, arrayName) // Array[T] 22 | m.RegisterDefaultPgType(&arrayOfT, arrayName) // *Array[T] 23 | 24 | var arrayOfPtrT Array[*T] 25 | m.RegisterDefaultPgType(arrayOfPtrT, arrayName) // Array[*T] 26 | m.RegisterDefaultPgType(&arrayOfPtrT, arrayName) // *Array[*T] 27 | 28 | var flatArrayOfT FlatArray[T] 29 | m.RegisterDefaultPgType(flatArrayOfT, arrayName) // FlatArray[T] 30 | m.RegisterDefaultPgType(&flatArrayOfT, arrayName) // *FlatArray[T] 31 | 32 | var flatArrayOfPtrT FlatArray[*T] 33 | m.RegisterDefaultPgType(flatArrayOfPtrT, arrayName) // FlatArray[*T] 34 | m.RegisterDefaultPgType(&flatArrayOfPtrT, arrayName) // *FlatArray[*T] 35 | } 36 | -------------------------------------------------------------------------------- /pgtype/zeronull/float8.go: -------------------------------------------------------------------------------- 1 | package zeronull 2 | 3 | import ( 4 | "database/sql/driver" 5 | 6 | "github.com/jackc/pgx/v5/pgtype" 7 | ) 8 | 9 | type Float8 float64 10 | 11 | // SkipUnderlyingTypePlan implements the [pgtype.SkipUnderlyingTypePlanner] interface. 12 | func (Float8) SkipUnderlyingTypePlan() {} 13 | 14 | // ScanFloat64 implements the [pgtype.Float64Scanner] interface. 15 | func (f *Float8) ScanFloat64(n pgtype.Float8) error { 16 | if !n.Valid { 17 | *f = 0 18 | return nil 19 | } 20 | 21 | *f = Float8(n.Float64) 22 | 23 | return nil 24 | } 25 | 26 | // Float64Value implements the [pgtype.Float64Valuer] interface. 27 | func (f Float8) Float64Value() (pgtype.Float8, error) { 28 | if f == 0 { 29 | return pgtype.Float8{}, nil 30 | } 31 | return pgtype.Float8{Float64: float64(f), Valid: true}, nil 32 | } 33 | 34 | // Scan implements the [database/sql.Scanner] interface. 35 | func (f *Float8) Scan(src any) error { 36 | if src == nil { 37 | *f = 0 38 | return nil 39 | } 40 | 41 | var nullable pgtype.Float8 42 | err := nullable.Scan(src) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | *f = Float8(nullable.Float64) 48 | 49 | return nil 50 | } 51 | 52 | // Value implements the [database/sql/driver.Valuer] interface. 53 | func (f Float8) Value() (driver.Value, error) { 54 | if f == 0 { 55 | return nil, nil 56 | } 57 | return float64(f), nil 58 | } 59 | -------------------------------------------------------------------------------- /pgproto3/authentication_gss.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/jackc/pgx/v5/internal/pgio" 9 | ) 10 | 11 | type AuthenticationGSS struct{} 12 | 13 | func (a *AuthenticationGSS) Backend() {} 14 | 15 | func (a *AuthenticationGSS) AuthenticationResponse() {} 16 | 17 | func (a *AuthenticationGSS) Decode(src []byte) error { 18 | if len(src) < 4 { 19 | return errors.New("authentication message too short") 20 | } 21 | 22 | authType := binary.BigEndian.Uint32(src) 23 | 24 | if authType != AuthTypeGSS { 25 | return errors.New("bad auth type") 26 | } 27 | return nil 28 | } 29 | 30 | func (a *AuthenticationGSS) Encode(dst []byte) ([]byte, error) { 31 | dst, sp := beginMessage(dst, 'R') 32 | dst = pgio.AppendUint32(dst, AuthTypeGSS) 33 | return finishMessage(dst, sp) 34 | } 35 | 36 | func (a *AuthenticationGSS) MarshalJSON() ([]byte, error) { 37 | return json.Marshal(struct { 38 | Type string 39 | Data []byte 40 | }{ 41 | Type: "AuthenticationGSS", 42 | }) 43 | } 44 | 45 | func (a *AuthenticationGSS) UnmarshalJSON(data []byte) error { 46 | // Ignore null, like in the main JSON package. 47 | if string(data) == "null" { 48 | return nil 49 | } 50 | 51 | var msg struct { 52 | Type string 53 | } 54 | if err := json.Unmarshal(data, &msg); err != nil { 55 | return err 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /derived_types_test.go: -------------------------------------------------------------------------------- 1 | package pgx_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestCompositeCodecTranscodeWithLoadTypes(t *testing.T) { 12 | skipCockroachDB(t, "Server does not support composite types (see https://github.com/cockroachdb/cockroach/issues/27792)") 13 | 14 | defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 15 | _, err := conn.Exec(ctx, ` 16 | drop type if exists dtype_test; 17 | drop domain if exists anotheruint64; 18 | 19 | create domain anotheruint64 as numeric(20,0); 20 | create type dtype_test as ( 21 | a text, 22 | b int4, 23 | c anotheruint64, 24 | d anotheruint64[] 25 | );`) 26 | require.NoError(t, err) 27 | defer conn.Exec(ctx, "drop type dtype_test") 28 | defer conn.Exec(ctx, "drop domain anotheruint64") 29 | 30 | types, err := conn.LoadTypes(ctx, []string{"dtype_test"}) 31 | require.NoError(t, err) 32 | require.Len(t, types, 6) 33 | require.Equal(t, types[0].Name, "public.anotheruint64") 34 | require.Equal(t, types[1].Name, "anotheruint64") 35 | require.Equal(t, types[2].Name, "public._anotheruint64") 36 | require.Equal(t, types[3].Name, "_anotheruint64") 37 | require.Equal(t, types[4].Name, "public.dtype_test") 38 | require.Equal(t, types[5].Name, "dtype_test") 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /internal/sanitize/sanitize_fuzz_test.go: -------------------------------------------------------------------------------- 1 | package sanitize_test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/internal/sanitize" 8 | ) 9 | 10 | func FuzzQuoteString(f *testing.F) { 11 | const prefix = "prefix" 12 | f.Add("new\nline") 13 | f.Add("sample text") 14 | f.Add("sample q'u'o't'e's") 15 | f.Add("select 'quoted $42', $1") 16 | 17 | f.Fuzz(func(t *testing.T, input string) { 18 | got := string(sanitize.QuoteString([]byte(prefix), input)) 19 | want := oldQuoteString(input) 20 | 21 | quoted, ok := strings.CutPrefix(got, prefix) 22 | if !ok { 23 | t.Fatalf("result has no prefix") 24 | } 25 | 26 | if want != quoted { 27 | t.Errorf("got %q", got) 28 | t.Fatalf("want %q", want) 29 | } 30 | }) 31 | } 32 | 33 | func FuzzQuoteBytes(f *testing.F) { 34 | const prefix = "prefix" 35 | f.Add([]byte(nil)) 36 | f.Add([]byte("\n")) 37 | f.Add([]byte("sample text")) 38 | f.Add([]byte("sample q'u'o't'e's")) 39 | f.Add([]byte("select 'quoted $42', $1")) 40 | 41 | f.Fuzz(func(t *testing.T, input []byte) { 42 | got := string(sanitize.QuoteBytes([]byte(prefix), input)) 43 | want := oldQuoteBytes(input) 44 | 45 | quoted, ok := strings.CutPrefix(got, prefix) 46 | if !ok { 47 | t.Fatalf("result has no prefix") 48 | } 49 | 50 | if want != quoted { 51 | t.Errorf("got %q", got) 52 | t.Fatalf("want %q", want) 53 | } 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /pgproto3/fuzz_test.go: -------------------------------------------------------------------------------- 1 | package pgproto3_test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/internal/pgio" 8 | "github.com/jackc/pgx/v5/pgproto3" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func FuzzFrontend(f *testing.F) { 13 | testcases := []struct { 14 | msgType byte 15 | msgLen uint32 16 | msgBody []byte 17 | }{ 18 | { 19 | msgType: 'Z', 20 | msgLen: 2, 21 | msgBody: []byte{'I'}, 22 | }, 23 | { 24 | msgType: 'Z', 25 | msgLen: 5, 26 | msgBody: []byte{'I'}, 27 | }, 28 | } 29 | for _, tc := range testcases { 30 | f.Add(tc.msgType, tc.msgLen, tc.msgBody) 31 | } 32 | f.Fuzz(func(t *testing.T, msgType byte, msgLen uint32, msgBody []byte) { 33 | // Prune any msgLen > len(msgBody) because they would hang the test waiting for more input. 34 | if int(msgLen) > len(msgBody)+4 { 35 | return 36 | } 37 | 38 | // Prune any messages that are too long. 39 | if msgLen > 128 || len(msgBody) > 128 { 40 | return 41 | } 42 | 43 | r := &bytes.Buffer{} 44 | w := &bytes.Buffer{} 45 | fe := pgproto3.NewFrontend(r, w) 46 | 47 | var encodedMsg []byte 48 | encodedMsg = append(encodedMsg, msgType) 49 | encodedMsg = pgio.AppendUint32(encodedMsg, msgLen) 50 | encodedMsg = append(encodedMsg, msgBody...) 51 | _, err := r.Write(encodedMsg) 52 | require.NoError(t, err) 53 | 54 | // Not checking anything other than no panic. 55 | fe.Receive() 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /pgproto3/password_message.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | ) 7 | 8 | type PasswordMessage struct { 9 | Password string 10 | } 11 | 12 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 13 | func (*PasswordMessage) Frontend() {} 14 | 15 | // InitialResponse identifies this message as an authentication response. 16 | func (*PasswordMessage) InitialResponse() {} 17 | 18 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 19 | // type identifier and 4 byte message length. 20 | func (dst *PasswordMessage) Decode(src []byte) error { 21 | buf := bytes.NewBuffer(src) 22 | 23 | b, err := buf.ReadBytes(0) 24 | if err != nil { 25 | return err 26 | } 27 | dst.Password = string(b[:len(b)-1]) 28 | 29 | return nil 30 | } 31 | 32 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 33 | func (src *PasswordMessage) Encode(dst []byte) ([]byte, error) { 34 | dst, sp := beginMessage(dst, 'p') 35 | dst = append(dst, src.Password...) 36 | dst = append(dst, 0) 37 | return finishMessage(dst, sp) 38 | } 39 | 40 | // MarshalJSON implements encoding/json.Marshaler. 41 | func (src PasswordMessage) MarshalJSON() ([]byte, error) { 42 | return json.Marshal(struct { 43 | Type string 44 | Password string 45 | }{ 46 | Type: "PasswordMessage", 47 | Password: src.Password, 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | If possible, please provide runnable example such as: 17 | 18 | ```go 19 | package main 20 | 21 | import ( 22 | "context" 23 | "log" 24 | "os" 25 | 26 | "github.com/jackc/pgx/v5" 27 | ) 28 | 29 | func main() { 30 | conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL")) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | defer conn.Close(context.Background()) 35 | 36 | // Your code here... 37 | } 38 | ``` 39 | 40 | Please run your example with the race detector enabled. For example, `go run -race main.go` or `go test -race`. 41 | 42 | **Expected behavior** 43 | A clear and concise description of what you expected to happen. 44 | 45 | **Actual behavior** 46 | A clear and concise description of what actually happened. 47 | 48 | **Version** 49 | - Go: `$ go version` -> [e.g. go version go1.18.3 darwin/amd64] 50 | - PostgreSQL: `$ psql --no-psqlrc --tuples-only -c 'select version()'` -> [e.g. PostgreSQL 14.4 on x86_64-apple-darwin21.5.0, compiled by Apple clang version 13.1.6 (clang-1316.0.21.2.5), 64-bit] 51 | - pgx: `$ grep 'github.com/jackc/pgx/v[0-9]' go.mod` -> [e.g. v4.16.1] 52 | 53 | **Additional context** 54 | Add any other context about the problem here. 55 | -------------------------------------------------------------------------------- /pgtype/zeronull/uuid.go: -------------------------------------------------------------------------------- 1 | package zeronull 2 | 3 | import ( 4 | "database/sql/driver" 5 | 6 | "github.com/jackc/pgx/v5/pgtype" 7 | ) 8 | 9 | type UUID [16]byte 10 | 11 | // SkipUnderlyingTypePlan implements the [pgtype.SkipUnderlyingTypePlanner] interface. 12 | func (UUID) SkipUnderlyingTypePlan() {} 13 | 14 | // ScanUUID implements the [pgtype.UUIDScanner] interface. 15 | func (u *UUID) ScanUUID(v pgtype.UUID) error { 16 | if !v.Valid { 17 | *u = UUID{} 18 | return nil 19 | } 20 | 21 | *u = UUID(v.Bytes) 22 | 23 | return nil 24 | } 25 | 26 | // UUIDValue implements the [pgtype.UUIDValuer] interface. 27 | func (u UUID) UUIDValue() (pgtype.UUID, error) { 28 | if u == (UUID{}) { 29 | return pgtype.UUID{}, nil 30 | } 31 | return pgtype.UUID{Bytes: u, Valid: true}, nil 32 | } 33 | 34 | // Scan implements the [database/sql.Scanner] interface. 35 | func (u *UUID) Scan(src any) error { 36 | if src == nil { 37 | *u = UUID{} 38 | return nil 39 | } 40 | 41 | var nullable pgtype.UUID 42 | err := nullable.Scan(src) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | *u = UUID(nullable.Bytes) 48 | 49 | return nil 50 | } 51 | 52 | // Value implements the [database/sql/driver.Valuer] interface. 53 | func (u UUID) Value() (driver.Value, error) { 54 | if u == (UUID{}) { 55 | return nil, nil 56 | } 57 | 58 | buf, err := pgtype.UUIDCodec{}.PlanEncode(nil, pgtype.UUIDOID, pgtype.TextFormatCode, u).Encode(u, nil) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | return string(buf), nil 64 | } 65 | -------------------------------------------------------------------------------- /pgproto3/trace_test.go: -------------------------------------------------------------------------------- 1 | package pgproto3_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "os" 7 | "testing" 8 | "time" 9 | 10 | "github.com/jackc/pgx/v5/pgconn" 11 | "github.com/jackc/pgx/v5/pgproto3" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestTrace(t *testing.T) { 16 | t.Parallel() 17 | 18 | ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 19 | defer cancel() 20 | 21 | conn, err := pgconn.Connect(ctx, os.Getenv("PGX_TEST_DATABASE")) 22 | require.NoError(t, err) 23 | defer conn.Close(ctx) 24 | 25 | if conn.ParameterStatus("crdb_version") != "" { 26 | t.Skip("Skipping message trace on CockroachDB as it varies slightly from PostgreSQL") 27 | } 28 | 29 | traceOutput := &bytes.Buffer{} 30 | conn.Frontend().Trace(traceOutput, pgproto3.TracerOptions{ 31 | SuppressTimestamps: true, 32 | RegressMode: true, 33 | }) 34 | 35 | result := conn.ExecParams(ctx, "select n from generate_series(1,5) n", nil, nil, nil, nil).Read() 36 | require.NoError(t, result.Err) 37 | 38 | expected := `F Parse 45 "" "select n from generate_series(1,5) n" 0 39 | F Bind 13 "" "" 0 0 0 40 | F Describe 7 P "" 41 | F Execute 10 "" 0 42 | F Sync 5 43 | B ParseComplete 5 44 | B BindComplete 5 45 | B RowDescription 27 1 "n" 0 0 23 4 -1 0 46 | B DataRow 12 1 1 '1' 47 | B DataRow 12 1 1 '2' 48 | B DataRow 12 1 1 '3' 49 | B DataRow 12 1 1 '4' 50 | B DataRow 12 1 1 '5' 51 | B CommandComplete 14 "SELECT 5" 52 | B ReadyForQuery 6 I 53 | ` 54 | 55 | require.Equal(t, expected, traceOutput.String()) 56 | } 57 | -------------------------------------------------------------------------------- /pgproto3/cancel_request.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/jackc/pgx/v5/internal/pgio" 9 | ) 10 | 11 | const cancelRequestCode = 80877102 12 | 13 | type CancelRequest struct { 14 | ProcessID uint32 15 | SecretKey uint32 16 | } 17 | 18 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 19 | func (*CancelRequest) Frontend() {} 20 | 21 | func (dst *CancelRequest) Decode(src []byte) error { 22 | if len(src) != 12 { 23 | return errors.New("bad cancel request size") 24 | } 25 | 26 | requestCode := binary.BigEndian.Uint32(src) 27 | 28 | if requestCode != cancelRequestCode { 29 | return errors.New("bad cancel request code") 30 | } 31 | 32 | dst.ProcessID = binary.BigEndian.Uint32(src[4:]) 33 | dst.SecretKey = binary.BigEndian.Uint32(src[8:]) 34 | 35 | return nil 36 | } 37 | 38 | // Encode encodes src into dst. dst will include the 4 byte message length. 39 | func (src *CancelRequest) Encode(dst []byte) ([]byte, error) { 40 | dst = pgio.AppendInt32(dst, 16) 41 | dst = pgio.AppendInt32(dst, cancelRequestCode) 42 | dst = pgio.AppendUint32(dst, src.ProcessID) 43 | dst = pgio.AppendUint32(dst, src.SecretKey) 44 | return dst, nil 45 | } 46 | 47 | // MarshalJSON implements encoding/json.Marshaler. 48 | func (src CancelRequest) MarshalJSON() ([]byte, error) { 49 | return json.Marshal(struct { 50 | Type string 51 | ProcessID uint32 52 | SecretKey uint32 53 | }{ 54 | Type: "CancelRequest", 55 | ProcessID: src.ProcessID, 56 | SecretKey: src.SecretKey, 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /pgproto3/sasl_response.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | ) 7 | 8 | type SASLResponse struct { 9 | Data []byte 10 | } 11 | 12 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 13 | func (*SASLResponse) Frontend() {} 14 | 15 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 16 | // type identifier and 4 byte message length. 17 | func (dst *SASLResponse) Decode(src []byte) error { 18 | *dst = SASLResponse{Data: src} 19 | return nil 20 | } 21 | 22 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 23 | func (src *SASLResponse) Encode(dst []byte) ([]byte, error) { 24 | dst, sp := beginMessage(dst, 'p') 25 | dst = append(dst, src.Data...) 26 | return finishMessage(dst, sp) 27 | } 28 | 29 | // MarshalJSON implements encoding/json.Marshaler. 30 | func (src SASLResponse) MarshalJSON() ([]byte, error) { 31 | return json.Marshal(struct { 32 | Type string 33 | Data string 34 | }{ 35 | Type: "SASLResponse", 36 | Data: string(src.Data), 37 | }) 38 | } 39 | 40 | // UnmarshalJSON implements encoding/json.Unmarshaler. 41 | func (dst *SASLResponse) UnmarshalJSON(data []byte) error { 42 | var msg struct { 43 | Data string 44 | } 45 | if err := json.Unmarshal(data, &msg); err != nil { 46 | return err 47 | } 48 | if msg.Data != "" { 49 | decoded, err := hex.DecodeString(msg.Data) 50 | if err != nil { 51 | return err 52 | } 53 | dst.Data = decoded 54 | } 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /internal/sanitize/sanitize_bench_test.go: -------------------------------------------------------------------------------- 1 | // sanitize_benchmark_test.go 2 | package sanitize_test 3 | 4 | import ( 5 | "testing" 6 | "time" 7 | 8 | "github.com/jackc/pgx/v5/internal/sanitize" 9 | ) 10 | 11 | var benchmarkSanitizeResult string 12 | 13 | const benchmarkQuery = "" + 14 | `SELECT * 15 | FROM "water_containers" 16 | WHERE NOT "id" = $1 -- int64 17 | AND "tags" NOT IN $2 -- nil 18 | AND "volume" > $3 -- float64 19 | AND "transportable" = $4 -- bool 20 | AND position($5 IN "sign") -- bytes 21 | AND "label" LIKE $6 -- string 22 | AND "created_at" > $7; -- time.Time` 23 | 24 | var benchmarkArgs = []any{ 25 | int64(12345), 26 | nil, 27 | float64(500), 28 | true, 29 | []byte("8BADF00D"), 30 | "kombucha's han'dy awokowa", 31 | time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), 32 | } 33 | 34 | func BenchmarkSanitize(b *testing.B) { 35 | query, err := sanitize.NewQuery(benchmarkQuery) 36 | if err != nil { 37 | b.Fatalf("failed to create query: %v", err) 38 | } 39 | 40 | b.ReportAllocs() 41 | 42 | for b.Loop() { 43 | benchmarkSanitizeResult, err = query.Sanitize(benchmarkArgs...) 44 | if err != nil { 45 | b.Fatalf("failed to sanitize query: %v", err) 46 | } 47 | } 48 | } 49 | 50 | var benchmarkNewSQLResult string 51 | 52 | func BenchmarkSanitizeSQL(b *testing.B) { 53 | b.ReportAllocs() 54 | var err error 55 | for b.Loop() { 56 | benchmarkNewSQLResult, err = sanitize.SanitizeSQL(benchmarkQuery, benchmarkArgs...) 57 | if err != nil { 58 | b.Fatalf("failed to sanitize SQL: %v", err) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pgproto3/backend_key_data.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | 7 | "github.com/jackc/pgx/v5/internal/pgio" 8 | ) 9 | 10 | type BackendKeyData struct { 11 | ProcessID uint32 12 | SecretKey uint32 13 | } 14 | 15 | // Backend identifies this message as sendable by the PostgreSQL backend. 16 | func (*BackendKeyData) Backend() {} 17 | 18 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 19 | // type identifier and 4 byte message length. 20 | func (dst *BackendKeyData) Decode(src []byte) error { 21 | if len(src) != 8 { 22 | return &invalidMessageLenErr{messageType: "BackendKeyData", expectedLen: 8, actualLen: len(src)} 23 | } 24 | 25 | dst.ProcessID = binary.BigEndian.Uint32(src[:4]) 26 | dst.SecretKey = binary.BigEndian.Uint32(src[4:]) 27 | 28 | return nil 29 | } 30 | 31 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 32 | func (src *BackendKeyData) Encode(dst []byte) ([]byte, error) { 33 | dst, sp := beginMessage(dst, 'K') 34 | dst = pgio.AppendUint32(dst, src.ProcessID) 35 | dst = pgio.AppendUint32(dst, src.SecretKey) 36 | return finishMessage(dst, sp) 37 | } 38 | 39 | // MarshalJSON implements encoding/json.Marshaler. 40 | func (src BackendKeyData) MarshalJSON() ([]byte, error) { 41 | return json.Marshal(struct { 42 | Type string 43 | ProcessID uint32 44 | SecretKey uint32 45 | }{ 46 | Type: "BackendKeyData", 47 | ProcessID: src.ProcessID, 48 | SecretKey: src.SecretKey, 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /pgproto3/parameter_status.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | ) 7 | 8 | type ParameterStatus struct { 9 | Name string 10 | Value string 11 | } 12 | 13 | // Backend identifies this message as sendable by the PostgreSQL backend. 14 | func (*ParameterStatus) Backend() {} 15 | 16 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 17 | // type identifier and 4 byte message length. 18 | func (dst *ParameterStatus) Decode(src []byte) error { 19 | buf := bytes.NewBuffer(src) 20 | 21 | b, err := buf.ReadBytes(0) 22 | if err != nil { 23 | return err 24 | } 25 | name := string(b[:len(b)-1]) 26 | 27 | b, err = buf.ReadBytes(0) 28 | if err != nil { 29 | return err 30 | } 31 | value := string(b[:len(b)-1]) 32 | 33 | *dst = ParameterStatus{Name: name, Value: value} 34 | return nil 35 | } 36 | 37 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 38 | func (src *ParameterStatus) Encode(dst []byte) ([]byte, error) { 39 | dst, sp := beginMessage(dst, 'S') 40 | dst = append(dst, src.Name...) 41 | dst = append(dst, 0) 42 | dst = append(dst, src.Value...) 43 | dst = append(dst, 0) 44 | return finishMessage(dst, sp) 45 | } 46 | 47 | // MarshalJSON implements encoding/json.Marshaler. 48 | func (ps ParameterStatus) MarshalJSON() ([]byte, error) { 49 | return json.Marshal(struct { 50 | Type string 51 | Name string 52 | Value string 53 | }{ 54 | Type: "ParameterStatus", 55 | Name: ps.Name, 56 | Value: ps.Value, 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /pgproto3/authentication_ok.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/jackc/pgx/v5/internal/pgio" 9 | ) 10 | 11 | // AuthenticationOk is a message sent from the backend indicating that authentication was successful. 12 | type AuthenticationOk struct{} 13 | 14 | // Backend identifies this message as sendable by the PostgreSQL backend. 15 | func (*AuthenticationOk) Backend() {} 16 | 17 | // Backend identifies this message as an authentication response. 18 | func (*AuthenticationOk) AuthenticationResponse() {} 19 | 20 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 21 | // type identifier and 4 byte message length. 22 | func (dst *AuthenticationOk) Decode(src []byte) error { 23 | if len(src) != 4 { 24 | return errors.New("bad authentication message size") 25 | } 26 | 27 | authType := binary.BigEndian.Uint32(src) 28 | 29 | if authType != AuthTypeOk { 30 | return errors.New("bad auth type") 31 | } 32 | 33 | return nil 34 | } 35 | 36 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 37 | func (src *AuthenticationOk) Encode(dst []byte) ([]byte, error) { 38 | dst, sp := beginMessage(dst, 'R') 39 | dst = pgio.AppendUint32(dst, AuthTypeOk) 40 | return finishMessage(dst, sp) 41 | } 42 | 43 | // MarshalJSON implements encoding/json.Marshaler. 44 | func (src AuthenticationOk) MarshalJSON() ([]byte, error) { 45 | return json.Marshal(struct { 46 | Type string 47 | }{ 48 | Type: "AuthenticationOK", 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /values.go: -------------------------------------------------------------------------------- 1 | package pgx 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/jackc/pgx/v5/internal/pgio" 7 | "github.com/jackc/pgx/v5/pgtype" 8 | ) 9 | 10 | // PostgreSQL format codes 11 | const ( 12 | TextFormatCode = 0 13 | BinaryFormatCode = 1 14 | ) 15 | 16 | func convertSimpleArgument(m *pgtype.Map, arg any) (any, error) { 17 | buf, err := m.Encode(0, TextFormatCode, arg, []byte{}) 18 | if err != nil { 19 | return nil, err 20 | } 21 | if buf == nil { 22 | return nil, nil 23 | } 24 | return string(buf), nil 25 | } 26 | 27 | func encodeCopyValue(m *pgtype.Map, buf []byte, oid uint32, arg any) ([]byte, error) { 28 | sp := len(buf) 29 | buf = pgio.AppendInt32(buf, -1) 30 | argBuf, err := m.Encode(oid, BinaryFormatCode, arg, buf) 31 | if err != nil { 32 | if argBuf2, err2 := tryScanStringCopyValueThenEncode(m, buf, oid, arg); err2 == nil { 33 | argBuf = argBuf2 34 | } else { 35 | return nil, err 36 | } 37 | } 38 | 39 | if argBuf != nil { 40 | buf = argBuf 41 | pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4)) 42 | } 43 | return buf, nil 44 | } 45 | 46 | func tryScanStringCopyValueThenEncode(m *pgtype.Map, buf []byte, oid uint32, arg any) ([]byte, error) { 47 | s, ok := arg.(string) 48 | if !ok { 49 | textBuf, err := m.Encode(oid, TextFormatCode, arg, nil) 50 | if err != nil { 51 | return nil, errors.New("not a string and cannot be encoded as text") 52 | } 53 | s = string(textBuf) 54 | } 55 | 56 | var v any 57 | err := m.Scan(oid, TextFormatCode, []byte(s), &v) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | return m.Encode(oid, BinaryFormatCode, v, buf) 63 | } 64 | -------------------------------------------------------------------------------- /pgproto3/authentication_gss_continue.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/jackc/pgx/v5/internal/pgio" 9 | ) 10 | 11 | type AuthenticationGSSContinue struct { 12 | Data []byte 13 | } 14 | 15 | func (a *AuthenticationGSSContinue) Backend() {} 16 | 17 | func (a *AuthenticationGSSContinue) AuthenticationResponse() {} 18 | 19 | func (a *AuthenticationGSSContinue) Decode(src []byte) error { 20 | if len(src) < 4 { 21 | return errors.New("authentication message too short") 22 | } 23 | 24 | authType := binary.BigEndian.Uint32(src) 25 | 26 | if authType != AuthTypeGSSCont { 27 | return errors.New("bad auth type") 28 | } 29 | 30 | a.Data = src[4:] 31 | return nil 32 | } 33 | 34 | func (a *AuthenticationGSSContinue) Encode(dst []byte) ([]byte, error) { 35 | dst, sp := beginMessage(dst, 'R') 36 | dst = pgio.AppendUint32(dst, AuthTypeGSSCont) 37 | dst = append(dst, a.Data...) 38 | return finishMessage(dst, sp) 39 | } 40 | 41 | func (a *AuthenticationGSSContinue) MarshalJSON() ([]byte, error) { 42 | return json.Marshal(struct { 43 | Type string 44 | Data []byte 45 | }{ 46 | Type: "AuthenticationGSSContinue", 47 | Data: a.Data, 48 | }) 49 | } 50 | 51 | func (a *AuthenticationGSSContinue) UnmarshalJSON(data []byte) error { 52 | // Ignore null, like in the main JSON package. 53 | if string(data) == "null" { 54 | return nil 55 | } 56 | 57 | var msg struct { 58 | Type string 59 | Data []byte 60 | } 61 | if err := json.Unmarshal(data, &msg); err != nil { 62 | return err 63 | } 64 | 65 | a.Data = msg.Data 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /pgproto3/execute.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | 8 | "github.com/jackc/pgx/v5/internal/pgio" 9 | ) 10 | 11 | type Execute struct { 12 | Portal string 13 | MaxRows uint32 14 | } 15 | 16 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 17 | func (*Execute) Frontend() {} 18 | 19 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 20 | // type identifier and 4 byte message length. 21 | func (dst *Execute) Decode(src []byte) error { 22 | buf := bytes.NewBuffer(src) 23 | 24 | b, err := buf.ReadBytes(0) 25 | if err != nil { 26 | return err 27 | } 28 | dst.Portal = string(b[:len(b)-1]) 29 | 30 | if buf.Len() < 4 { 31 | return &invalidMessageFormatErr{messageType: "Execute"} 32 | } 33 | dst.MaxRows = binary.BigEndian.Uint32(buf.Next(4)) 34 | 35 | return nil 36 | } 37 | 38 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 39 | func (src *Execute) Encode(dst []byte) ([]byte, error) { 40 | dst, sp := beginMessage(dst, 'E') 41 | dst = append(dst, src.Portal...) 42 | dst = append(dst, 0) 43 | dst = pgio.AppendUint32(dst, src.MaxRows) 44 | return finishMessage(dst, sp) 45 | } 46 | 47 | // MarshalJSON implements encoding/json.Marshaler. 48 | func (src Execute) MarshalJSON() ([]byte, error) { 49 | return json.Marshal(struct { 50 | Type string 51 | Portal string 52 | MaxRows uint32 53 | }{ 54 | Type: "Execute", 55 | Portal: src.Portal, 56 | MaxRows: src.MaxRows, 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /pgtype/line_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | pgx "github.com/jackc/pgx/v5" 8 | "github.com/jackc/pgx/v5/pgtype" 9 | "github.com/jackc/pgx/v5/pgxtest" 10 | ) 11 | 12 | func TestLineTranscode(t *testing.T) { 13 | ctr := defaultConnTestRunner 14 | ctr.AfterConnect = func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 15 | pgxtest.SkipCockroachDB(t, conn, "Server does not support type line") 16 | 17 | if _, ok := conn.TypeMap().TypeForName("line"); !ok { 18 | t.Skip("Skipping due to no line type") 19 | } 20 | 21 | // line may exist but not be usable on 9.3 :( 22 | var isPG93 bool 23 | err := conn.QueryRow(context.Background(), "select version() ~ '9.3'").Scan(&isPG93) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | if isPG93 { 28 | t.Skip("Skipping due to unimplemented line type in PG 9.3") 29 | } 30 | } 31 | 32 | pgxtest.RunValueRoundTripTests(context.Background(), t, ctr, nil, "line", []pgxtest.ValueRoundTripTest{ 33 | { 34 | pgtype.Line{ 35 | A: 1.23, B: 4.56, C: 7.89012345, 36 | Valid: true, 37 | }, 38 | new(pgtype.Line), 39 | isExpectedEq(pgtype.Line{ 40 | A: 1.23, B: 4.56, C: 7.89012345, 41 | Valid: true, 42 | }), 43 | }, 44 | { 45 | pgtype.Line{ 46 | A: -1.23, B: -4.56, C: -7.89, 47 | Valid: true, 48 | }, 49 | new(pgtype.Line), 50 | isExpectedEq(pgtype.Line{ 51 | A: -1.23, B: -4.56, C: -7.89, 52 | Valid: true, 53 | }), 54 | }, 55 | {pgtype.Line{}, new(pgtype.Line), isExpectedEq(pgtype.Line{})}, 56 | {nil, new(pgtype.Line), isExpectedEq(pgtype.Line{})}, 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /pgtype/polygon_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype" 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func isExpectedEqPolygon(a any) func(any) bool { 12 | return func(v any) bool { 13 | ap := a.(pgtype.Polygon) 14 | vp := v.(pgtype.Polygon) 15 | 16 | if !(ap.Valid == vp.Valid && len(ap.P) == len(vp.P)) { 17 | return false 18 | } 19 | 20 | for i := range ap.P { 21 | if ap.P[i] != vp.P[i] { 22 | return false 23 | } 24 | } 25 | 26 | return true 27 | } 28 | } 29 | 30 | func TestPolygonTranscode(t *testing.T) { 31 | skipCockroachDB(t, "Server does not support type polygon") 32 | 33 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "polygon", []pgxtest.ValueRoundTripTest{ 34 | { 35 | pgtype.Polygon{ 36 | P: []pgtype.Vec2{{3.14, 1.678901234}, {7.1, 5.234}, {5.0, 3.234}}, 37 | Valid: true, 38 | }, 39 | new(pgtype.Polygon), 40 | isExpectedEqPolygon(pgtype.Polygon{ 41 | P: []pgtype.Vec2{{3.14, 1.678901234}, {7.1, 5.234}, {5.0, 3.234}}, 42 | Valid: true, 43 | }), 44 | }, 45 | { 46 | pgtype.Polygon{ 47 | P: []pgtype.Vec2{{3.14, -1.678}, {7.1, -5.234}, {23.1, 9.34}}, 48 | Valid: true, 49 | }, 50 | new(pgtype.Polygon), 51 | isExpectedEqPolygon(pgtype.Polygon{ 52 | P: []pgtype.Vec2{{3.14, -1.678}, {7.1, -5.234}, {23.1, 9.34}}, 53 | Valid: true, 54 | }), 55 | }, 56 | {pgtype.Polygon{}, new(pgtype.Polygon), isExpectedEqPolygon(pgtype.Polygon{})}, 57 | {nil, new(pgtype.Polygon), isExpectedEqPolygon(pgtype.Polygon{})}, 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /pgproto3/copy_data.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | ) 7 | 8 | type CopyData struct { 9 | Data []byte 10 | } 11 | 12 | // Backend identifies this message as sendable by the PostgreSQL backend. 13 | func (*CopyData) Backend() {} 14 | 15 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 16 | func (*CopyData) Frontend() {} 17 | 18 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 19 | // type identifier and 4 byte message length. 20 | func (dst *CopyData) Decode(src []byte) error { 21 | dst.Data = src 22 | return nil 23 | } 24 | 25 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 26 | func (src *CopyData) Encode(dst []byte) ([]byte, error) { 27 | dst, sp := beginMessage(dst, 'd') 28 | dst = append(dst, src.Data...) 29 | return finishMessage(dst, sp) 30 | } 31 | 32 | // MarshalJSON implements encoding/json.Marshaler. 33 | func (src CopyData) MarshalJSON() ([]byte, error) { 34 | return json.Marshal(struct { 35 | Type string 36 | Data string 37 | }{ 38 | Type: "CopyData", 39 | Data: hex.EncodeToString(src.Data), 40 | }) 41 | } 42 | 43 | // UnmarshalJSON implements encoding/json.Unmarshaler. 44 | func (dst *CopyData) UnmarshalJSON(data []byte) error { 45 | // Ignore null, like in the main JSON package. 46 | if string(data) == "null" { 47 | return nil 48 | } 49 | 50 | var msg struct { 51 | Data string 52 | } 53 | if err := json.Unmarshal(data, &msg); err != nil { 54 | return err 55 | } 56 | 57 | dst.Data = []byte(msg.Data) 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /pgconn/pgconn_private_test.go: -------------------------------------------------------------------------------- 1 | package pgconn 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCommandTag(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | commandTag CommandTag 14 | rowsAffected int64 15 | isInsert bool 16 | isUpdate bool 17 | isDelete bool 18 | isSelect bool 19 | }{ 20 | {commandTag: CommandTag{s: "INSERT 0 5"}, rowsAffected: 5, isInsert: true}, 21 | {commandTag: CommandTag{s: "UPDATE 0"}, rowsAffected: 0, isUpdate: true}, 22 | {commandTag: CommandTag{s: "UPDATE 1"}, rowsAffected: 1, isUpdate: true}, 23 | {commandTag: CommandTag{s: "DELETE 0"}, rowsAffected: 0, isDelete: true}, 24 | {commandTag: CommandTag{s: "DELETE 1"}, rowsAffected: 1, isDelete: true}, 25 | {commandTag: CommandTag{s: "DELETE 1234567890"}, rowsAffected: 1234567890, isDelete: true}, 26 | {commandTag: CommandTag{s: "SELECT 1"}, rowsAffected: 1, isSelect: true}, 27 | {commandTag: CommandTag{s: "SELECT 99999999999"}, rowsAffected: 99999999999, isSelect: true}, 28 | {commandTag: CommandTag{s: "CREATE TABLE"}, rowsAffected: 0}, 29 | {commandTag: CommandTag{s: "ALTER TABLE"}, rowsAffected: 0}, 30 | {commandTag: CommandTag{s: "DROP TABLE"}, rowsAffected: 0}, 31 | } 32 | 33 | for i, tt := range tests { 34 | ct := tt.commandTag 35 | assert.Equalf(t, tt.rowsAffected, ct.RowsAffected(), "%d. %v", i, tt.commandTag) 36 | assert.Equalf(t, tt.isInsert, ct.Insert(), "%d. %v", i, tt.commandTag) 37 | assert.Equalf(t, tt.isUpdate, ct.Update(), "%d. %v", i, tt.commandTag) 38 | assert.Equalf(t, tt.isDelete, ct.Delete(), "%d. %v", i, tt.commandTag) 39 | assert.Equalf(t, tt.isSelect, ct.Select(), "%d. %v", i, tt.commandTag) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pgproto3/example/pgfortune/README.md: -------------------------------------------------------------------------------- 1 | # pgfortune 2 | 3 | pgfortune is a mock PostgreSQL server that responds to every query with a fortune. 4 | 5 | ## Installation 6 | 7 | Install `fortune` and `cowsay`. They should be available in any Unix package manager (apt, yum, brew, etc.) 8 | 9 | ``` 10 | go get -u github.com/jackc/pgproto3/example/pgfortune 11 | ``` 12 | 13 | ## Usage 14 | 15 | ``` 16 | $ pgfortune 17 | ``` 18 | 19 | By default pgfortune listens on 127.0.0.1:15432 and responds to queries with `fortune | cowsay -f elephant`. These are 20 | configurable with the `listen` and `response-command` arguments respectively. 21 | 22 | While `pgfortune` is running connect to it with `psql`. 23 | 24 | ``` 25 | $ psql -h 127.0.0.1 -p 15432 26 | Timing is on. 27 | Null display is "∅". 28 | Line style is unicode. 29 | psql (11.5, server 0.0.0) 30 | Type "help" for help. 31 | 32 | jack@127.0.0.1:15432 jack=# select foo; 33 | fortune 34 | ───────────────────────────────────────────── 35 | _________________________________________ ↵ 36 | / Ships are safe in harbor, but they were \↵ 37 | \ never meant to stay there. /↵ 38 | ----------------------------------------- ↵ 39 | \ /\ ___ /\ ↵ 40 | \ // \/ \/ \\ ↵ 41 | (( O O )) ↵ 42 | \\ / \ // ↵ 43 | \/ | | \/ ↵ 44 | | | | | ↵ 45 | | | | | ↵ 46 | | o | ↵ 47 | | | | | ↵ 48 | |m| |m| ↵ 49 | 50 | (1 row) 51 | 52 | Time: 28.161 ms 53 | ``` 54 | -------------------------------------------------------------------------------- /pgtype/zeronull/int_test.go: -------------------------------------------------------------------------------- 1 | // Code generated from pgtype/zeronull/int_test.go.erb. DO NOT EDIT. 2 | 3 | package zeronull_test 4 | 5 | import ( 6 | "context" 7 | "testing" 8 | 9 | "github.com/jackc/pgx/v5/pgtype/zeronull" 10 | "github.com/jackc/pgx/v5/pgxtest" 11 | ) 12 | 13 | func TestInt2Transcode(t *testing.T) { 14 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int2", []pgxtest.ValueRoundTripTest{ 15 | { 16 | (zeronull.Int2)(1), 17 | new(zeronull.Int2), 18 | isExpectedEq((zeronull.Int2)(1)), 19 | }, 20 | { 21 | nil, 22 | new(zeronull.Int2), 23 | isExpectedEq((zeronull.Int2)(0)), 24 | }, 25 | { 26 | (zeronull.Int2)(0), 27 | new(any), 28 | isExpectedEq(nil), 29 | }, 30 | }) 31 | } 32 | 33 | func TestInt4Transcode(t *testing.T) { 34 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int4", []pgxtest.ValueRoundTripTest{ 35 | { 36 | (zeronull.Int4)(1), 37 | new(zeronull.Int4), 38 | isExpectedEq((zeronull.Int4)(1)), 39 | }, 40 | { 41 | nil, 42 | new(zeronull.Int4), 43 | isExpectedEq((zeronull.Int4)(0)), 44 | }, 45 | { 46 | (zeronull.Int4)(0), 47 | new(any), 48 | isExpectedEq(nil), 49 | }, 50 | }) 51 | } 52 | 53 | func TestInt8Transcode(t *testing.T) { 54 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int8", []pgxtest.ValueRoundTripTest{ 55 | { 56 | (zeronull.Int8)(1), 57 | new(zeronull.Int8), 58 | isExpectedEq((zeronull.Int8)(1)), 59 | }, 60 | { 61 | nil, 62 | new(zeronull.Int8), 63 | isExpectedEq((zeronull.Int8)(0)), 64 | }, 65 | { 66 | (zeronull.Int8)(0), 67 | new(any), 68 | isExpectedEq(nil), 69 | }, 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /pgconn/benchmark_private_test.go: -------------------------------------------------------------------------------- 1 | package pgconn 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkCommandTagRowsAffected(b *testing.B) { 9 | benchmarks := []struct { 10 | commandTag string 11 | rowsAffected int64 12 | }{ 13 | {"UPDATE 1", 1}, 14 | {"UPDATE 123456789", 123456789}, 15 | {"INSERT 0 1", 1}, 16 | {"INSERT 0 123456789", 123456789}, 17 | } 18 | 19 | for _, bm := range benchmarks { 20 | ct := CommandTag{s: bm.commandTag} 21 | b.Run(bm.commandTag, func(b *testing.B) { 22 | var n int64 23 | for b.Loop() { 24 | n = ct.RowsAffected() 25 | } 26 | if n != bm.rowsAffected { 27 | b.Errorf("expected %d got %d", bm.rowsAffected, n) 28 | } 29 | }) 30 | } 31 | } 32 | 33 | func BenchmarkCommandTagTypeFromString(b *testing.B) { 34 | ct := CommandTag{s: "UPDATE 1"} 35 | 36 | var update bool 37 | for b.Loop() { 38 | update = strings.HasPrefix(ct.String(), "UPDATE") 39 | } 40 | if !update { 41 | b.Error("expected update") 42 | } 43 | } 44 | 45 | func BenchmarkCommandTagInsert(b *testing.B) { 46 | benchmarks := []struct { 47 | commandTag string 48 | is bool 49 | }{ 50 | {"INSERT 1", true}, 51 | {"INSERT 1234567890", true}, 52 | {"UPDATE 1", false}, 53 | {"UPDATE 1234567890", false}, 54 | {"DELETE 1", false}, 55 | {"DELETE 1234567890", false}, 56 | {"SELECT 1", false}, 57 | {"SELECT 1234567890", false}, 58 | {"UNKNOWN 1234567890", false}, 59 | } 60 | 61 | for _, bm := range benchmarks { 62 | ct := CommandTag{s: bm.commandTag} 63 | b.Run(bm.commandTag, func(b *testing.B) { 64 | var is bool 65 | for b.Loop() { 66 | is = ct.Insert() 67 | } 68 | if is != bm.is { 69 | b.Errorf("expected %v got %v", bm.is, is) 70 | } 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /conn_internal_test.go: -------------------------------------------------------------------------------- 1 | package pgx 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func mustParseConfig(t testing.TB, connString string) *ConnConfig { 14 | config, err := ParseConfig(connString) 15 | require.Nil(t, err) 16 | return config 17 | } 18 | 19 | func mustConnect(t testing.TB, config *ConnConfig) *Conn { 20 | conn, err := ConnectConfig(context.Background(), config) 21 | if err != nil { 22 | t.Fatalf("Unable to establish connection: %v", err) 23 | } 24 | return conn 25 | } 26 | 27 | // Ensures the connection limits the size of its cached objects. 28 | // This test examines the internals of *Conn so must be in the same package. 29 | func TestStmtCacheSizeLimit(t *testing.T) { 30 | const cacheLimit = 16 31 | 32 | connConfig := mustParseConfig(t, os.Getenv("PGX_TEST_DATABASE")) 33 | connConfig.StatementCacheCapacity = cacheLimit 34 | conn := mustConnect(t, connConfig) 35 | defer func() { 36 | err := conn.Close(context.Background()) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | }() 41 | 42 | // run a set of unique queries that should overflow the cache 43 | ctx := context.Background() 44 | for i := range cacheLimit * 2 { 45 | uniqueString := fmt.Sprintf("unique %d", i) 46 | uniqueSQL := fmt.Sprintf("select '%s'", uniqueString) 47 | var output string 48 | err := conn.QueryRow(ctx, uniqueSQL).Scan(&output) 49 | require.NoError(t, err) 50 | require.Equal(t, uniqueString, output) 51 | } 52 | // preparedStatements contains cacheLimit+1 because deallocation happens before the query 53 | assert.Len(t, conn.preparedStatements, cacheLimit+1) 54 | assert.Equal(t, cacheLimit, conn.statementCache.Len()) 55 | } 56 | -------------------------------------------------------------------------------- /internal/sanitize/benchmmark.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | current_branch=$(git rev-parse --abbrev-ref HEAD) 4 | if [ "$current_branch" == "HEAD" ]; then 5 | current_branch=$(git rev-parse HEAD) 6 | fi 7 | 8 | restore_branch() { 9 | echo "Restoring original branch/commit: $current_branch" 10 | git checkout "$current_branch" 11 | } 12 | trap restore_branch EXIT 13 | 14 | # Check if there are uncommitted changes 15 | if ! git diff --quiet || ! git diff --cached --quiet; then 16 | echo "There are uncommitted changes. Please commit or stash them before running this script." 17 | exit 1 18 | fi 19 | 20 | # Ensure that at least one commit argument is passed 21 | if [ "$#" -lt 1 ]; then 22 | echo "Usage: $0 ... " 23 | exit 1 24 | fi 25 | 26 | commits=("$@") 27 | benchmarks_dir=benchmarks 28 | 29 | if ! mkdir -p "${benchmarks_dir}"; then 30 | echo "Unable to create dir for benchmarks data" 31 | exit 1 32 | fi 33 | 34 | # Benchmark results 35 | bench_files=() 36 | 37 | # Run benchmark for each listed commit 38 | for i in "${!commits[@]}"; do 39 | commit="${commits[i]}" 40 | git checkout "$commit" || { 41 | echo "Failed to checkout $commit" 42 | exit 1 43 | } 44 | 45 | # Sanitized commmit message 46 | commit_message=$(git log -1 --pretty=format:"%s" | tr -c '[:alnum:]-_' '_') 47 | 48 | # Benchmark data will go there 49 | bench_file="${benchmarks_dir}/${i}_${commit_message}.bench" 50 | 51 | if ! go test -bench=. -count=10 >"$bench_file"; then 52 | echo "Benchmarking failed for commit $commit" 53 | exit 1 54 | fi 55 | 56 | bench_files+=("$bench_file") 57 | done 58 | 59 | # go install golang.org/x/perf/cmd/benchstat[@latest] 60 | benchstat "${bench_files[@]}" 61 | -------------------------------------------------------------------------------- /pgproto3/authentication_cleartext_password.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/jackc/pgx/v5/internal/pgio" 9 | ) 10 | 11 | // AuthenticationCleartextPassword is a message sent from the backend indicating that a clear-text password is required. 12 | type AuthenticationCleartextPassword struct{} 13 | 14 | // Backend identifies this message as sendable by the PostgreSQL backend. 15 | func (*AuthenticationCleartextPassword) Backend() {} 16 | 17 | // Backend identifies this message as an authentication response. 18 | func (*AuthenticationCleartextPassword) AuthenticationResponse() {} 19 | 20 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 21 | // type identifier and 4 byte message length. 22 | func (dst *AuthenticationCleartextPassword) Decode(src []byte) error { 23 | if len(src) != 4 { 24 | return errors.New("bad authentication message size") 25 | } 26 | 27 | authType := binary.BigEndian.Uint32(src) 28 | 29 | if authType != AuthTypeCleartextPassword { 30 | return errors.New("bad auth type") 31 | } 32 | 33 | return nil 34 | } 35 | 36 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 37 | func (src *AuthenticationCleartextPassword) Encode(dst []byte) ([]byte, error) { 38 | dst, sp := beginMessage(dst, 'R') 39 | dst = pgio.AppendUint32(dst, AuthTypeCleartextPassword) 40 | return finishMessage(dst, sp) 41 | } 42 | 43 | // MarshalJSON implements encoding/json.Marshaler. 44 | func (src AuthenticationCleartextPassword) MarshalJSON() ([]byte, error) { 45 | return json.Marshal(struct { 46 | Type string 47 | }{ 48 | Type: "AuthenticationCleartextPassword", 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /internal/iobufpool/iobufpool.go: -------------------------------------------------------------------------------- 1 | // Package iobufpool implements a global segregated-fit pool of buffers for IO. 2 | // 3 | // It uses *[]byte instead of []byte to avoid the sync.Pool allocation with Put. Unfortunately, using a pointer to avoid 4 | // an allocation is purposely not documented. https://github.com/golang/go/issues/16323 5 | package iobufpool 6 | 7 | import ( 8 | "math/bits" 9 | "sync" 10 | ) 11 | 12 | const minPoolExpOf2 = 8 13 | 14 | var pools [18]*sync.Pool 15 | 16 | func init() { 17 | for i := range pools { 18 | bufLen := 1 << (minPoolExpOf2 + i) 19 | pools[i] = &sync.Pool{ 20 | New: func() any { 21 | buf := make([]byte, bufLen) 22 | return &buf 23 | }, 24 | } 25 | } 26 | } 27 | 28 | // Get gets a []byte of len size with cap <= size*2. 29 | func Get(size int) *[]byte { 30 | i := getPoolIdx(size) 31 | if i >= len(pools) { 32 | buf := make([]byte, size) 33 | return &buf 34 | } 35 | 36 | ptrBuf := (pools[i].Get().(*[]byte)) 37 | *ptrBuf = (*ptrBuf)[:size] 38 | 39 | return ptrBuf 40 | } 41 | 42 | func getPoolIdx(size int) int { 43 | if size < 2 { 44 | return 0 45 | } 46 | idx := bits.Len(uint(size-1)) - minPoolExpOf2 47 | if idx < 0 { 48 | return 0 49 | } 50 | return idx 51 | } 52 | 53 | // Put returns buf to the pool. 54 | func Put(buf *[]byte) { 55 | i := putPoolIdx(cap(*buf)) 56 | if i < 0 { 57 | return 58 | } 59 | 60 | pools[i].Put(buf) 61 | } 62 | 63 | func putPoolIdx(size int) int { 64 | // Only exact power-of-2 sizes match pool buckets 65 | if size&(size-1) != 0 { 66 | return -1 67 | } 68 | 69 | // Calculate log2(size) using trailing zeros count 70 | exp := bits.TrailingZeros(uint(size)) 71 | idx := exp - minPoolExpOf2 72 | 73 | if idx < 0 || idx >= len(pools) { 74 | return -1 75 | } 76 | 77 | return idx 78 | } 79 | -------------------------------------------------------------------------------- /internal/stmtcache/stmtcache.go: -------------------------------------------------------------------------------- 1 | // Package stmtcache is a cache for statement descriptions. 2 | package stmtcache 3 | 4 | import ( 5 | "crypto/sha256" 6 | "encoding/hex" 7 | 8 | "github.com/jackc/pgx/v5/pgconn" 9 | ) 10 | 11 | // StatementName returns a statement name that will be stable for sql across multiple connections and program 12 | // executions. 13 | func StatementName(sql string) string { 14 | digest := sha256.Sum256([]byte(sql)) 15 | return "stmtcache_" + hex.EncodeToString(digest[0:24]) 16 | } 17 | 18 | // Cache caches statement descriptions. 19 | type Cache interface { 20 | // Get returns the statement description for sql. Returns nil if not found. 21 | Get(sql string) *pgconn.StatementDescription 22 | 23 | // Put stores sd in the cache. Put panics if sd.SQL is "". Put does nothing if sd.SQL already exists in the cache. 24 | Put(sd *pgconn.StatementDescription) 25 | 26 | // Invalidate invalidates statement description identified by sql. Does nothing if not found. 27 | Invalidate(sql string) 28 | 29 | // InvalidateAll invalidates all statement descriptions. 30 | InvalidateAll() 31 | 32 | // GetInvalidated returns a slice of all statement descriptions invalidated since the last call to RemoveInvalidated. 33 | GetInvalidated() []*pgconn.StatementDescription 34 | 35 | // RemoveInvalidated removes all invalidated statement descriptions. No other calls to Cache must be made between a 36 | // call to GetInvalidated and RemoveInvalidated or RemoveInvalidated may remove statement descriptions that were 37 | // never seen by the call to GetInvalidated. 38 | RemoveInvalidated() 39 | 40 | // Len returns the number of cached prepared statement descriptions. 41 | Len() int 42 | 43 | // Cap returns the maximum number of cached prepared statement descriptions. 44 | Cap() int 45 | } 46 | -------------------------------------------------------------------------------- /pgproto3/ready_for_query.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | ) 7 | 8 | type ReadyForQuery struct { 9 | TxStatus byte 10 | } 11 | 12 | // Backend identifies this message as sendable by the PostgreSQL backend. 13 | func (*ReadyForQuery) Backend() {} 14 | 15 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 16 | // type identifier and 4 byte message length. 17 | func (dst *ReadyForQuery) Decode(src []byte) error { 18 | if len(src) != 1 { 19 | return &invalidMessageLenErr{messageType: "ReadyForQuery", expectedLen: 1, actualLen: len(src)} 20 | } 21 | 22 | dst.TxStatus = src[0] 23 | 24 | return nil 25 | } 26 | 27 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 28 | func (src *ReadyForQuery) Encode(dst []byte) ([]byte, error) { 29 | return append(dst, 'Z', 0, 0, 0, 5, src.TxStatus), nil 30 | } 31 | 32 | // MarshalJSON implements encoding/json.Marshaler. 33 | func (src ReadyForQuery) MarshalJSON() ([]byte, error) { 34 | return json.Marshal(struct { 35 | Type string 36 | TxStatus string 37 | }{ 38 | Type: "ReadyForQuery", 39 | TxStatus: string(src.TxStatus), 40 | }) 41 | } 42 | 43 | // UnmarshalJSON implements encoding/json.Unmarshaler. 44 | func (dst *ReadyForQuery) UnmarshalJSON(data []byte) error { 45 | // Ignore null, like in the main JSON package. 46 | if string(data) == "null" { 47 | return nil 48 | } 49 | 50 | var msg struct { 51 | TxStatus string 52 | } 53 | if err := json.Unmarshal(data, &msg); err != nil { 54 | return err 55 | } 56 | if len(msg.TxStatus) != 1 { 57 | return errors.New("invalid length for ReadyForQuery.TxStatus") 58 | } 59 | dst.TxStatus = msg.TxStatus[0] 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /pgtype/example_custom_type_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/jackc/pgx/v5" 9 | "github.com/jackc/pgx/v5/pgtype" 10 | ) 11 | 12 | // Point represents a point that may be null. 13 | type Point struct { 14 | X, Y float32 // Coordinates of point 15 | Valid bool 16 | } 17 | 18 | func (p *Point) ScanPoint(v pgtype.Point) error { 19 | *p = Point{ 20 | X: float32(v.P.X), 21 | Y: float32(v.P.Y), 22 | Valid: v.Valid, 23 | } 24 | return nil 25 | } 26 | 27 | func (p Point) PointValue() (pgtype.Point, error) { 28 | return pgtype.Point{ 29 | P: pgtype.Vec2{X: float64(p.X), Y: float64(p.Y)}, 30 | Valid: true, 31 | }, nil 32 | } 33 | 34 | func (src *Point) String() string { 35 | if !src.Valid { 36 | return "null point" 37 | } 38 | 39 | return fmt.Sprintf("%.1f, %.1f", src.X, src.Y) 40 | } 41 | 42 | func Example_customType() { 43 | conn, err := pgx.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE")) 44 | if err != nil { 45 | fmt.Printf("Unable to establish connection: %v", err) 46 | return 47 | } 48 | defer conn.Close(context.Background()) 49 | 50 | if conn.PgConn().ParameterStatus("crdb_version") != "" { 51 | // Skip test / example when running on CockroachDB which doesn't support the point type. Since an example can't be 52 | // skipped fake success instead. 53 | fmt.Println("null point") 54 | fmt.Println("1.5, 2.5") 55 | return 56 | } 57 | 58 | p := &Point{} 59 | err = conn.QueryRow(context.Background(), "select null::point").Scan(p) 60 | if err != nil { 61 | fmt.Println(err) 62 | return 63 | } 64 | fmt.Println(p) 65 | 66 | err = conn.QueryRow(context.Background(), "select point(1.5,2.5)").Scan(p) 67 | if err != nil { 68 | fmt.Println(err) 69 | return 70 | } 71 | fmt.Println(p) 72 | // Output: 73 | // null point 74 | // 1.5, 2.5 75 | } 76 | -------------------------------------------------------------------------------- /pgtype/zeronull/timestamp.go: -------------------------------------------------------------------------------- 1 | package zeronull 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/jackc/pgx/v5/pgtype" 9 | ) 10 | 11 | type Timestamp time.Time 12 | 13 | // SkipUnderlyingTypePlan implements the [pgtype.SkipUnderlyingTypePlanner] interface. 14 | func (Timestamp) SkipUnderlyingTypePlan() {} 15 | 16 | // ScanTimestamp implements the [pgtype.TimestampScanner] interface. 17 | func (ts *Timestamp) ScanTimestamp(v pgtype.Timestamp) error { 18 | if !v.Valid { 19 | *ts = Timestamp{} 20 | return nil 21 | } 22 | 23 | switch v.InfinityModifier { 24 | case pgtype.Finite: 25 | *ts = Timestamp(v.Time) 26 | return nil 27 | case pgtype.Infinity: 28 | return fmt.Errorf("cannot scan Infinity into *time.Time") 29 | case pgtype.NegativeInfinity: 30 | return fmt.Errorf("cannot scan -Infinity into *time.Time") 31 | default: 32 | return fmt.Errorf("invalid InfinityModifier: %v", v.InfinityModifier) 33 | } 34 | } 35 | 36 | // TimestampValue implements the [pgtype.TimestampValuer] interface. 37 | func (ts Timestamp) TimestampValue() (pgtype.Timestamp, error) { 38 | if time.Time(ts).IsZero() { 39 | return pgtype.Timestamp{}, nil 40 | } 41 | 42 | return pgtype.Timestamp{Time: time.Time(ts), Valid: true}, nil 43 | } 44 | 45 | // Scan implements the [database/sql.Scanner] interface. 46 | func (ts *Timestamp) Scan(src any) error { 47 | if src == nil { 48 | *ts = Timestamp{} 49 | return nil 50 | } 51 | 52 | var nullable pgtype.Timestamp 53 | err := nullable.Scan(src) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | *ts = Timestamp(nullable.Time) 59 | 60 | return nil 61 | } 62 | 63 | // Value implements the [database/sql/driver.Valuer] interface. 64 | func (ts Timestamp) Value() (driver.Value, error) { 65 | if time.Time(ts).IsZero() { 66 | return nil, nil 67 | } 68 | 69 | return time.Time(ts), nil 70 | } 71 | -------------------------------------------------------------------------------- /examples/chat/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/jackc/pgx/v5/pgxpool" 10 | ) 11 | 12 | var pool *pgxpool.Pool 13 | 14 | func main() { 15 | var err error 16 | pool, err = pgxpool.New(context.Background(), os.Getenv("DATABASE_URL")) 17 | if err != nil { 18 | fmt.Fprintln(os.Stderr, "Unable to connect to database:", err) 19 | os.Exit(1) 20 | } 21 | 22 | go listen() 23 | 24 | fmt.Println(`Type a message and press enter. 25 | 26 | This message should appear in any other chat instances connected to the same 27 | database. 28 | 29 | Type "exit" to quit.`) 30 | 31 | scanner := bufio.NewScanner(os.Stdin) 32 | for scanner.Scan() { 33 | msg := scanner.Text() 34 | if msg == "exit" { 35 | os.Exit(0) 36 | } 37 | 38 | _, err = pool.Exec(context.Background(), "select pg_notify('chat', $1)", msg) 39 | if err != nil { 40 | fmt.Fprintln(os.Stderr, "Error sending notification:", err) 41 | os.Exit(1) 42 | } 43 | } 44 | if err := scanner.Err(); err != nil { 45 | fmt.Fprintln(os.Stderr, "Error scanning from stdin:", err) 46 | os.Exit(1) 47 | } 48 | } 49 | 50 | func listen() { 51 | conn, err := pool.Acquire(context.Background()) 52 | if err != nil { 53 | fmt.Fprintln(os.Stderr, "Error acquiring connection:", err) 54 | os.Exit(1) 55 | } 56 | defer conn.Release() 57 | 58 | _, err = conn.Exec(context.Background(), "listen chat") 59 | if err != nil { 60 | fmt.Fprintln(os.Stderr, "Error listening to chat channel:", err) 61 | os.Exit(1) 62 | } 63 | 64 | for { 65 | notification, err := conn.Conn().WaitForNotification(context.Background()) 66 | if err != nil { 67 | fmt.Fprintln(os.Stderr, "Error waiting for notification:", err) 68 | os.Exit(1) 69 | } 70 | 71 | fmt.Println("PID:", notification.PID, "Channel:", notification.Channel, "Payload:", notification.Payload) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pgtype/zeronull/timestamptz.go: -------------------------------------------------------------------------------- 1 | package zeronull 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/jackc/pgx/v5/pgtype" 9 | ) 10 | 11 | type Timestamptz time.Time 12 | 13 | // SkipUnderlyingTypePlan implements the [pgtype.SkipUnderlyingTypePlanner] interface. 14 | func (Timestamptz) SkipUnderlyingTypePlan() {} 15 | 16 | // ScanTimestamptz implements the [pgtype.TimestamptzScanner] interface. 17 | func (ts *Timestamptz) ScanTimestamptz(v pgtype.Timestamptz) error { 18 | if !v.Valid { 19 | *ts = Timestamptz{} 20 | return nil 21 | } 22 | 23 | switch v.InfinityModifier { 24 | case pgtype.Finite: 25 | *ts = Timestamptz(v.Time) 26 | return nil 27 | case pgtype.Infinity: 28 | return fmt.Errorf("cannot scan Infinity into *time.Time") 29 | case pgtype.NegativeInfinity: 30 | return fmt.Errorf("cannot scan -Infinity into *time.Time") 31 | default: 32 | return fmt.Errorf("invalid InfinityModifier: %v", v.InfinityModifier) 33 | } 34 | } 35 | 36 | // TimestamptzValue implements the [pgtype.TimestamptzValuer] interface. 37 | func (ts Timestamptz) TimestamptzValue() (pgtype.Timestamptz, error) { 38 | if time.Time(ts).IsZero() { 39 | return pgtype.Timestamptz{}, nil 40 | } 41 | 42 | return pgtype.Timestamptz{Time: time.Time(ts), Valid: true}, nil 43 | } 44 | 45 | // Scan implements the [database/sql.Scanner] interface. 46 | func (ts *Timestamptz) Scan(src any) error { 47 | if src == nil { 48 | *ts = Timestamptz{} 49 | return nil 50 | } 51 | 52 | var nullable pgtype.Timestamptz 53 | err := nullable.Scan(src) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | *ts = Timestamptz(nullable.Time) 59 | 60 | return nil 61 | } 62 | 63 | // Value implements the [database/sql/driver.Valuer] interface. 64 | func (ts Timestamptz) Value() (driver.Value, error) { 65 | if time.Time(ts).IsZero() { 66 | return nil, nil 67 | } 68 | 69 | return time.Time(ts), nil 70 | } 71 | -------------------------------------------------------------------------------- /pgconn/errors_test.go: -------------------------------------------------------------------------------- 1 | package pgconn_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jackc/pgx/v5/pgconn" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestConfigError(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | err error 14 | expectedMsg string 15 | }{ 16 | { 17 | name: "url with password", 18 | err: pgconn.NewParseConfigError("postgresql://foo:password@host", "msg", nil), 19 | expectedMsg: "cannot parse `postgresql://foo:xxxxx@host`: msg", 20 | }, 21 | { 22 | name: "keyword/value with password unquoted", 23 | err: pgconn.NewParseConfigError("host=host password=password user=user", "msg", nil), 24 | expectedMsg: "cannot parse `host=host password=xxxxx user=user`: msg", 25 | }, 26 | { 27 | name: "keyword/value with password quoted", 28 | err: pgconn.NewParseConfigError("host=host password='pass word' user=user", "msg", nil), 29 | expectedMsg: "cannot parse `host=host password=xxxxx user=user`: msg", 30 | }, 31 | { 32 | name: "weird url", 33 | err: pgconn.NewParseConfigError("postgresql://foo::password@host:1:", "msg", nil), 34 | expectedMsg: "cannot parse `postgresql://foo:xxxxx@host:1:`: msg", 35 | }, 36 | { 37 | name: "weird url with slash in password", 38 | err: pgconn.NewParseConfigError("postgres://user:pass/word@host:5432/db_name", "msg", nil), 39 | expectedMsg: "cannot parse `postgres://user:xxxxxx@host:5432/db_name`: msg", 40 | }, 41 | { 42 | name: "url without password", 43 | err: pgconn.NewParseConfigError("postgresql://other@host/db", "msg", nil), 44 | expectedMsg: "cannot parse `postgresql://other@host/db`: msg", 45 | }, 46 | } 47 | for _, tt := range tests { 48 | t.Run(tt.name, func(t *testing.T) { 49 | t.Parallel() 50 | assert.EqualError(t, tt.err, tt.expectedMsg) 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pgproto3/chunkreader_test.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "bytes" 5 | "math/rand/v2" 6 | "testing" 7 | ) 8 | 9 | func TestChunkReaderNextDoesNotReadIfAlreadyBuffered(t *testing.T) { 10 | server := &bytes.Buffer{} 11 | r := newChunkReader(server, 4) 12 | 13 | src := []byte{1, 2, 3, 4} 14 | server.Write(src) 15 | 16 | n1, err := r.Next(2) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | if !bytes.Equal(n1, src[0:2]) { 21 | t.Fatalf("Expected read bytes to be %v, but they were %v", src[0:2], n1) 22 | } 23 | 24 | n2, err := r.Next(2) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | if !bytes.Equal(n2, src[2:4]) { 29 | t.Fatalf("Expected read bytes to be %v, but they were %v", src[2:4], n2) 30 | } 31 | 32 | if !bytes.Equal((*r.buf)[:len(src)], src) { 33 | t.Fatalf("Expected r.buf to be %v, but it was %v", src, r.buf) 34 | } 35 | 36 | _, err = r.Next(0) // Trigger the buffer reset. 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | 41 | if r.rp != 0 { 42 | t.Fatalf("Expected r.rp to be %v, but it was %v", 0, r.rp) 43 | } 44 | if r.wp != 0 { 45 | t.Fatalf("Expected r.wp to be %v, but it was %v", 0, r.wp) 46 | } 47 | } 48 | 49 | type randomReader struct { 50 | rnd *rand.Rand 51 | } 52 | 53 | // Read reads a random number of random bytes. 54 | func (r *randomReader) Read(p []byte) (n int, err error) { 55 | n = r.rnd.IntN(len(p) + 1) 56 | for i := 0; i < n; i++ { 57 | p[i] = byte(r.rnd.Uint64()) 58 | } 59 | return n, nil 60 | } 61 | 62 | func TestChunkReaderNextFuzz(t *testing.T) { 63 | rr := &randomReader{rnd: rand.New(rand.NewPCG(1, 0))} 64 | r := newChunkReader(rr, 8192) 65 | 66 | randomSizes := rand.New(rand.NewPCG(0, 0)) 67 | 68 | for range 100_000 { 69 | size := randomSizes.IntN(16384) + 1 70 | buf, err := r.Next(size) 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | if len(buf) != size { 75 | t.Fatalf("Expected to get %v bytes but got %v bytes", size, len(buf)) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /pgtype/bool_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype" 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func TestBoolCodec(t *testing.T) { 12 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "bool", []pgxtest.ValueRoundTripTest{ 13 | {true, new(bool), isExpectedEq(true)}, 14 | {false, new(bool), isExpectedEq(false)}, 15 | {true, new(pgtype.Bool), isExpectedEq(pgtype.Bool{Bool: true, Valid: true})}, 16 | {pgtype.Bool{}, new(pgtype.Bool), isExpectedEq(pgtype.Bool{})}, 17 | {nil, new(*bool), isExpectedEq((*bool)(nil))}, 18 | }) 19 | } 20 | 21 | func TestBoolMarshalJSON(t *testing.T) { 22 | successfulTests := []struct { 23 | source pgtype.Bool 24 | result string 25 | }{ 26 | {source: pgtype.Bool{}, result: "null"}, 27 | {source: pgtype.Bool{Bool: true, Valid: true}, result: "true"}, 28 | {source: pgtype.Bool{Bool: false, Valid: true}, result: "false"}, 29 | } 30 | for i, tt := range successfulTests { 31 | r, err := tt.source.MarshalJSON() 32 | if err != nil { 33 | t.Errorf("%d: %v", i, err) 34 | } 35 | 36 | if string(r) != tt.result { 37 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r)) 38 | } 39 | } 40 | } 41 | 42 | func TestBoolUnmarshalJSON(t *testing.T) { 43 | successfulTests := []struct { 44 | source string 45 | result pgtype.Bool 46 | }{ 47 | {source: "null", result: pgtype.Bool{}}, 48 | {source: "true", result: pgtype.Bool{Bool: true, Valid: true}}, 49 | {source: "false", result: pgtype.Bool{Bool: false, Valid: true}}, 50 | } 51 | for i, tt := range successfulTests { 52 | var r pgtype.Bool 53 | err := r.UnmarshalJSON([]byte(tt.source)) 54 | if err != nil { 55 | t.Errorf("%d: %v", i, err) 56 | } 57 | 58 | if r != tt.result { 59 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pgtype/zeronull/int.go.erb: -------------------------------------------------------------------------------- 1 | package zeronull 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | "math" 7 | 8 | "github.com/jackc/pgx/v5/pgtype" 9 | ) 10 | 11 | <% [2, 4, 8].each do |pg_byte_size| %> 12 | <% pg_bit_size = pg_byte_size * 8 %> 13 | type Int<%= pg_byte_size %> int<%= pg_bit_size %> 14 | 15 | // SkipUnderlyingTypePlan implements the [pgtype.SkipUnderlyingTypePlanner] interface. 16 | func (Int<%= pg_byte_size %>) SkipUnderlyingTypePlan() {} 17 | 18 | // ScanInt64 implements the [pgtype.Int64Scanner] interface. 19 | func (dst *Int<%= pg_byte_size %>) ScanInt64(n pgtype.Int8) error { 20 | if !n.Valid { 21 | *dst = 0 22 | return nil 23 | } 24 | 25 | if n.Int64 < math.MinInt<%= pg_bit_size %> { 26 | return fmt.Errorf("%d is less than minimum value for Int<%= pg_byte_size %>", n.Int64) 27 | } 28 | if n.Int64 > math.MaxInt<%= pg_bit_size %> { 29 | return fmt.Errorf("%d is greater than maximum value for Int<%= pg_byte_size %>", n.Int64) 30 | } 31 | *dst = Int<%= pg_byte_size %>(n.Int64) 32 | 33 | return nil 34 | } 35 | 36 | // Int64Value implements the [pgtype.Int64Valuer] interface. 37 | func (src Int<%= pg_byte_size %>) Int64Value() (pgtype.Int8, error) { 38 | if src == 0 { 39 | return pgtype.Int8{}, nil 40 | } 41 | return pgtype.Int8{Int64: int64(src), Valid: true}, nil 42 | } 43 | 44 | // Scan implements the [database/sql.Scanner] interface. 45 | func (dst *Int<%= pg_byte_size %>) Scan(src any) error { 46 | if src == nil { 47 | *dst = 0 48 | return nil 49 | } 50 | 51 | var nullable pgtype.Int<%= pg_byte_size %> 52 | err := nullable.Scan(src) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | *dst = Int<%= pg_byte_size %>(nullable.Int<%= pg_bit_size %>) 58 | 59 | return nil 60 | } 61 | 62 | // Value implements the [database/sql/driver.Valuer] interface. 63 | func (src Int<%= pg_byte_size %>) Value() (driver.Value, error) { 64 | if src == 0 { 65 | return nil, nil 66 | } 67 | return int64(src), nil 68 | } 69 | <% end %> 70 | -------------------------------------------------------------------------------- /pgconn/doc.go: -------------------------------------------------------------------------------- 1 | // Package pgconn is a low-level PostgreSQL database driver. 2 | /* 3 | pgconn provides lower level access to a PostgreSQL connection than a database/sql or pgx connection. It operates at 4 | nearly the same level is the C library libpq. 5 | 6 | Establishing a Connection 7 | 8 | Use Connect to establish a connection. It accepts a connection string in URL or keyword/value format and will read the 9 | environment for libpq style environment variables. 10 | 11 | Executing a Query 12 | 13 | ExecParams and ExecPrepared execute a single query. They return readers that iterate over each row. The Read method 14 | reads all rows into memory. 15 | 16 | Executing Multiple Queries in a Single Round Trip 17 | 18 | Exec and ExecBatch can execute multiple queries in a single round trip. They return readers that iterate over each query 19 | result. The ReadAll method reads all query results into memory. 20 | 21 | Pipeline Mode 22 | 23 | Pipeline mode allows sending queries without having read the results of previously sent queries. It allows control of 24 | exactly how many and when network round trips occur. 25 | 26 | Context Support 27 | 28 | All potentially blocking operations take a context.Context. The default behavior when a context is canceled is for the 29 | method to immediately return. In most circumstances, this will also close the underlying connection. This behavior can 30 | be customized by using BuildContextWatcherHandler on the Config to create a ctxwatch.Handler with different behavior. 31 | This can be especially useful when queries that are frequently canceled and the overhead of creating new connections is 32 | a problem. DeadlineContextWatcherHandler and CancelRequestContextWatcherHandler can be used to introduce a delay before 33 | interrupting the query in such a way as to close the connection. 34 | 35 | The CancelRequest method may be used to request the PostgreSQL server cancel an in-progress query without forcing the 36 | client to abort. 37 | */ 38 | package pgconn 39 | -------------------------------------------------------------------------------- /pgproto3/command_complete.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | ) 7 | 8 | type CommandComplete struct { 9 | CommandTag []byte 10 | } 11 | 12 | // Backend identifies this message as sendable by the PostgreSQL backend. 13 | func (*CommandComplete) Backend() {} 14 | 15 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 16 | // type identifier and 4 byte message length. 17 | func (dst *CommandComplete) Decode(src []byte) error { 18 | idx := bytes.IndexByte(src, 0) 19 | if idx == -1 { 20 | return &invalidMessageFormatErr{messageType: "CommandComplete", details: "unterminated string"} 21 | } 22 | if idx != len(src)-1 { 23 | return &invalidMessageFormatErr{messageType: "CommandComplete", details: "string terminated too early"} 24 | } 25 | 26 | dst.CommandTag = src[:idx] 27 | 28 | return nil 29 | } 30 | 31 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 32 | func (src *CommandComplete) Encode(dst []byte) ([]byte, error) { 33 | dst, sp := beginMessage(dst, 'C') 34 | dst = append(dst, src.CommandTag...) 35 | dst = append(dst, 0) 36 | return finishMessage(dst, sp) 37 | } 38 | 39 | // MarshalJSON implements encoding/json.Marshaler. 40 | func (src CommandComplete) MarshalJSON() ([]byte, error) { 41 | return json.Marshal(struct { 42 | Type string 43 | CommandTag string 44 | }{ 45 | Type: "CommandComplete", 46 | CommandTag: string(src.CommandTag), 47 | }) 48 | } 49 | 50 | // UnmarshalJSON implements encoding/json.Unmarshaler. 51 | func (dst *CommandComplete) UnmarshalJSON(data []byte) error { 52 | // Ignore null, like in the main JSON package. 53 | if string(data) == "null" { 54 | return nil 55 | } 56 | 57 | var msg struct { 58 | CommandTag string 59 | } 60 | if err := json.Unmarshal(data, &msg); err != nil { 61 | return err 62 | } 63 | 64 | dst.CommandTag = []byte(msg.CommandTag) 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /pgtype/path_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype" 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func isExpectedEqPath(a any) func(any) bool { 12 | return func(v any) bool { 13 | ap := a.(pgtype.Path) 14 | vp := v.(pgtype.Path) 15 | 16 | if !(ap.Valid == vp.Valid && ap.Closed == vp.Closed && len(ap.P) == len(vp.P)) { 17 | return false 18 | } 19 | 20 | for i := range ap.P { 21 | if ap.P[i] != vp.P[i] { 22 | return false 23 | } 24 | } 25 | 26 | return true 27 | } 28 | } 29 | 30 | func TestPathTranscode(t *testing.T) { 31 | skipCockroachDB(t, "Server does not support type path") 32 | 33 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "path", []pgxtest.ValueRoundTripTest{ 34 | { 35 | pgtype.Path{ 36 | P: []pgtype.Vec2{{3.14, 1.678901234}, {7.1, 5.234}}, 37 | Closed: false, 38 | Valid: true, 39 | }, 40 | new(pgtype.Path), 41 | isExpectedEqPath(pgtype.Path{ 42 | P: []pgtype.Vec2{{3.14, 1.678901234}, {7.1, 5.234}}, 43 | Closed: false, 44 | Valid: true, 45 | }), 46 | }, 47 | { 48 | pgtype.Path{ 49 | P: []pgtype.Vec2{{3.14, 1.678}, {7.1, 5.234}, {23.1, 9.34}}, 50 | Closed: true, 51 | Valid: true, 52 | }, 53 | new(pgtype.Path), 54 | isExpectedEqPath(pgtype.Path{ 55 | P: []pgtype.Vec2{{3.14, 1.678}, {7.1, 5.234}, {23.1, 9.34}}, 56 | Closed: true, 57 | Valid: true, 58 | }), 59 | }, 60 | { 61 | pgtype.Path{ 62 | P: []pgtype.Vec2{{7.1, 1.678}, {-13.14, -5.234}}, 63 | Closed: true, 64 | Valid: true, 65 | }, 66 | new(pgtype.Path), 67 | isExpectedEqPath(pgtype.Path{ 68 | P: []pgtype.Vec2{{7.1, 1.678}, {-13.14, -5.234}}, 69 | Closed: true, 70 | Valid: true, 71 | }), 72 | }, 73 | {pgtype.Path{}, new(pgtype.Path), isExpectedEqPath(pgtype.Path{})}, 74 | {nil, new(pgtype.Path), isExpectedEqPath(pgtype.Path{})}, 75 | }) 76 | } 77 | -------------------------------------------------------------------------------- /pgproto3/notification_response.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | 8 | "github.com/jackc/pgx/v5/internal/pgio" 9 | ) 10 | 11 | type NotificationResponse struct { 12 | PID uint32 13 | Channel string 14 | Payload string 15 | } 16 | 17 | // Backend identifies this message as sendable by the PostgreSQL backend. 18 | func (*NotificationResponse) Backend() {} 19 | 20 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 21 | // type identifier and 4 byte message length. 22 | func (dst *NotificationResponse) Decode(src []byte) error { 23 | buf := bytes.NewBuffer(src) 24 | 25 | if buf.Len() < 4 { 26 | return &invalidMessageFormatErr{messageType: "NotificationResponse", details: "too short"} 27 | } 28 | 29 | pid := binary.BigEndian.Uint32(buf.Next(4)) 30 | 31 | b, err := buf.ReadBytes(0) 32 | if err != nil { 33 | return err 34 | } 35 | channel := string(b[:len(b)-1]) 36 | 37 | b, err = buf.ReadBytes(0) 38 | if err != nil { 39 | return err 40 | } 41 | payload := string(b[:len(b)-1]) 42 | 43 | *dst = NotificationResponse{PID: pid, Channel: channel, Payload: payload} 44 | return nil 45 | } 46 | 47 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 48 | func (src *NotificationResponse) Encode(dst []byte) ([]byte, error) { 49 | dst, sp := beginMessage(dst, 'A') 50 | dst = pgio.AppendUint32(dst, src.PID) 51 | dst = append(dst, src.Channel...) 52 | dst = append(dst, 0) 53 | dst = append(dst, src.Payload...) 54 | dst = append(dst, 0) 55 | return finishMessage(dst, sp) 56 | } 57 | 58 | // MarshalJSON implements encoding/json.Marshaler. 59 | func (src NotificationResponse) MarshalJSON() ([]byte, error) { 60 | return json.Marshal(struct { 61 | Type string 62 | PID uint32 63 | Channel string 64 | Payload string 65 | }{ 66 | Type: "NotificationResponse", 67 | PID: src.PID, 68 | Channel: src.Channel, 69 | Payload: src.Payload, 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /pgtype/derived_types_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | pgx "github.com/jackc/pgx/v5" 8 | "github.com/jackc/pgx/v5/pgtype" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestDerivedTypes(t *testing.T) { 13 | skipCockroachDB(t, "Server does not support composite types (see https://github.com/cockroachdb/cockroach/issues/27792)") 14 | 15 | defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 16 | _, err := conn.Exec(ctx, ` 17 | drop type if exists dt_test; 18 | drop domain if exists dt_uint64; 19 | 20 | create domain dt_uint64 as numeric(20,0); 21 | create type dt_test as ( 22 | a text, 23 | b dt_uint64, 24 | c dt_uint64[] 25 | );`) 26 | require.NoError(t, err) 27 | defer conn.Exec(ctx, "drop domain dt_uint64") 28 | defer conn.Exec(ctx, "drop type dt_test") 29 | 30 | dtypes, err := conn.LoadTypes(ctx, []string{"dt_test"}) 31 | require.Len(t, dtypes, 6) 32 | require.Equal(t, dtypes[0].Name, "public.dt_uint64") 33 | require.Equal(t, dtypes[1].Name, "dt_uint64") 34 | require.Equal(t, dtypes[2].Name, "public._dt_uint64") 35 | require.Equal(t, dtypes[3].Name, "_dt_uint64") 36 | require.Equal(t, dtypes[4].Name, "public.dt_test") 37 | require.Equal(t, dtypes[5].Name, "dt_test") 38 | require.NoError(t, err) 39 | conn.TypeMap().RegisterTypes(dtypes) 40 | 41 | formats := []struct { 42 | name string 43 | code int16 44 | }{ 45 | {name: "TextFormat", code: pgx.TextFormatCode}, 46 | {name: "BinaryFormat", code: pgx.BinaryFormatCode}, 47 | } 48 | 49 | for _, format := range formats { 50 | var a string 51 | var b uint64 52 | var c *[]uint64 53 | 54 | row := conn.QueryRow(ctx, "select $1::dt_test", pgx.QueryResultFormats{format.code}, pgtype.CompositeFields{"hi", uint64(42), []uint64{10, 20, 30}}) 55 | err := row.Scan(pgtype.CompositeFields{&a, &b, &c}) 56 | require.NoError(t, err) 57 | require.EqualValuesf(t, "hi", a, "%v", format.name) 58 | require.EqualValuesf(t, 42, b, "%v", format.name) 59 | } 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /pgxpool/bench_test.go: -------------------------------------------------------------------------------- 1 | package pgxpool_test 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/jackc/pgx/v5" 9 | "github.com/jackc/pgx/v5/pgxpool" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func BenchmarkAcquireAndRelease(b *testing.B) { 14 | pool, err := pgxpool.New(context.Background(), os.Getenv("PGX_TEST_DATABASE")) 15 | require.NoError(b, err) 16 | defer pool.Close() 17 | 18 | for b.Loop() { 19 | c, err := pool.Acquire(context.Background()) 20 | if err != nil { 21 | b.Fatal(err) 22 | } 23 | c.Release() 24 | } 25 | } 26 | 27 | func BenchmarkMinimalPreparedSelectBaseline(b *testing.B) { 28 | config, err := pgxpool.ParseConfig(os.Getenv("PGX_TEST_DATABASE")) 29 | require.NoError(b, err) 30 | 31 | config.AfterConnect = func(ctx context.Context, c *pgx.Conn) error { 32 | _, err := c.Prepare(ctx, "ps1", "select $1::int8") 33 | return err 34 | } 35 | 36 | db, err := pgxpool.NewWithConfig(context.Background(), config) 37 | require.NoError(b, err) 38 | 39 | conn, err := db.Acquire(context.Background()) 40 | require.NoError(b, err) 41 | defer conn.Release() 42 | 43 | var n int64 44 | 45 | for i := 0; b.Loop(); i++ { 46 | err = conn.QueryRow(context.Background(), "ps1", i).Scan(&n) 47 | if err != nil { 48 | b.Fatal(err) 49 | } 50 | 51 | if n != int64(i) { 52 | b.Fatalf("expected %d, got %d", i, n) 53 | } 54 | } 55 | } 56 | 57 | func BenchmarkMinimalPreparedSelect(b *testing.B) { 58 | config, err := pgxpool.ParseConfig(os.Getenv("PGX_TEST_DATABASE")) 59 | require.NoError(b, err) 60 | 61 | config.AfterConnect = func(ctx context.Context, c *pgx.Conn) error { 62 | _, err := c.Prepare(ctx, "ps1", "select $1::int8") 63 | return err 64 | } 65 | 66 | db, err := pgxpool.NewWithConfig(context.Background(), config) 67 | require.NoError(b, err) 68 | 69 | var n int64 70 | 71 | for i := 0; b.Loop(); i++ { 72 | err = db.QueryRow(context.Background(), "ps1", i).Scan(&n) 73 | if err != nil { 74 | b.Fatal(err) 75 | } 76 | 77 | if n != int64(i) { 78 | b.Fatalf("expected %d, got %d", i, n) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /pgtype/bits_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "testing" 7 | 8 | "github.com/jackc/pgx/v5/pgtype" 9 | "github.com/jackc/pgx/v5/pgxtest" 10 | ) 11 | 12 | func isExpectedEqBits(a any) func(any) bool { 13 | return func(v any) bool { 14 | ab := a.(pgtype.Bits) 15 | vb := v.(pgtype.Bits) 16 | return bytes.Equal(ab.Bytes, vb.Bytes) && ab.Len == vb.Len && ab.Valid == vb.Valid 17 | } 18 | } 19 | 20 | func TestBitsCodecBit(t *testing.T) { 21 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "bit(40)", []pgxtest.ValueRoundTripTest{ 22 | { 23 | pgtype.Bits{Bytes: []byte{0, 0, 0, 0, 0}, Len: 40, Valid: true}, 24 | new(pgtype.Bits), 25 | isExpectedEqBits(pgtype.Bits{Bytes: []byte{0, 0, 0, 0, 0}, Len: 40, Valid: true}), 26 | }, 27 | { 28 | pgtype.Bits{Bytes: []byte{0, 1, 128, 254, 255}, Len: 40, Valid: true}, 29 | new(pgtype.Bits), 30 | isExpectedEqBits(pgtype.Bits{Bytes: []byte{0, 1, 128, 254, 255}, Len: 40, Valid: true}), 31 | }, 32 | {pgtype.Bits{}, new(pgtype.Bits), isExpectedEqBits(pgtype.Bits{})}, 33 | {nil, new(pgtype.Bits), isExpectedEqBits(pgtype.Bits{})}, 34 | }) 35 | } 36 | 37 | func TestBitsCodecVarbit(t *testing.T) { 38 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "varbit", []pgxtest.ValueRoundTripTest{ 39 | { 40 | pgtype.Bits{Bytes: []byte{}, Len: 0, Valid: true}, 41 | new(pgtype.Bits), 42 | isExpectedEqBits(pgtype.Bits{Bytes: []byte{}, Len: 0, Valid: true}), 43 | }, 44 | { 45 | pgtype.Bits{Bytes: []byte{0, 1, 128, 254, 255}, Len: 40, Valid: true}, 46 | new(pgtype.Bits), 47 | isExpectedEqBits(pgtype.Bits{Bytes: []byte{0, 1, 128, 254, 255}, Len: 40, Valid: true}), 48 | }, 49 | { 50 | pgtype.Bits{Bytes: []byte{0, 1, 128, 254, 128}, Len: 33, Valid: true}, 51 | new(pgtype.Bits), 52 | isExpectedEqBits(pgtype.Bits{Bytes: []byte{0, 1, 128, 254, 128}, Len: 33, Valid: true}), 53 | }, 54 | {pgtype.Bits{}, new(pgtype.Bits), isExpectedEqBits(pgtype.Bits{})}, 55 | {nil, new(pgtype.Bits), isExpectedEqBits(pgtype.Bits{})}, 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /pgtype/record_codec_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | pgx "github.com/jackc/pgx/v5" 8 | "github.com/jackc/pgx/v5/pgtype" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestRecordCodec(t *testing.T) { 13 | defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 14 | var a string 15 | var b int32 16 | err := conn.QueryRow(ctx, `select row('foo'::text, 42::int4)`).Scan(pgtype.CompositeFields{&a, &b}) 17 | require.NoError(t, err) 18 | 19 | require.Equal(t, "foo", a) 20 | require.Equal(t, int32(42), b) 21 | }) 22 | } 23 | 24 | func TestRecordCodecDecodeValue(t *testing.T) { 25 | skipCockroachDB(t, "Server converts row int4 to int8") 26 | 27 | defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, _ testing.TB, conn *pgx.Conn) { 28 | for _, tt := range []struct { 29 | sql string 30 | expected any 31 | }{ 32 | { 33 | sql: `select row()`, 34 | expected: []any{}, 35 | }, 36 | { 37 | sql: `select row('foo'::text, 42::int4)`, 38 | expected: []any{"foo", int32(42)}, 39 | }, 40 | { 41 | sql: `select row(100.0::float4, 1.09::float4)`, 42 | expected: []any{float32(100), float32(1.09)}, 43 | }, 44 | { 45 | sql: `select row('foo'::text, array[1, 2, null, 4]::int4[], 42::int4)`, 46 | expected: []any{"foo", []any{int32(1), int32(2), nil, int32(4)}, int32(42)}, 47 | }, 48 | { 49 | sql: `select row(null)`, 50 | expected: []any{nil}, 51 | }, 52 | { 53 | sql: `select null::record`, 54 | expected: nil, 55 | }, 56 | } { 57 | t.Run(tt.sql, func(t *testing.T) { 58 | rows, err := conn.Query(context.Background(), tt.sql) 59 | require.NoError(t, err) 60 | defer rows.Close() 61 | 62 | for rows.Next() { 63 | values, err := rows.Values() 64 | require.NoError(t, err) 65 | require.Len(t, values, 1) 66 | require.Equal(t, tt.expected, values[0]) 67 | } 68 | 69 | require.NoError(t, rows.Err()) 70 | }) 71 | } 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /pgproto3/parameter_description.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | "errors" 8 | "math" 9 | 10 | "github.com/jackc/pgx/v5/internal/pgio" 11 | ) 12 | 13 | type ParameterDescription struct { 14 | ParameterOIDs []uint32 15 | } 16 | 17 | // Backend identifies this message as sendable by the PostgreSQL backend. 18 | func (*ParameterDescription) Backend() {} 19 | 20 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 21 | // type identifier and 4 byte message length. 22 | func (dst *ParameterDescription) Decode(src []byte) error { 23 | buf := bytes.NewBuffer(src) 24 | 25 | if buf.Len() < 2 { 26 | return &invalidMessageFormatErr{messageType: "ParameterDescription"} 27 | } 28 | 29 | // Reported parameter count will be incorrect when number of args is greater than uint16 30 | buf.Next(2) 31 | // Instead infer parameter count by remaining size of message 32 | parameterCount := buf.Len() / 4 33 | 34 | *dst = ParameterDescription{ParameterOIDs: make([]uint32, parameterCount)} 35 | 36 | for i := range parameterCount { 37 | dst.ParameterOIDs[i] = binary.BigEndian.Uint32(buf.Next(4)) 38 | } 39 | 40 | return nil 41 | } 42 | 43 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 44 | func (src *ParameterDescription) Encode(dst []byte) ([]byte, error) { 45 | dst, sp := beginMessage(dst, 't') 46 | 47 | if len(src.ParameterOIDs) > math.MaxUint16 { 48 | return nil, errors.New("too many parameter oids") 49 | } 50 | dst = pgio.AppendUint16(dst, uint16(len(src.ParameterOIDs))) 51 | for _, oid := range src.ParameterOIDs { 52 | dst = pgio.AppendUint32(dst, oid) 53 | } 54 | 55 | return finishMessage(dst, sp) 56 | } 57 | 58 | // MarshalJSON implements encoding/json.Marshaler. 59 | func (src ParameterDescription) MarshalJSON() ([]byte, error) { 60 | return json.Marshal(struct { 61 | Type string 62 | ParameterOIDs []uint32 63 | }{ 64 | Type: "ParameterDescription", 65 | ParameterOIDs: src.ParameterOIDs, 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /pgtype/macaddr_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "net" 7 | "testing" 8 | 9 | "github.com/jackc/pgx/v5/pgxtest" 10 | ) 11 | 12 | func isExpectedEqHardwareAddr(a any) func(any) bool { 13 | return func(v any) bool { 14 | aa := a.(net.HardwareAddr) 15 | vv := v.(net.HardwareAddr) 16 | 17 | if (aa == nil) != (vv == nil) { 18 | return false 19 | } 20 | 21 | if aa == nil { 22 | return true 23 | } 24 | 25 | return bytes.Equal(aa, vv) 26 | } 27 | } 28 | 29 | func TestMacaddrCodec(t *testing.T) { 30 | skipCockroachDB(t, "Server does not support type macaddr") 31 | 32 | // Only testing known OID query exec modes as net.HardwareAddr could map to macaddr or macaddr8. 33 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, "macaddr", []pgxtest.ValueRoundTripTest{ 34 | { 35 | mustParseMacaddr(t, "01:23:45:67:89:ab"), 36 | new(net.HardwareAddr), 37 | isExpectedEqHardwareAddr(mustParseMacaddr(t, "01:23:45:67:89:ab")), 38 | }, 39 | { 40 | "01:23:45:67:89:ab", 41 | new(net.HardwareAddr), 42 | isExpectedEqHardwareAddr(mustParseMacaddr(t, "01:23:45:67:89:ab")), 43 | }, 44 | { 45 | mustParseMacaddr(t, "01:23:45:67:89:ab"), 46 | new(string), 47 | isExpectedEq("01:23:45:67:89:ab"), 48 | }, 49 | {nil, new(*net.HardwareAddr), isExpectedEq((*net.HardwareAddr)(nil))}, 50 | }) 51 | 52 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, "macaddr8", []pgxtest.ValueRoundTripTest{ 53 | { 54 | mustParseMacaddr(t, "01:23:45:67:89:ab:01:08"), 55 | new(net.HardwareAddr), 56 | isExpectedEqHardwareAddr(mustParseMacaddr(t, "01:23:45:67:89:ab:01:08")), 57 | }, 58 | { 59 | "01:23:45:67:89:ab:01:08", 60 | new(net.HardwareAddr), 61 | isExpectedEqHardwareAddr(mustParseMacaddr(t, "01:23:45:67:89:ab:01:08")), 62 | }, 63 | { 64 | mustParseMacaddr(t, "01:23:45:67:89:ab:01:08"), 65 | new(string), 66 | isExpectedEq("01:23:45:67:89:ab:01:08"), 67 | }, 68 | {nil, new(*net.HardwareAddr), isExpectedEq((*net.HardwareAddr)(nil))}, 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /examples/todo/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This is a sample todo list implemented using pgx as the connector to a 4 | PostgreSQL data store. 5 | 6 | # Usage 7 | 8 | Create a PostgreSQL database and run structure.sql into it to create the 9 | necessary data schema. 10 | 11 | Example: 12 | 13 | createdb todo 14 | psql todo < structure.sql 15 | 16 | Build todo: 17 | 18 | go build 19 | 20 | ## Connection configuration 21 | 22 | The database connection is configured via DATABASE_URL and standard PostgreSQL environment variables (PGHOST, PGUSER, etc.) 23 | 24 | You can either export them then run todo: 25 | 26 | export PGDATABASE=todo 27 | ./todo list 28 | 29 | Or you can prefix the todo execution with the environment variables: 30 | 31 | PGDATABASE=todo ./todo list 32 | 33 | ## Add a todo item 34 | 35 | ./todo add 'Learn go' 36 | 37 | ## List tasks 38 | 39 | ./todo list 40 | 41 | ## Update a task 42 | 43 | ./todo update 1 'Learn more go' 44 | 45 | ## Delete a task 46 | 47 | ./todo remove 1 48 | 49 | # Example Setup and Execution 50 | 51 | jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ createdb todo 52 | jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ psql todo < structure.sql 53 | Expanded display is used automatically. 54 | Timing is on. 55 | CREATE TABLE 56 | Time: 6.363 ms 57 | jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ go build 58 | jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ export PGDATABASE=todo 59 | jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ ./todo list 60 | jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ ./todo add 'Learn Go' 61 | jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ ./todo list 62 | 1. Learn Go 63 | jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ ./todo update 1 'Learn more Go' 64 | jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ ./todo list 65 | 1. Learn more Go 66 | jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ ./todo remove 1 67 | jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ ./todo list 68 | -------------------------------------------------------------------------------- /pgtype/enum_codec_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | pgx "github.com/jackc/pgx/v5" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestEnumCodec(t *testing.T) { 12 | defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 13 | _, err := conn.Exec(ctx, `drop type if exists enum_test; 14 | 15 | create type enum_test as enum ('foo', 'bar', 'baz');`) 16 | require.NoError(t, err) 17 | defer conn.Exec(ctx, "drop type enum_test") 18 | 19 | dt, err := conn.LoadType(ctx, "enum_test") 20 | require.NoError(t, err) 21 | 22 | conn.TypeMap().RegisterType(dt) 23 | 24 | var s string 25 | err = conn.QueryRow(ctx, `select 'foo'::enum_test`).Scan(&s) 26 | require.NoError(t, err) 27 | require.Equal(t, "foo", s) 28 | 29 | err = conn.QueryRow(ctx, `select $1::enum_test`, "bar").Scan(&s) 30 | require.NoError(t, err) 31 | require.Equal(t, "bar", s) 32 | 33 | err = conn.QueryRow(ctx, `select 'foo'::enum_test`).Scan(&s) 34 | require.NoError(t, err) 35 | require.Equal(t, "foo", s) 36 | 37 | err = conn.QueryRow(ctx, `select $1::enum_test`, "bar").Scan(&s) 38 | require.NoError(t, err) 39 | require.Equal(t, "bar", s) 40 | 41 | err = conn.QueryRow(ctx, `select 'baz'::enum_test`).Scan(&s) 42 | require.NoError(t, err) 43 | require.Equal(t, "baz", s) 44 | }) 45 | } 46 | 47 | func TestEnumCodecValues(t *testing.T) { 48 | defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 49 | _, err := conn.Exec(ctx, `drop type if exists enum_test; 50 | 51 | create type enum_test as enum ('foo', 'bar', 'baz');`) 52 | require.NoError(t, err) 53 | defer conn.Exec(ctx, "drop type enum_test") 54 | 55 | dt, err := conn.LoadType(ctx, "enum_test") 56 | require.NoError(t, err) 57 | 58 | conn.TypeMap().RegisterType(dt) 59 | 60 | rows, err := conn.Query(ctx, `select 'foo'::enum_test`) 61 | require.NoError(t, err) 62 | require.True(t, rows.Next()) 63 | values, err := rows.Values() 64 | require.NoError(t, err) 65 | require.Equal(t, []any{"foo"}, values) 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /pgconn/defaults.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package pgconn 5 | 6 | import ( 7 | "os" 8 | "os/user" 9 | "path/filepath" 10 | ) 11 | 12 | func defaultSettings() map[string]string { 13 | settings := make(map[string]string) 14 | 15 | settings["host"] = defaultHost() 16 | settings["port"] = "5432" 17 | 18 | // Default to the OS user name. Purposely ignoring err getting user name from 19 | // OS. The client application will simply have to specify the user in that 20 | // case (which they typically will be doing anyway). 21 | user, err := user.Current() 22 | if err == nil { 23 | settings["user"] = user.Username 24 | settings["passfile"] = filepath.Join(user.HomeDir, ".pgpass") 25 | settings["servicefile"] = filepath.Join(user.HomeDir, ".pg_service.conf") 26 | sslcert := filepath.Join(user.HomeDir, ".postgresql", "postgresql.crt") 27 | sslkey := filepath.Join(user.HomeDir, ".postgresql", "postgresql.key") 28 | if _, err := os.Stat(sslcert); err == nil { 29 | if _, err := os.Stat(sslkey); err == nil { 30 | // Both the cert and key must be present to use them, or do not use either 31 | settings["sslcert"] = sslcert 32 | settings["sslkey"] = sslkey 33 | } 34 | } 35 | sslrootcert := filepath.Join(user.HomeDir, ".postgresql", "root.crt") 36 | if _, err := os.Stat(sslrootcert); err == nil { 37 | settings["sslrootcert"] = sslrootcert 38 | } 39 | } 40 | 41 | settings["target_session_attrs"] = "any" 42 | 43 | return settings 44 | } 45 | 46 | // defaultHost attempts to mimic libpq's default host. libpq uses the default unix socket location on *nix and localhost 47 | // on Windows. The default socket location is compiled into libpq. Since pgx does not have access to that default it 48 | // checks the existence of common locations. 49 | func defaultHost() string { 50 | candidatePaths := []string{ 51 | "/var/run/postgresql", // Debian 52 | "/private/tmp", // OSX - homebrew 53 | "/tmp", // standard PostgreSQL 54 | } 55 | 56 | for _, path := range candidatePaths { 57 | if _, err := os.Stat(path); err == nil { 58 | return path 59 | } 60 | } 61 | 62 | return "localhost" 63 | } 64 | -------------------------------------------------------------------------------- /pgconn/defaults_windows.go: -------------------------------------------------------------------------------- 1 | package pgconn 2 | 3 | import ( 4 | "os" 5 | "os/user" 6 | "path/filepath" 7 | "strings" 8 | ) 9 | 10 | func defaultSettings() map[string]string { 11 | settings := make(map[string]string) 12 | 13 | settings["host"] = defaultHost() 14 | settings["port"] = "5432" 15 | 16 | // Default to the OS user name. Purposely ignoring err getting user name from 17 | // OS. The client application will simply have to specify the user in that 18 | // case (which they typically will be doing anyway). 19 | user, err := user.Current() 20 | appData := os.Getenv("APPDATA") 21 | if err == nil { 22 | // Windows gives us the username here as `DOMAIN\user` or `LOCALPCNAME\user`, 23 | // but the libpq default is just the `user` portion, so we strip off the first part. 24 | username := user.Username 25 | if strings.Contains(username, "\\") { 26 | username = username[strings.LastIndex(username, "\\")+1:] 27 | } 28 | 29 | settings["user"] = username 30 | settings["passfile"] = filepath.Join(appData, "postgresql", "pgpass.conf") 31 | settings["servicefile"] = filepath.Join(user.HomeDir, ".pg_service.conf") 32 | sslcert := filepath.Join(appData, "postgresql", "postgresql.crt") 33 | sslkey := filepath.Join(appData, "postgresql", "postgresql.key") 34 | if _, err := os.Stat(sslcert); err == nil { 35 | if _, err := os.Stat(sslkey); err == nil { 36 | // Both the cert and key must be present to use them, or do not use either 37 | settings["sslcert"] = sslcert 38 | settings["sslkey"] = sslkey 39 | } 40 | } 41 | sslrootcert := filepath.Join(appData, "postgresql", "root.crt") 42 | if _, err := os.Stat(sslrootcert); err == nil { 43 | settings["sslrootcert"] = sslrootcert 44 | } 45 | } 46 | 47 | settings["target_session_attrs"] = "any" 48 | 49 | return settings 50 | } 51 | 52 | // defaultHost attempts to mimic libpq's default host. libpq uses the default unix socket location on *nix and localhost 53 | // on Windows. The default socket location is compiled into libpq. Since pgx does not have access to that default it 54 | // checks the existence of common locations. 55 | func defaultHost() string { 56 | return "localhost" 57 | } 58 | -------------------------------------------------------------------------------- /pgproto3/function_call_test.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/binary" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestFunctionCall_EncodeDecode(t *testing.T) { 12 | type fields struct { 13 | Function uint32 14 | ArgFormatCodes []uint16 15 | Arguments [][]byte 16 | ResultFormatCode uint16 17 | } 18 | tests := []struct { 19 | name string 20 | fields fields 21 | wantErr bool 22 | }{ 23 | {"valid", fields{uint32(123), []uint16{0, 1, 0, 1}, [][]byte{[]byte("foo"), []byte("bar"), []byte("baz")}, uint16(1)}, false}, 24 | {"invalid format code", fields{uint32(123), []uint16{2, 1, 0, 1}, [][]byte{[]byte("foo"), []byte("bar"), []byte("baz")}, uint16(0)}, true}, 25 | {"invalid result format code", fields{uint32(123), []uint16{1, 1, 0, 1}, [][]byte{[]byte("foo"), []byte("bar"), []byte("baz")}, uint16(2)}, true}, 26 | } 27 | for _, tt := range tests { 28 | t.Run(tt.name, func(t *testing.T) { 29 | src := &FunctionCall{ 30 | Function: tt.fields.Function, 31 | ArgFormatCodes: tt.fields.ArgFormatCodes, 32 | Arguments: tt.fields.Arguments, 33 | ResultFormatCode: tt.fields.ResultFormatCode, 34 | } 35 | encoded, err := src.Encode([]byte{}) 36 | require.NoError(t, err) 37 | dst := &FunctionCall{} 38 | // Check the header 39 | msgTypeCode := encoded[0] 40 | if msgTypeCode != 'F' { 41 | t.Errorf("msgTypeCode %v should be 'F'", msgTypeCode) 42 | return 43 | } 44 | // Check length, does not include type code character 45 | l := binary.BigEndian.Uint32(encoded[1:5]) 46 | if int(l) != (len(encoded) - 1) { 47 | t.Errorf("Incorrect message length, got = %v, wanted = %v", l, len(encoded)) 48 | } 49 | // Check decoding works as expected 50 | err = dst.Decode(encoded[5:]) 51 | if err != nil { 52 | if !tt.wantErr { 53 | t.Errorf("FunctionCall.Decode() error = %v, wantErr %v", err, tt.wantErr) 54 | } 55 | return 56 | } 57 | 58 | if !reflect.DeepEqual(src, dst) { 59 | t.Error("difference after encode / decode cycle") 60 | t.Errorf("src = %v", src) 61 | t.Errorf("dst = %v", dst) 62 | } 63 | }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /pgproto3/close.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | ) 8 | 9 | type Close struct { 10 | ObjectType byte // 'S' = prepared statement, 'P' = portal 11 | Name string 12 | } 13 | 14 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 15 | func (*Close) Frontend() {} 16 | 17 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 18 | // type identifier and 4 byte message length. 19 | func (dst *Close) Decode(src []byte) error { 20 | if len(src) < 2 { 21 | return &invalidMessageFormatErr{messageType: "Close"} 22 | } 23 | 24 | dst.ObjectType = src[0] 25 | rp := 1 26 | 27 | idx := bytes.IndexByte(src[rp:], 0) 28 | if idx != len(src[rp:])-1 { 29 | return &invalidMessageFormatErr{messageType: "Close"} 30 | } 31 | 32 | dst.Name = string(src[rp : len(src)-1]) 33 | 34 | return nil 35 | } 36 | 37 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 38 | func (src *Close) Encode(dst []byte) ([]byte, error) { 39 | dst, sp := beginMessage(dst, 'C') 40 | dst = append(dst, src.ObjectType) 41 | dst = append(dst, src.Name...) 42 | dst = append(dst, 0) 43 | return finishMessage(dst, sp) 44 | } 45 | 46 | // MarshalJSON implements encoding/json.Marshaler. 47 | func (src Close) MarshalJSON() ([]byte, error) { 48 | return json.Marshal(struct { 49 | Type string 50 | ObjectType string 51 | Name string 52 | }{ 53 | Type: "Close", 54 | ObjectType: string(src.ObjectType), 55 | Name: src.Name, 56 | }) 57 | } 58 | 59 | // UnmarshalJSON implements encoding/json.Unmarshaler. 60 | func (dst *Close) UnmarshalJSON(data []byte) error { 61 | // Ignore null, like in the main JSON package. 62 | if string(data) == "null" { 63 | return nil 64 | } 65 | 66 | var msg struct { 67 | ObjectType string 68 | Name string 69 | } 70 | if err := json.Unmarshal(data, &msg); err != nil { 71 | return err 72 | } 73 | 74 | if len(msg.ObjectType) != 1 { 75 | return errors.New("invalid length for Close.ObjectType") 76 | } 77 | 78 | dst.ObjectType = byte(msg.ObjectType[0]) 79 | dst.Name = msg.Name 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /pgproto3/describe.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | ) 8 | 9 | type Describe struct { 10 | ObjectType byte // 'S' = prepared statement, 'P' = portal 11 | Name string 12 | } 13 | 14 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 15 | func (*Describe) Frontend() {} 16 | 17 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 18 | // type identifier and 4 byte message length. 19 | func (dst *Describe) Decode(src []byte) error { 20 | if len(src) < 2 { 21 | return &invalidMessageFormatErr{messageType: "Describe"} 22 | } 23 | 24 | dst.ObjectType = src[0] 25 | rp := 1 26 | 27 | idx := bytes.IndexByte(src[rp:], 0) 28 | if idx != len(src[rp:])-1 { 29 | return &invalidMessageFormatErr{messageType: "Describe"} 30 | } 31 | 32 | dst.Name = string(src[rp : len(src)-1]) 33 | 34 | return nil 35 | } 36 | 37 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 38 | func (src *Describe) Encode(dst []byte) ([]byte, error) { 39 | dst, sp := beginMessage(dst, 'D') 40 | dst = append(dst, src.ObjectType) 41 | dst = append(dst, src.Name...) 42 | dst = append(dst, 0) 43 | return finishMessage(dst, sp) 44 | } 45 | 46 | // MarshalJSON implements encoding/json.Marshaler. 47 | func (src Describe) MarshalJSON() ([]byte, error) { 48 | return json.Marshal(struct { 49 | Type string 50 | ObjectType string 51 | Name string 52 | }{ 53 | Type: "Describe", 54 | ObjectType: string(src.ObjectType), 55 | Name: src.Name, 56 | }) 57 | } 58 | 59 | // UnmarshalJSON implements encoding/json.Unmarshaler. 60 | func (dst *Describe) UnmarshalJSON(data []byte) error { 61 | // Ignore null, like in the main JSON package. 62 | if string(data) == "null" { 63 | return nil 64 | } 65 | 66 | var msg struct { 67 | ObjectType string 68 | Name string 69 | } 70 | if err := json.Unmarshal(data, &msg); err != nil { 71 | return err 72 | } 73 | if len(msg.ObjectType) != 1 { 74 | return errors.New("invalid length for Describe.ObjectType") 75 | } 76 | 77 | dst.ObjectType = byte(msg.ObjectType[0]) 78 | dst.Name = msg.Name 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /pgproto3/authentication_sasl_final.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/jackc/pgx/v5/internal/pgio" 9 | ) 10 | 11 | // AuthenticationSASLFinal is a message sent from the backend indicating a SASL authentication has completed. 12 | type AuthenticationSASLFinal struct { 13 | Data []byte 14 | } 15 | 16 | // Backend identifies this message as sendable by the PostgreSQL backend. 17 | func (*AuthenticationSASLFinal) Backend() {} 18 | 19 | // Backend identifies this message as an authentication response. 20 | func (*AuthenticationSASLFinal) AuthenticationResponse() {} 21 | 22 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 23 | // type identifier and 4 byte message length. 24 | func (dst *AuthenticationSASLFinal) Decode(src []byte) error { 25 | if len(src) < 4 { 26 | return errors.New("authentication message too short") 27 | } 28 | 29 | authType := binary.BigEndian.Uint32(src) 30 | 31 | if authType != AuthTypeSASLFinal { 32 | return errors.New("bad auth type") 33 | } 34 | 35 | dst.Data = src[4:] 36 | 37 | return nil 38 | } 39 | 40 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 41 | func (src *AuthenticationSASLFinal) Encode(dst []byte) ([]byte, error) { 42 | dst, sp := beginMessage(dst, 'R') 43 | dst = pgio.AppendUint32(dst, AuthTypeSASLFinal) 44 | dst = append(dst, src.Data...) 45 | return finishMessage(dst, sp) 46 | } 47 | 48 | // MarshalJSON implements encoding/json.Unmarshaler. 49 | func (src AuthenticationSASLFinal) MarshalJSON() ([]byte, error) { 50 | return json.Marshal(struct { 51 | Type string 52 | Data string 53 | }{ 54 | Type: "AuthenticationSASLFinal", 55 | Data: string(src.Data), 56 | }) 57 | } 58 | 59 | // UnmarshalJSON implements encoding/json.Unmarshaler. 60 | func (dst *AuthenticationSASLFinal) UnmarshalJSON(data []byte) error { 61 | // Ignore null, like in the main JSON package. 62 | if string(data) == "null" { 63 | return nil 64 | } 65 | 66 | var msg struct { 67 | Data string 68 | } 69 | if err := json.Unmarshal(data, &msg); err != nil { 70 | return err 71 | } 72 | 73 | dst.Data = []byte(msg.Data) 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /pgproto3/authentication_sasl_continue.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/jackc/pgx/v5/internal/pgio" 9 | ) 10 | 11 | // AuthenticationSASLContinue is a message sent from the backend containing a SASL challenge. 12 | type AuthenticationSASLContinue struct { 13 | Data []byte 14 | } 15 | 16 | // Backend identifies this message as sendable by the PostgreSQL backend. 17 | func (*AuthenticationSASLContinue) Backend() {} 18 | 19 | // Backend identifies this message as an authentication response. 20 | func (*AuthenticationSASLContinue) AuthenticationResponse() {} 21 | 22 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 23 | // type identifier and 4 byte message length. 24 | func (dst *AuthenticationSASLContinue) Decode(src []byte) error { 25 | if len(src) < 4 { 26 | return errors.New("authentication message too short") 27 | } 28 | 29 | authType := binary.BigEndian.Uint32(src) 30 | 31 | if authType != AuthTypeSASLContinue { 32 | return errors.New("bad auth type") 33 | } 34 | 35 | dst.Data = src[4:] 36 | 37 | return nil 38 | } 39 | 40 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 41 | func (src *AuthenticationSASLContinue) Encode(dst []byte) ([]byte, error) { 42 | dst, sp := beginMessage(dst, 'R') 43 | dst = pgio.AppendUint32(dst, AuthTypeSASLContinue) 44 | dst = append(dst, src.Data...) 45 | return finishMessage(dst, sp) 46 | } 47 | 48 | // MarshalJSON implements encoding/json.Marshaler. 49 | func (src AuthenticationSASLContinue) MarshalJSON() ([]byte, error) { 50 | return json.Marshal(struct { 51 | Type string 52 | Data string 53 | }{ 54 | Type: "AuthenticationSASLContinue", 55 | Data: string(src.Data), 56 | }) 57 | } 58 | 59 | // UnmarshalJSON implements encoding/json.Unmarshaler. 60 | func (dst *AuthenticationSASLContinue) UnmarshalJSON(data []byte) error { 61 | // Ignore null, like in the main JSON package. 62 | if string(data) == "null" { 63 | return nil 64 | } 65 | 66 | var msg struct { 67 | Data string 68 | } 69 | if err := json.Unmarshal(data, &msg); err != nil { 70 | return err 71 | } 72 | 73 | dst.Data = []byte(msg.Data) 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /pgtype/float4_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype" 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func TestFloat4Codec(t *testing.T) { 12 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "float4", []pgxtest.ValueRoundTripTest{ 13 | {pgtype.Float4{Float32: -1, Valid: true}, new(pgtype.Float4), isExpectedEq(pgtype.Float4{Float32: -1, Valid: true})}, 14 | {pgtype.Float4{Float32: 0, Valid: true}, new(pgtype.Float4), isExpectedEq(pgtype.Float4{Float32: 0, Valid: true})}, 15 | {pgtype.Float4{Float32: 1, Valid: true}, new(pgtype.Float4), isExpectedEq(pgtype.Float4{Float32: 1, Valid: true})}, 16 | {float32(0.00001), new(float32), isExpectedEq(float32(0.00001))}, 17 | {float32(9999.99), new(float32), isExpectedEq(float32(9999.99))}, 18 | {pgtype.Float4{}, new(pgtype.Float4), isExpectedEq(pgtype.Float4{})}, 19 | {int64(1), new(int64), isExpectedEq(int64(1))}, 20 | {"1.23", new(string), isExpectedEq("1.23")}, 21 | {nil, new(*float32), isExpectedEq((*float32)(nil))}, 22 | }) 23 | } 24 | 25 | func TestFloat4MarshalJSON(t *testing.T) { 26 | successfulTests := []struct { 27 | source pgtype.Float4 28 | result string 29 | }{ 30 | {source: pgtype.Float4{Float32: 0}, result: "null"}, 31 | {source: pgtype.Float4{Float32: 1.23, Valid: true}, result: "1.23"}, 32 | } 33 | for i, tt := range successfulTests { 34 | r, err := tt.source.MarshalJSON() 35 | if err != nil { 36 | t.Errorf("%d: %v", i, err) 37 | } 38 | 39 | if string(r) != tt.result { 40 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r)) 41 | } 42 | } 43 | } 44 | 45 | func TestFloat4UnmarshalJSON(t *testing.T) { 46 | successfulTests := []struct { 47 | source string 48 | result pgtype.Float4 49 | }{ 50 | {source: "null", result: pgtype.Float4{Float32: 0}}, 51 | {source: "1.23", result: pgtype.Float4{Float32: 1.23, Valid: true}}, 52 | } 53 | for i, tt := range successfulTests { 54 | var r pgtype.Float4 55 | err := r.UnmarshalJSON([]byte(tt.source)) 56 | if err != nil { 57 | t.Errorf("%d: %v", i, err) 58 | } 59 | 60 | if r != tt.result { 61 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pgtype/float8_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype" 8 | "github.com/jackc/pgx/v5/pgxtest" 9 | ) 10 | 11 | func TestFloat8Codec(t *testing.T) { 12 | pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "float8", []pgxtest.ValueRoundTripTest{ 13 | {pgtype.Float8{Float64: -1, Valid: true}, new(pgtype.Float8), isExpectedEq(pgtype.Float8{Float64: -1, Valid: true})}, 14 | {pgtype.Float8{Float64: 0, Valid: true}, new(pgtype.Float8), isExpectedEq(pgtype.Float8{Float64: 0, Valid: true})}, 15 | {pgtype.Float8{Float64: 1, Valid: true}, new(pgtype.Float8), isExpectedEq(pgtype.Float8{Float64: 1, Valid: true})}, 16 | {float64(0.00001), new(float64), isExpectedEq(float64(0.00001))}, 17 | {float64(9999.99), new(float64), isExpectedEq(float64(9999.99))}, 18 | {pgtype.Float8{}, new(pgtype.Float8), isExpectedEq(pgtype.Float8{})}, 19 | {int64(1), new(int64), isExpectedEq(int64(1))}, 20 | {"1.23", new(string), isExpectedEq("1.23")}, 21 | {nil, new(*float64), isExpectedEq((*float64)(nil))}, 22 | }) 23 | } 24 | 25 | func TestFloat8MarshalJSON(t *testing.T) { 26 | successfulTests := []struct { 27 | source pgtype.Float8 28 | result string 29 | }{ 30 | {source: pgtype.Float8{Float64: 0}, result: "null"}, 31 | {source: pgtype.Float8{Float64: 1.23, Valid: true}, result: "1.23"}, 32 | } 33 | for i, tt := range successfulTests { 34 | r, err := tt.source.MarshalJSON() 35 | if err != nil { 36 | t.Errorf("%d: %v", i, err) 37 | } 38 | 39 | if string(r) != tt.result { 40 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r)) 41 | } 42 | } 43 | } 44 | 45 | func TestFloat8UnmarshalJSON(t *testing.T) { 46 | successfulTests := []struct { 47 | source string 48 | result pgtype.Float8 49 | }{ 50 | {source: "null", result: pgtype.Float8{Float64: 0}}, 51 | {source: "1.23", result: pgtype.Float8{Float64: 1.23, Valid: true}}, 52 | } 53 | for i, tt := range successfulTests { 54 | var r pgtype.Float8 55 | err := r.UnmarshalJSON([]byte(tt.source)) 56 | if err != nil { 57 | t.Errorf("%d: %v", i, err) 58 | } 59 | 60 | if r != tt.result { 61 | t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pgproto3/authentication_md5_password.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/jackc/pgx/v5/internal/pgio" 9 | ) 10 | 11 | // AuthenticationMD5Password is a message sent from the backend indicating that an MD5 hashed password is required. 12 | type AuthenticationMD5Password struct { 13 | Salt [4]byte 14 | } 15 | 16 | // Backend identifies this message as sendable by the PostgreSQL backend. 17 | func (*AuthenticationMD5Password) Backend() {} 18 | 19 | // Backend identifies this message as an authentication response. 20 | func (*AuthenticationMD5Password) AuthenticationResponse() {} 21 | 22 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 23 | // type identifier and 4 byte message length. 24 | func (dst *AuthenticationMD5Password) Decode(src []byte) error { 25 | if len(src) != 8 { 26 | return errors.New("bad authentication message size") 27 | } 28 | 29 | authType := binary.BigEndian.Uint32(src) 30 | 31 | if authType != AuthTypeMD5Password { 32 | return errors.New("bad auth type") 33 | } 34 | 35 | copy(dst.Salt[:], src[4:8]) 36 | 37 | return nil 38 | } 39 | 40 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 41 | func (src *AuthenticationMD5Password) Encode(dst []byte) ([]byte, error) { 42 | dst, sp := beginMessage(dst, 'R') 43 | dst = pgio.AppendUint32(dst, AuthTypeMD5Password) 44 | dst = append(dst, src.Salt[:]...) 45 | return finishMessage(dst, sp) 46 | } 47 | 48 | // MarshalJSON implements encoding/json.Marshaler. 49 | func (src AuthenticationMD5Password) MarshalJSON() ([]byte, error) { 50 | return json.Marshal(struct { 51 | Type string 52 | Salt [4]byte 53 | }{ 54 | Type: "AuthenticationMD5Password", 55 | Salt: src.Salt, 56 | }) 57 | } 58 | 59 | // UnmarshalJSON implements encoding/json.Unmarshaler. 60 | func (dst *AuthenticationMD5Password) UnmarshalJSON(data []byte) error { 61 | // Ignore null, like in the main JSON package. 62 | if string(data) == "null" { 63 | return nil 64 | } 65 | 66 | var msg struct { 67 | Type string 68 | Salt [4]byte 69 | } 70 | if err := json.Unmarshal(data, &msg); err != nil { 71 | return err 72 | } 73 | 74 | dst.Salt = msg.Salt 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /pgproto3/authentication_sasl.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | "errors" 8 | 9 | "github.com/jackc/pgx/v5/internal/pgio" 10 | ) 11 | 12 | // AuthenticationSASL is a message sent from the backend indicating that SASL authentication is required. 13 | type AuthenticationSASL struct { 14 | AuthMechanisms []string 15 | } 16 | 17 | // Backend identifies this message as sendable by the PostgreSQL backend. 18 | func (*AuthenticationSASL) Backend() {} 19 | 20 | // Backend identifies this message as an authentication response. 21 | func (*AuthenticationSASL) AuthenticationResponse() {} 22 | 23 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 24 | // type identifier and 4 byte message length. 25 | func (dst *AuthenticationSASL) Decode(src []byte) error { 26 | if len(src) < 4 { 27 | return errors.New("authentication message too short") 28 | } 29 | 30 | authType := binary.BigEndian.Uint32(src) 31 | 32 | if authType != AuthTypeSASL { 33 | return errors.New("bad auth type") 34 | } 35 | 36 | authMechanisms := src[4:] 37 | for len(authMechanisms) > 1 { 38 | idx := bytes.IndexByte(authMechanisms, 0) 39 | if idx == -1 { 40 | return &invalidMessageFormatErr{messageType: "AuthenticationSASL", details: "unterminated string"} 41 | } 42 | dst.AuthMechanisms = append(dst.AuthMechanisms, string(authMechanisms[:idx])) 43 | authMechanisms = authMechanisms[idx+1:] 44 | } 45 | 46 | return nil 47 | } 48 | 49 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 50 | func (src *AuthenticationSASL) Encode(dst []byte) ([]byte, error) { 51 | dst, sp := beginMessage(dst, 'R') 52 | dst = pgio.AppendUint32(dst, AuthTypeSASL) 53 | 54 | for _, s := range src.AuthMechanisms { 55 | dst = append(dst, []byte(s)...) 56 | dst = append(dst, 0) 57 | } 58 | dst = append(dst, 0) 59 | 60 | return finishMessage(dst, sp) 61 | } 62 | 63 | // MarshalJSON implements encoding/json.Marshaler. 64 | func (src AuthenticationSASL) MarshalJSON() ([]byte, error) { 65 | return json.Marshal(struct { 66 | Type string 67 | AuthMechanisms []string 68 | }{ 69 | Type: "AuthenticationSASL", 70 | AuthMechanisms: src.AuthMechanisms, 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /pgxpool/conn_test.go: -------------------------------------------------------------------------------- 1 | package pgxpool_test 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | "github.com/jackc/pgx/v5/pgxpool" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestConnExec(t *testing.T) { 14 | t.Parallel() 15 | 16 | ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 17 | defer cancel() 18 | 19 | pool, err := pgxpool.New(ctx, os.Getenv("PGX_TEST_DATABASE")) 20 | require.NoError(t, err) 21 | defer pool.Close() 22 | 23 | c, err := pool.Acquire(ctx) 24 | require.NoError(t, err) 25 | defer c.Release() 26 | 27 | testExec(t, ctx, c) 28 | } 29 | 30 | func TestConnQuery(t *testing.T) { 31 | t.Parallel() 32 | 33 | ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 34 | defer cancel() 35 | 36 | pool, err := pgxpool.New(ctx, os.Getenv("PGX_TEST_DATABASE")) 37 | require.NoError(t, err) 38 | defer pool.Close() 39 | 40 | c, err := pool.Acquire(ctx) 41 | require.NoError(t, err) 42 | defer c.Release() 43 | 44 | testQuery(t, ctx, c) 45 | } 46 | 47 | func TestConnQueryRow(t *testing.T) { 48 | t.Parallel() 49 | 50 | ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 51 | defer cancel() 52 | 53 | pool, err := pgxpool.New(ctx, os.Getenv("PGX_TEST_DATABASE")) 54 | require.NoError(t, err) 55 | defer pool.Close() 56 | 57 | c, err := pool.Acquire(ctx) 58 | require.NoError(t, err) 59 | defer c.Release() 60 | 61 | testQueryRow(t, ctx, c) 62 | } 63 | 64 | func TestConnSendBatch(t *testing.T) { 65 | t.Parallel() 66 | 67 | ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 68 | defer cancel() 69 | 70 | pool, err := pgxpool.New(ctx, os.Getenv("PGX_TEST_DATABASE")) 71 | require.NoError(t, err) 72 | defer pool.Close() 73 | 74 | c, err := pool.Acquire(ctx) 75 | require.NoError(t, err) 76 | defer c.Release() 77 | 78 | testSendBatch(t, ctx, c) 79 | } 80 | 81 | func TestConnCopyFrom(t *testing.T) { 82 | t.Parallel() 83 | 84 | ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 85 | defer cancel() 86 | 87 | pool, err := pgxpool.New(ctx, os.Getenv("PGX_TEST_DATABASE")) 88 | require.NoError(t, err) 89 | defer pool.Close() 90 | 91 | c, err := pool.Acquire(ctx) 92 | require.NoError(t, err) 93 | defer c.Release() 94 | 95 | testCopyFrom(t, ctx, c) 96 | } 97 | -------------------------------------------------------------------------------- /pgxpool/tx_test.go: -------------------------------------------------------------------------------- 1 | package pgxpool_test 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | "github.com/jackc/pgx/v5/pgxpool" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestTxExec(t *testing.T) { 14 | t.Parallel() 15 | 16 | ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 17 | defer cancel() 18 | 19 | pool, err := pgxpool.New(ctx, os.Getenv("PGX_TEST_DATABASE")) 20 | require.NoError(t, err) 21 | defer pool.Close() 22 | 23 | tx, err := pool.Begin(ctx) 24 | require.NoError(t, err) 25 | defer tx.Rollback(ctx) 26 | 27 | testExec(t, ctx, tx) 28 | } 29 | 30 | func TestTxQuery(t *testing.T) { 31 | t.Parallel() 32 | 33 | ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 34 | defer cancel() 35 | 36 | pool, err := pgxpool.New(ctx, os.Getenv("PGX_TEST_DATABASE")) 37 | require.NoError(t, err) 38 | defer pool.Close() 39 | 40 | tx, err := pool.Begin(ctx) 41 | require.NoError(t, err) 42 | defer tx.Rollback(ctx) 43 | 44 | testQuery(t, ctx, tx) 45 | } 46 | 47 | func TestTxQueryRow(t *testing.T) { 48 | t.Parallel() 49 | 50 | ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 51 | defer cancel() 52 | 53 | pool, err := pgxpool.New(ctx, os.Getenv("PGX_TEST_DATABASE")) 54 | require.NoError(t, err) 55 | defer pool.Close() 56 | 57 | tx, err := pool.Begin(ctx) 58 | require.NoError(t, err) 59 | defer tx.Rollback(ctx) 60 | 61 | testQueryRow(t, ctx, tx) 62 | } 63 | 64 | func TestTxSendBatch(t *testing.T) { 65 | t.Parallel() 66 | 67 | ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 68 | defer cancel() 69 | 70 | pool, err := pgxpool.New(ctx, os.Getenv("PGX_TEST_DATABASE")) 71 | require.NoError(t, err) 72 | defer pool.Close() 73 | 74 | tx, err := pool.Begin(ctx) 75 | require.NoError(t, err) 76 | defer tx.Rollback(ctx) 77 | 78 | testSendBatch(t, ctx, tx) 79 | } 80 | 81 | func TestTxCopyFrom(t *testing.T) { 82 | t.Parallel() 83 | 84 | ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 85 | defer cancel() 86 | 87 | pool, err := pgxpool.New(ctx, os.Getenv("PGX_TEST_DATABASE")) 88 | require.NoError(t, err) 89 | defer pool.Close() 90 | 91 | tx, err := pool.Begin(ctx) 92 | require.NoError(t, err) 93 | defer tx.Rollback(ctx) 94 | 95 | testCopyFrom(t, ctx, tx) 96 | } 97 | -------------------------------------------------------------------------------- /pgbouncer_test.go: -------------------------------------------------------------------------------- 1 | package pgx_test 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/jackc/pgx/v5" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestPgbouncerStatementCacheDescribe(t *testing.T) { 14 | connString := os.Getenv("PGX_TEST_PGBOUNCER_CONN_STRING") 15 | if connString == "" { 16 | t.Skipf("Skipping due to missing environment variable %v", "PGX_TEST_PGBOUNCER_CONN_STRING") 17 | } 18 | 19 | config := mustParseConfig(t, connString) 20 | config.DefaultQueryExecMode = pgx.QueryExecModeCacheDescribe 21 | config.DescriptionCacheCapacity = 1024 22 | 23 | testPgbouncer(t, config, 10, 100) 24 | } 25 | 26 | func TestPgbouncerSimpleProtocol(t *testing.T) { 27 | connString := os.Getenv("PGX_TEST_PGBOUNCER_CONN_STRING") 28 | if connString == "" { 29 | t.Skipf("Skipping due to missing environment variable %v", "PGX_TEST_PGBOUNCER_CONN_STRING") 30 | } 31 | 32 | config := mustParseConfig(t, connString) 33 | config.DefaultQueryExecMode = pgx.QueryExecModeSimpleProtocol 34 | 35 | testPgbouncer(t, config, 10, 100) 36 | } 37 | 38 | func testPgbouncer(t *testing.T, config *pgx.ConnConfig, workers, iterations int) { 39 | doneChan := make(chan struct{}) 40 | 41 | for range workers { 42 | go func() { 43 | defer func() { doneChan <- struct{}{} }() 44 | conn, err := pgx.ConnectConfig(context.Background(), config) 45 | require.Nil(t, err) 46 | defer closeConn(t, conn) 47 | 48 | for range iterations { 49 | var i32 int32 50 | var i64 int64 51 | var f32 float32 52 | var s string 53 | var s2 string 54 | err = conn.QueryRow(context.Background(), "select 1::int4, 2::int8, 3::float4, 'hi'::text").Scan(&i32, &i64, &f32, &s) 55 | require.NoError(t, err) 56 | assert.Equal(t, int32(1), i32) 57 | assert.Equal(t, int64(2), i64) 58 | assert.Equal(t, float32(3), f32) 59 | assert.Equal(t, "hi", s) 60 | 61 | err = conn.QueryRow(context.Background(), "select 1::int8, 2::float4, 'bye'::text, 4::int4, 'whatever'::text").Scan(&i64, &f32, &s, &i32, &s2) 62 | require.NoError(t, err) 63 | assert.Equal(t, int64(1), i64) 64 | assert.Equal(t, float32(2), f32) 65 | assert.Equal(t, "bye", s) 66 | assert.Equal(t, int32(4), i32) 67 | assert.Equal(t, "whatever", s2) 68 | } 69 | }() 70 | } 71 | 72 | for range workers { 73 | <-doneChan 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /pipeline_test.go: -------------------------------------------------------------------------------- 1 | package pgx_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5" 8 | "github.com/jackc/pgx/v5/pgconn" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestPipelineWithoutPreparedOrDescribedStatements(t *testing.T) { 13 | t.Parallel() 14 | 15 | defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 16 | pipeline := conn.PgConn().StartPipeline(ctx) 17 | 18 | eqb := pgx.ExtendedQueryBuilder{} 19 | 20 | err := eqb.Build(conn.TypeMap(), nil, []any{1, 2}) 21 | require.NoError(t, err) 22 | pipeline.SendQueryParams(`select $1::bigint + $2::bigint`, eqb.ParamValues, nil, eqb.ParamFormats, eqb.ResultFormats) 23 | 24 | err = eqb.Build(conn.TypeMap(), nil, []any{3, 4, 5}) 25 | require.NoError(t, err) 26 | pipeline.SendQueryParams(`select $1::bigint + $2::bigint + $3::bigint`, eqb.ParamValues, nil, eqb.ParamFormats, eqb.ResultFormats) 27 | 28 | err = pipeline.Sync() 29 | require.NoError(t, err) 30 | 31 | results, err := pipeline.GetResults() 32 | require.NoError(t, err) 33 | rr, ok := results.(*pgconn.ResultReader) 34 | require.True(t, ok) 35 | rows := pgx.RowsFromResultReader(conn.TypeMap(), rr) 36 | 37 | rowCount := 0 38 | var n int64 39 | for rows.Next() { 40 | err = rows.Scan(&n) 41 | require.NoError(t, err) 42 | rowCount++ 43 | } 44 | require.NoError(t, rows.Err()) 45 | require.Equal(t, 1, rowCount) 46 | require.Equal(t, "SELECT 1", rows.CommandTag().String()) 47 | require.EqualValues(t, 3, n) 48 | 49 | results, err = pipeline.GetResults() 50 | require.NoError(t, err) 51 | rr, ok = results.(*pgconn.ResultReader) 52 | require.True(t, ok) 53 | rows = pgx.RowsFromResultReader(conn.TypeMap(), rr) 54 | 55 | rowCount = 0 56 | n = 0 57 | for rows.Next() { 58 | err = rows.Scan(&n) 59 | require.NoError(t, err) 60 | rowCount++ 61 | } 62 | require.NoError(t, rows.Err()) 63 | require.Equal(t, 1, rowCount) 64 | require.Equal(t, "SELECT 1", rows.CommandTag().String()) 65 | require.EqualValues(t, 12, n) 66 | 67 | results, err = pipeline.GetResults() 68 | require.NoError(t, err) 69 | _, ok = results.(*pgconn.PipelineSync) 70 | require.True(t, ok) 71 | 72 | results, err = pipeline.GetResults() 73 | require.NoError(t, err) 74 | require.Nil(t, results) 75 | 76 | err = pipeline.Close() 77 | require.NoError(t, err) 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /pgconn/ctxwatch/context_watcher.go: -------------------------------------------------------------------------------- 1 | package ctxwatch 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | ) 7 | 8 | // ContextWatcher watches a context and performs an action when the context is canceled. It can watch one context at a 9 | // time. 10 | type ContextWatcher struct { 11 | handler Handler 12 | unwatchChan chan struct{} 13 | 14 | lock sync.Mutex 15 | watchInProgress bool 16 | onCancelWasCalled bool 17 | } 18 | 19 | // NewContextWatcher returns a ContextWatcher. onCancel will be called when a watched context is canceled. 20 | // OnUnwatchAfterCancel will be called when Unwatch is called and the watched context had already been canceled and 21 | // onCancel called. 22 | func NewContextWatcher(handler Handler) *ContextWatcher { 23 | cw := &ContextWatcher{ 24 | handler: handler, 25 | unwatchChan: make(chan struct{}), 26 | } 27 | 28 | return cw 29 | } 30 | 31 | // Watch starts watching ctx. If ctx is canceled then the onCancel function passed to NewContextWatcher will be called. 32 | func (cw *ContextWatcher) Watch(ctx context.Context) { 33 | cw.lock.Lock() 34 | defer cw.lock.Unlock() 35 | 36 | if cw.watchInProgress { 37 | panic("Watch already in progress") 38 | } 39 | 40 | cw.onCancelWasCalled = false 41 | 42 | if ctx.Done() != nil { 43 | cw.watchInProgress = true 44 | go func() { 45 | select { 46 | case <-ctx.Done(): 47 | cw.handler.HandleCancel(ctx) 48 | cw.onCancelWasCalled = true 49 | <-cw.unwatchChan 50 | case <-cw.unwatchChan: 51 | } 52 | }() 53 | } else { 54 | cw.watchInProgress = false 55 | } 56 | } 57 | 58 | // Unwatch stops watching the previously watched context. If the onCancel function passed to NewContextWatcher was 59 | // called then onUnwatchAfterCancel will also be called. 60 | func (cw *ContextWatcher) Unwatch() { 61 | cw.lock.Lock() 62 | defer cw.lock.Unlock() 63 | 64 | if cw.watchInProgress { 65 | cw.unwatchChan <- struct{}{} 66 | if cw.onCancelWasCalled { 67 | cw.handler.HandleUnwatchAfterCancel() 68 | } 69 | cw.watchInProgress = false 70 | } 71 | } 72 | 73 | type Handler interface { 74 | // HandleCancel is called when the context that a ContextWatcher is currently watching is canceled. canceledCtx is the 75 | // context that was canceled. 76 | HandleCancel(canceledCtx context.Context) 77 | 78 | // HandleUnwatchAfterCancel is called when a ContextWatcher that called HandleCancel on this Handler is unwatched. 79 | HandleUnwatchAfterCancel() 80 | } 81 | -------------------------------------------------------------------------------- /pgproto3/parse.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | "errors" 8 | "math" 9 | 10 | "github.com/jackc/pgx/v5/internal/pgio" 11 | ) 12 | 13 | type Parse struct { 14 | Name string 15 | Query string 16 | ParameterOIDs []uint32 17 | } 18 | 19 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 20 | func (*Parse) Frontend() {} 21 | 22 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 23 | // type identifier and 4 byte message length. 24 | func (dst *Parse) Decode(src []byte) error { 25 | *dst = Parse{} 26 | 27 | buf := bytes.NewBuffer(src) 28 | 29 | b, err := buf.ReadBytes(0) 30 | if err != nil { 31 | return err 32 | } 33 | dst.Name = string(b[:len(b)-1]) 34 | 35 | b, err = buf.ReadBytes(0) 36 | if err != nil { 37 | return err 38 | } 39 | dst.Query = string(b[:len(b)-1]) 40 | 41 | if buf.Len() < 2 { 42 | return &invalidMessageFormatErr{messageType: "Parse"} 43 | } 44 | parameterOIDCount := int(binary.BigEndian.Uint16(buf.Next(2))) 45 | 46 | for range parameterOIDCount { 47 | if buf.Len() < 4 { 48 | return &invalidMessageFormatErr{messageType: "Parse"} 49 | } 50 | dst.ParameterOIDs = append(dst.ParameterOIDs, binary.BigEndian.Uint32(buf.Next(4))) 51 | } 52 | 53 | return nil 54 | } 55 | 56 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 57 | func (src *Parse) Encode(dst []byte) ([]byte, error) { 58 | dst, sp := beginMessage(dst, 'P') 59 | 60 | dst = append(dst, src.Name...) 61 | dst = append(dst, 0) 62 | dst = append(dst, src.Query...) 63 | dst = append(dst, 0) 64 | 65 | if len(src.ParameterOIDs) > math.MaxUint16 { 66 | return nil, errors.New("too many parameter oids") 67 | } 68 | dst = pgio.AppendUint16(dst, uint16(len(src.ParameterOIDs))) 69 | for _, oid := range src.ParameterOIDs { 70 | dst = pgio.AppendUint32(dst, oid) 71 | } 72 | 73 | return finishMessage(dst, sp) 74 | } 75 | 76 | // MarshalJSON implements encoding/json.Marshaler. 77 | func (src Parse) MarshalJSON() ([]byte, error) { 78 | return json.Marshal(struct { 79 | Type string 80 | Name string 81 | Query string 82 | ParameterOIDs []uint32 83 | }{ 84 | Type: "Parse", 85 | Name: src.Name, 86 | Query: src.Query, 87 | ParameterOIDs: src.ParameterOIDs, 88 | }) 89 | } 90 | -------------------------------------------------------------------------------- /internal/pgio/write_test.go: -------------------------------------------------------------------------------- 1 | package pgio 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestAppendUint16NilBuf(t *testing.T) { 9 | buf := AppendUint16(nil, 1) 10 | if !reflect.DeepEqual(buf, []byte{0, 1}) { 11 | t.Errorf("AppendUint16(nil, 1) => %v, want %v", buf, []byte{0, 1}) 12 | } 13 | } 14 | 15 | func TestAppendUint16EmptyBuf(t *testing.T) { 16 | buf := []byte{} 17 | buf = AppendUint16(buf, 1) 18 | if !reflect.DeepEqual(buf, []byte{0, 1}) { 19 | t.Errorf("AppendUint16(nil, 1) => %v, want %v", buf, []byte{0, 1}) 20 | } 21 | } 22 | 23 | func TestAppendUint16BufWithCapacityDoesNotAllocate(t *testing.T) { 24 | buf := make([]byte, 0, 4) 25 | AppendUint16(buf, 1) 26 | buf = buf[0:2] 27 | if !reflect.DeepEqual(buf, []byte{0, 1}) { 28 | t.Errorf("AppendUint16(nil, 1) => %v, want %v", buf, []byte{0, 1}) 29 | } 30 | } 31 | 32 | func TestAppendUint32NilBuf(t *testing.T) { 33 | buf := AppendUint32(nil, 1) 34 | if !reflect.DeepEqual(buf, []byte{0, 0, 0, 1}) { 35 | t.Errorf("AppendUint32(nil, 1) => %v, want %v", buf, []byte{0, 0, 0, 1}) 36 | } 37 | } 38 | 39 | func TestAppendUint32EmptyBuf(t *testing.T) { 40 | buf := []byte{} 41 | buf = AppendUint32(buf, 1) 42 | if !reflect.DeepEqual(buf, []byte{0, 0, 0, 1}) { 43 | t.Errorf("AppendUint32(nil, 1) => %v, want %v", buf, []byte{0, 0, 0, 1}) 44 | } 45 | } 46 | 47 | func TestAppendUint32BufWithCapacityDoesNotAllocate(t *testing.T) { 48 | buf := make([]byte, 0, 4) 49 | AppendUint32(buf, 1) 50 | buf = buf[0:4] 51 | if !reflect.DeepEqual(buf, []byte{0, 0, 0, 1}) { 52 | t.Errorf("AppendUint32(nil, 1) => %v, want %v", buf, []byte{0, 0, 0, 1}) 53 | } 54 | } 55 | 56 | func TestAppendUint64NilBuf(t *testing.T) { 57 | buf := AppendUint64(nil, 1) 58 | if !reflect.DeepEqual(buf, []byte{0, 0, 0, 0, 0, 0, 0, 1}) { 59 | t.Errorf("AppendUint64(nil, 1) => %v, want %v", buf, []byte{0, 0, 0, 0, 0, 0, 0, 1}) 60 | } 61 | } 62 | 63 | func TestAppendUint64EmptyBuf(t *testing.T) { 64 | buf := []byte{} 65 | buf = AppendUint64(buf, 1) 66 | if !reflect.DeepEqual(buf, []byte{0, 0, 0, 0, 0, 0, 0, 1}) { 67 | t.Errorf("AppendUint64(nil, 1) => %v, want %v", buf, []byte{0, 0, 0, 0, 0, 0, 0, 1}) 68 | } 69 | } 70 | 71 | func TestAppendUint64BufWithCapacityDoesNotAllocate(t *testing.T) { 72 | buf := make([]byte, 0, 8) 73 | AppendUint64(buf, 1) 74 | buf = buf[0:8] 75 | if !reflect.DeepEqual(buf, []byte{0, 0, 0, 0, 0, 0, 0, 1}) { 76 | t.Errorf("AppendUint64(nil, 1) => %v, want %v", buf, []byte{0, 0, 0, 0, 0, 0, 0, 1}) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /pgproto3/sasl_initial_response.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "encoding/json" 7 | "errors" 8 | 9 | "github.com/jackc/pgx/v5/internal/pgio" 10 | ) 11 | 12 | type SASLInitialResponse struct { 13 | AuthMechanism string 14 | Data []byte 15 | } 16 | 17 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 18 | func (*SASLInitialResponse) Frontend() {} 19 | 20 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 21 | // type identifier and 4 byte message length. 22 | func (dst *SASLInitialResponse) Decode(src []byte) error { 23 | *dst = SASLInitialResponse{} 24 | 25 | rp := 0 26 | 27 | idx := bytes.IndexByte(src, 0) 28 | if idx < 0 { 29 | return errors.New("invalid SASLInitialResponse") 30 | } 31 | 32 | dst.AuthMechanism = string(src[rp:idx]) 33 | rp = idx + 1 34 | 35 | rp += 4 // The rest of the message is data so we can just skip the size 36 | dst.Data = src[rp:] 37 | 38 | return nil 39 | } 40 | 41 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 42 | func (src *SASLInitialResponse) Encode(dst []byte) ([]byte, error) { 43 | dst, sp := beginMessage(dst, 'p') 44 | 45 | dst = append(dst, []byte(src.AuthMechanism)...) 46 | dst = append(dst, 0) 47 | 48 | dst = pgio.AppendInt32(dst, int32(len(src.Data))) 49 | dst = append(dst, src.Data...) 50 | 51 | return finishMessage(dst, sp) 52 | } 53 | 54 | // MarshalJSON implements encoding/json.Marshaler. 55 | func (src SASLInitialResponse) MarshalJSON() ([]byte, error) { 56 | return json.Marshal(struct { 57 | Type string 58 | AuthMechanism string 59 | Data string 60 | }{ 61 | Type: "SASLInitialResponse", 62 | AuthMechanism: src.AuthMechanism, 63 | Data: string(src.Data), 64 | }) 65 | } 66 | 67 | // UnmarshalJSON implements encoding/json.Unmarshaler. 68 | func (dst *SASLInitialResponse) UnmarshalJSON(data []byte) error { 69 | // Ignore null, like in the main JSON package. 70 | if string(data) == "null" { 71 | return nil 72 | } 73 | 74 | var msg struct { 75 | AuthMechanism string 76 | Data string 77 | } 78 | if err := json.Unmarshal(data, &msg); err != nil { 79 | return err 80 | } 81 | dst.AuthMechanism = msg.AuthMechanism 82 | if msg.Data != "" { 83 | decoded, err := hex.DecodeString(msg.Data) 84 | if err != nil { 85 | return err 86 | } 87 | dst.Data = decoded 88 | } 89 | return nil 90 | } 91 | -------------------------------------------------------------------------------- /pgtype/integration_benchmark_test.go.erb: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/jackc/pgx/v5/pgtype/testutil" 8 | "github.com/jackc/pgx/v5" 9 | ) 10 | 11 | <% 12 | [ 13 | ["int4", ["int16", "int32", "int64", "uint64", "pgtype.Int4"], [[1, 1], [1, 10], [10, 1], [100, 10]]], 14 | ["numeric", ["int64", "float64", "pgtype.Numeric"], [[1, 1], [1, 10], [10, 1], [100, 10]]], 15 | ].each do |pg_type, go_types, rows_columns| 16 | %> 17 | <% go_types.each do |go_type| %> 18 | <% rows_columns.each do |rows, columns| %> 19 | <% [["Text", "pgx.TextFormatCode"], ["Binary", "pgx.BinaryFormatCode"]].each do |format_name, format_code| %> 20 | func BenchmarkQuery<%= format_name %>FormatDecode_PG_<%= pg_type %>_to_Go_<%= go_type.gsub(/\W/, "_") %>_<%= rows %>_rows_<%= columns %>_columns(b *testing.B) { 21 | defaultConnTestRunner.RunTest(context.Background(), b, func(ctx context.Context, _ testing.TB, conn *pgx.Conn) { 22 | b.ResetTimer() 23 | var v [<%= columns %>]<%= go_type %> 24 | for i := 0; i < b.N; i++ { 25 | rows, _ := conn.Query( 26 | ctx, 27 | `select <% columns.times do |col_idx| %><% if col_idx != 0 %>, <% end %>n::<%= pg_type %> + <%= col_idx%><% end %> from generate_series(1, <%= rows %>) n`, 28 | pgx.QueryResultFormats{<%= format_code %>}, 29 | ) 30 | _, err := pgx.ForEachRow(rows, []any{<% columns.times do |col_idx| %><% if col_idx != 0 %>, <% end %>&v[<%= col_idx%>]<% end %>}, func() error { return nil }) 31 | if err != nil { 32 | b.Fatal(err) 33 | } 34 | } 35 | }) 36 | } 37 | <% end %> 38 | <% end %> 39 | <% end %> 40 | <% end %> 41 | 42 | <% [10, 100, 1000].each do |array_size| %> 43 | <% [["Text", "pgx.TextFormatCode"], ["Binary", "pgx.BinaryFormatCode"]].each do |format_name, format_code| %> 44 | func BenchmarkQuery<%= format_name %>FormatDecode_PG_Int4Array_With_Go_Int4Array_<%= array_size %>(b *testing.B) { 45 | defaultConnTestRunner.RunTest(context.Background(), b, func(ctx context.Context, _ testing.TB, conn *pgx.Conn) { 46 | b.ResetTimer() 47 | var v []int32 48 | for i := 0; i < b.N; i++ { 49 | rows, _ := conn.Query( 50 | ctx, 51 | `select array_agg(n) from generate_series(1, <%= array_size %>) n`, 52 | pgx.QueryResultFormats{<%= format_code %>}, 53 | ) 54 | _, err := pgx.ForEachRow(rows, []any{&v}, func() error { return nil }) 55 | if err != nil { 56 | b.Fatal(err) 57 | } 58 | } 59 | }) 60 | } 61 | <% end %> 62 | <% end %> 63 | -------------------------------------------------------------------------------- /ci/setup_test.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eux 3 | 4 | if [[ "${PGVERSION-}" =~ ^[0-9.]+$ ]] 5 | then 6 | sudo apt-get remove -y --purge postgresql libpq-dev libpq5 postgresql-client-common postgresql-common 7 | sudo rm -rf /var/lib/postgresql 8 | wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - 9 | sudo sh -c "echo deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main $PGVERSION >> /etc/apt/sources.list.d/postgresql.list" 10 | sudo apt-get update -qq 11 | sudo apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::="--force-confnew" install postgresql-$PGVERSION postgresql-server-dev-$PGVERSION postgresql-contrib-$PGVERSION 12 | 13 | sudo cp testsetup/pg_hba.conf /etc/postgresql/$PGVERSION/main/pg_hba.conf 14 | sudo sh -c "echo \"listen_addresses = '127.0.0.1'\" >> /etc/postgresql/$PGVERSION/main/postgresql.conf" 15 | sudo sh -c "cat testsetup/postgresql_ssl.conf >> /etc/postgresql/$PGVERSION/main/postgresql.conf" 16 | 17 | cd testsetup 18 | 19 | # Generate CA, server, and encrypted client certificates. 20 | go run generate_certs.go 21 | 22 | # Copy certificates to server directory and set permissions. 23 | sudo cp ca.pem /var/lib/postgresql/$PGVERSION/main/root.crt 24 | sudo chown postgres:postgres /var/lib/postgresql/$PGVERSION/main/root.crt 25 | sudo cp localhost.key /var/lib/postgresql/$PGVERSION/main/server.key 26 | sudo chown postgres:postgres /var/lib/postgresql/$PGVERSION/main/server.key 27 | sudo chmod 600 /var/lib/postgresql/$PGVERSION/main/server.key 28 | sudo cp localhost.crt /var/lib/postgresql/$PGVERSION/main/server.crt 29 | sudo chown postgres:postgres /var/lib/postgresql/$PGVERSION/main/server.crt 30 | 31 | cp ca.pem /tmp 32 | cp pgx_sslcert.key /tmp 33 | cp pgx_sslcert.crt /tmp 34 | 35 | cd .. 36 | 37 | sudo /etc/init.d/postgresql restart 38 | 39 | createdb -U postgres pgx_test 40 | psql -U postgres -f testsetup/postgresql_setup.sql pgx_test 41 | fi 42 | 43 | if [[ "${PGVERSION-}" =~ ^cockroach ]] 44 | then 45 | wget -qO- https://binaries.cockroachdb.com/cockroach-v24.3.3.linux-amd64.tgz | tar xvz 46 | sudo mv cockroach-v24.3.3.linux-amd64/cockroach /usr/local/bin/ 47 | cockroach start-single-node --insecure --background --listen-addr=localhost 48 | cockroach sql --insecure -e 'create database pgx_test' 49 | fi 50 | 51 | if [ "${CRATEVERSION-}" != "" ] 52 | then 53 | docker run \ 54 | -p "6543:5432" \ 55 | -d \ 56 | crate:"$CRATEVERSION" \ 57 | crate \ 58 | -Cnetwork.host=0.0.0.0 \ 59 | -Ctransport.host=localhost \ 60 | -Clicense.enterprise=false 61 | fi 62 | -------------------------------------------------------------------------------- /examples/url_shortener/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "log" 7 | "net/http" 8 | "os" 9 | 10 | "github.com/jackc/pgx/v5" 11 | "github.com/jackc/pgx/v5/pgxpool" 12 | ) 13 | 14 | var db *pgxpool.Pool 15 | 16 | func getUrlHandler(w http.ResponseWriter, req *http.Request) { 17 | var url string 18 | err := db.QueryRow(context.Background(), "select url from shortened_urls where id=$1", req.URL.Path).Scan(&url) 19 | switch err { 20 | case nil: 21 | http.Redirect(w, req, url, http.StatusSeeOther) 22 | case pgx.ErrNoRows: 23 | http.NotFound(w, req) 24 | default: 25 | http.Error(w, "Internal server error", http.StatusInternalServerError) 26 | } 27 | } 28 | 29 | func putUrlHandler(w http.ResponseWriter, req *http.Request) { 30 | id := req.URL.Path 31 | var url string 32 | if body, err := io.ReadAll(req.Body); err == nil { 33 | url = string(body) 34 | } else { 35 | http.Error(w, "Internal server error", http.StatusInternalServerError) 36 | return 37 | } 38 | 39 | if _, err := db.Exec(context.Background(), `insert into shortened_urls(id, url) values ($1, $2) 40 | on conflict (id) do update set url=excluded.url`, id, url); err == nil { 41 | w.WriteHeader(http.StatusOK) 42 | } else { 43 | http.Error(w, "Internal server error", http.StatusInternalServerError) 44 | } 45 | } 46 | 47 | func deleteUrlHandler(w http.ResponseWriter, req *http.Request) { 48 | if _, err := db.Exec(context.Background(), "delete from shortened_urls where id=$1", req.URL.Path); err == nil { 49 | w.WriteHeader(http.StatusOK) 50 | } else { 51 | http.Error(w, "Internal server error", http.StatusInternalServerError) 52 | } 53 | } 54 | 55 | func urlHandler(w http.ResponseWriter, req *http.Request) { 56 | switch req.Method { 57 | case "GET": 58 | getUrlHandler(w, req) 59 | 60 | case "PUT": 61 | putUrlHandler(w, req) 62 | 63 | case "DELETE": 64 | deleteUrlHandler(w, req) 65 | 66 | default: 67 | w.Header().Add("Allow", "GET, PUT, DELETE") 68 | w.WriteHeader(http.StatusMethodNotAllowed) 69 | } 70 | } 71 | 72 | func main() { 73 | poolConfig, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL")) 74 | if err != nil { 75 | log.Fatalln("Unable to parse DATABASE_URL:", err) 76 | } 77 | 78 | db, err = pgxpool.NewWithConfig(context.Background(), poolConfig) 79 | if err != nil { 80 | log.Fatalln("Unable to create connection pool:", err) 81 | } 82 | 83 | http.HandleFunc("/", urlHandler) 84 | 85 | log.Println("Starting URL shortener on localhost:8080") 86 | err = http.ListenAndServe("localhost:8080", nil) 87 | if err != nil { 88 | log.Fatalln("Unable to start web server:", err) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /pgtype/example_child_records_test.go: -------------------------------------------------------------------------------- 1 | package pgtype_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/jackc/pgx/v5" 10 | ) 11 | 12 | type Player struct { 13 | Name string 14 | Position string 15 | } 16 | 17 | type Team struct { 18 | Name string 19 | Players []Player 20 | } 21 | 22 | // This example uses a single query to return parent and child records. 23 | func Example_childRecords() { 24 | ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 25 | defer cancel() 26 | 27 | conn, err := pgx.Connect(ctx, os.Getenv("PGX_TEST_DATABASE")) 28 | if err != nil { 29 | fmt.Printf("Unable to establish connection: %v", err) 30 | return 31 | } 32 | 33 | if conn.PgConn().ParameterStatus("crdb_version") != "" { 34 | // Skip test / example when running on CockroachDB. Since an example can't be skipped fake success instead. 35 | fmt.Println(`Alpha 36 | Adam: wing 37 | Bill: halfback 38 | Charlie: fullback 39 | Beta 40 | Don: halfback 41 | Edgar: halfback 42 | Frank: fullback`) 43 | return 44 | } 45 | 46 | // Setup example schema and data. 47 | _, err = conn.Exec(ctx, ` 48 | create temporary table teams ( 49 | name text primary key 50 | ); 51 | 52 | create temporary table players ( 53 | name text primary key, 54 | team_name text, 55 | position text 56 | ); 57 | 58 | insert into teams (name) values 59 | ('Alpha'), 60 | ('Beta'); 61 | 62 | insert into players (name, team_name, position) values 63 | ('Adam', 'Alpha', 'wing'), 64 | ('Bill', 'Alpha', 'halfback'), 65 | ('Charlie', 'Alpha', 'fullback'), 66 | ('Don', 'Beta', 'halfback'), 67 | ('Edgar', 'Beta', 'halfback'), 68 | ('Frank', 'Beta', 'fullback') 69 | `) 70 | if err != nil { 71 | fmt.Printf("Unable to setup example schema and data: %v", err) 72 | return 73 | } 74 | 75 | rows, _ := conn.Query(ctx, ` 76 | select t.name, 77 | (select array_agg(row(p.name, position) order by p.name) from players p where p.team_name = t.name) 78 | from teams t 79 | order by t.name 80 | `) 81 | teams, err := pgx.CollectRows(rows, pgx.RowToStructByPos[Team]) 82 | if err != nil { 83 | fmt.Printf("CollectRows error: %v", err) 84 | return 85 | } 86 | 87 | for _, team := range teams { 88 | fmt.Println(team.Name) 89 | for _, player := range team.Players { 90 | fmt.Printf(" %s: %s\n", player.Name, player.Position) 91 | } 92 | } 93 | 94 | // Output: 95 | // Alpha 96 | // Adam: wing 97 | // Bill: halfback 98 | // Charlie: fullback 99 | // Beta 100 | // Don: halfback 101 | // Edgar: halfback 102 | // Frank: fullback 103 | } 104 | -------------------------------------------------------------------------------- /pgproto3/function_call_response.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/hex" 6 | "encoding/json" 7 | 8 | "github.com/jackc/pgx/v5/internal/pgio" 9 | ) 10 | 11 | type FunctionCallResponse struct { 12 | Result []byte 13 | } 14 | 15 | // Backend identifies this message as sendable by the PostgreSQL backend. 16 | func (*FunctionCallResponse) Backend() {} 17 | 18 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 19 | // type identifier and 4 byte message length. 20 | func (dst *FunctionCallResponse) Decode(src []byte) error { 21 | if len(src) < 4 { 22 | return &invalidMessageFormatErr{messageType: "FunctionCallResponse"} 23 | } 24 | rp := 0 25 | resultSize := int(binary.BigEndian.Uint32(src[rp:])) 26 | rp += 4 27 | 28 | if resultSize == -1 { 29 | dst.Result = nil 30 | return nil 31 | } 32 | 33 | if len(src[rp:]) != resultSize { 34 | return &invalidMessageFormatErr{messageType: "FunctionCallResponse"} 35 | } 36 | 37 | dst.Result = src[rp:] 38 | return nil 39 | } 40 | 41 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 42 | func (src *FunctionCallResponse) Encode(dst []byte) ([]byte, error) { 43 | dst, sp := beginMessage(dst, 'V') 44 | 45 | if src.Result == nil { 46 | dst = pgio.AppendInt32(dst, -1) 47 | } else { 48 | dst = pgio.AppendInt32(dst, int32(len(src.Result))) 49 | dst = append(dst, src.Result...) 50 | } 51 | 52 | return finishMessage(dst, sp) 53 | } 54 | 55 | // MarshalJSON implements encoding/json.Marshaler. 56 | func (src FunctionCallResponse) MarshalJSON() ([]byte, error) { 57 | var formattedValue map[string]string 58 | var hasNonPrintable bool 59 | for _, b := range src.Result { 60 | if b < 32 { 61 | hasNonPrintable = true 62 | break 63 | } 64 | } 65 | 66 | if hasNonPrintable { 67 | formattedValue = map[string]string{"binary": hex.EncodeToString(src.Result)} 68 | } else { 69 | formattedValue = map[string]string{"text": string(src.Result)} 70 | } 71 | 72 | return json.Marshal(struct { 73 | Type string 74 | Result map[string]string 75 | }{ 76 | Type: "FunctionCallResponse", 77 | Result: formattedValue, 78 | }) 79 | } 80 | 81 | // UnmarshalJSON implements encoding/json.Unmarshaler. 82 | func (dst *FunctionCallResponse) UnmarshalJSON(data []byte) error { 83 | // Ignore null, like in the main JSON package. 84 | if string(data) == "null" { 85 | return nil 86 | } 87 | 88 | var msg struct { 89 | Result map[string]string 90 | } 91 | err := json.Unmarshal(data, &msg) 92 | if err != nil { 93 | return err 94 | } 95 | dst.Result, err = getValueFromJSON(msg.Result) 96 | return err 97 | } 98 | -------------------------------------------------------------------------------- /pgproto3/startup_message.go: -------------------------------------------------------------------------------- 1 | package pgproto3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | 10 | "github.com/jackc/pgx/v5/internal/pgio" 11 | ) 12 | 13 | const ProtocolVersionNumber = 196608 // 3.0 14 | 15 | type StartupMessage struct { 16 | ProtocolVersion uint32 17 | Parameters map[string]string 18 | } 19 | 20 | // Frontend identifies this message as sendable by a PostgreSQL frontend. 21 | func (*StartupMessage) Frontend() {} 22 | 23 | // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 24 | // type identifier and 4 byte message length. 25 | func (dst *StartupMessage) Decode(src []byte) error { 26 | if len(src) < 4 { 27 | return errors.New("startup message too short") 28 | } 29 | 30 | dst.ProtocolVersion = binary.BigEndian.Uint32(src) 31 | rp := 4 32 | 33 | if dst.ProtocolVersion != ProtocolVersionNumber { 34 | return fmt.Errorf("Bad startup message version number. Expected %d, got %d", ProtocolVersionNumber, dst.ProtocolVersion) 35 | } 36 | 37 | dst.Parameters = make(map[string]string) 38 | for { 39 | idx := bytes.IndexByte(src[rp:], 0) 40 | if idx < 0 { 41 | return &invalidMessageFormatErr{messageType: "StartupMessage"} 42 | } 43 | key := string(src[rp : rp+idx]) 44 | rp += idx + 1 45 | 46 | idx = bytes.IndexByte(src[rp:], 0) 47 | if idx < 0 { 48 | return &invalidMessageFormatErr{messageType: "StartupMessage"} 49 | } 50 | value := string(src[rp : rp+idx]) 51 | rp += idx + 1 52 | 53 | dst.Parameters[key] = value 54 | 55 | if len(src[rp:]) == 1 { 56 | if src[rp] != 0 { 57 | return fmt.Errorf("Bad startup message last byte. Expected 0, got %d", src[rp]) 58 | } 59 | break 60 | } 61 | } 62 | 63 | return nil 64 | } 65 | 66 | // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 67 | func (src *StartupMessage) Encode(dst []byte) ([]byte, error) { 68 | sp := len(dst) 69 | dst = pgio.AppendInt32(dst, -1) 70 | 71 | dst = pgio.AppendUint32(dst, src.ProtocolVersion) 72 | for k, v := range src.Parameters { 73 | dst = append(dst, k...) 74 | dst = append(dst, 0) 75 | dst = append(dst, v...) 76 | dst = append(dst, 0) 77 | } 78 | dst = append(dst, 0) 79 | 80 | return finishMessage(dst, sp) 81 | } 82 | 83 | // MarshalJSON implements encoding/json.Marshaler. 84 | func (src StartupMessage) MarshalJSON() ([]byte, error) { 85 | return json.Marshal(struct { 86 | Type string 87 | ProtocolVersion uint32 88 | Parameters map[string]string 89 | }{ 90 | Type: "StartupMessage", 91 | ProtocolVersion: src.ProtocolVersion, 92 | Parameters: src.Parameters, 93 | }) 94 | } 95 | --------------------------------------------------------------------------------