From b52421341541e98ca5a8c08d8da6ed6c6033720b Mon Sep 17 00:00:00 2001 From: Joe Schafer Date: Mon, 1 Jan 2024 21:37:49 -0800 Subject: [PATCH 01/27] Upgrade to pgx v5 - For pggen row structs, implement pgx.RowScanner. - Remove the typeResolver, since we're punting type resolution to the pgx.Conn type map for now. #74 --- example/acceptance_test.go | 8 +- example/author/query.sql.go | 94 ++++-------- example/author/query.sql_test.go | 2 +- example/complex_params/query.sql.go | 6 +- example/composite/codegen_test.go | 2 +- example/composite/query.sql.go | 6 +- example/composite/query.sql_test.go | 2 +- example/custom_types/query.sql.go | 6 +- example/custom_types/query.sql_test.go | 4 +- example/device/query.sql.go | 6 +- example/device/query.sql_test.go | 2 +- example/domain/query.sql.go | 6 +- example/enums/query.sql.go | 6 +- example/enums/query.sql_test.go | 2 +- example/erp/order/customer.sql.go | 6 +- example/erp/order/customer.sql_test.go | 2 +- example/erp/order/price.sql.go | 2 +- example/function/query.sql.go | 6 +- example/go_pointer_types/query.sql.go | 6 +- .../inline_param_count/inline0/query.sql.go | 6 +- .../inline0/query.sql_test.go | 2 +- .../inline_param_count/inline1/query.sql.go | 6 +- .../inline1/query.sql_test.go | 2 +- .../inline_param_count/inline2/query.sql.go | 6 +- .../inline2/query.sql_test.go | 2 +- .../inline_param_count/inline3/query.sql.go | 6 +- .../inline3/query.sql_test.go | 2 +- example/ltree/codegen_test.go | 4 +- example/ltree/query.sql.go | 6 +- example/ltree/query.sql_test.go | 2 +- example/nested/query.sql.go | 10 +- example/pgcrypto/query.sql.go | 6 +- .../separate_out_dir/out/alpha_query.sql.0.go | 6 +- example/slices/query.sql.go | 6 +- example/syntax/query.sql.go | 6 +- example/void/query.sql.go | 6 +- generate.go | 2 +- go.mod | 7 +- go.sum | 21 ++- internal/codegen/golang/declarer_composite.go | 2 +- internal/codegen/golang/gotype/known_types.go | 142 +++++++++--------- internal/codegen/golang/gotype/types.go | 2 +- internal/codegen/golang/query.gotemplate | 31 +--- internal/codegen/golang/templater.go | 8 +- internal/codegen/golang/type_resolver_test.go | 6 +- internal/pg/column.go | 17 +-- internal/pg/column_test.go | 7 +- internal/pg/known_types.go | 4 +- internal/pg/query.sql.go | 128 +++++----------- internal/pg/type_cache.go | 19 ++- internal/pg/type_fetcher.go | 31 ++-- internal/pg/type_fetcher_test.go | 7 +- internal/pg/types.go | 89 ++++++----- internal/pgdocker/pgdocker.go | 2 +- internal/pginfer/pginfer.go | 14 +- internal/pgplan/pgplan.go | 2 +- internal/pgtest/pg_test_db.go | 2 +- 57 files changed, 346 insertions(+), 455 deletions(-) diff --git a/example/acceptance_test.go b/example/acceptance_test.go index c1e231a9..5a8cb469 100644 --- a/example/acceptance_test.go +++ b/example/acceptance_test.go @@ -7,7 +7,7 @@ import ( "context" "flag" "fmt" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" "github.com/jschaf/pggen/internal/errs" "github.com/jschaf/pggen/internal/pgdocker" "math/rand" @@ -57,7 +57,7 @@ func TestExamples(t *testing.T) { "--go-type", "int8=int", "--go-type", "int4=int", "--go-type", "text=string", - "--go-type", "citext=github.com/jackc/pgtype.Text", + "--go-type", "citext=github.com/jackc/pgx/v5/pgtype.Text", }, }, { @@ -179,8 +179,8 @@ func TestExamples(t *testing.T) { args: []string{ "--schema-glob", "example/ltree/schema.sql", "--query-glob", "example/ltree/query.sql", - "--go-type", "ltree=github.com/jackc/pgtype.Text", - "--go-type", "_ltree=github.com/jackc/pgtype.TextArray", + "--go-type", "ltree=github.com/jackc/pgx/v5/pgtype.Text", + "--go-type", "_ltree=github.com/jackc/pgx/v5/pgtype.TextArray", }, }, { diff --git a/example/author/query.sql.go b/example/author/query.sql.go index e654ea13..2b892bec 100644 --- a/example/author/query.sql.go +++ b/example/author/query.sql.go @@ -5,9 +5,8 @@ package author import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" ) // Querier is a typesafe Go interface backed by SQL queries. @@ -48,8 +47,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn // underlying Postgres transport to use } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -61,36 +59,7 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false - } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true -} - -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt + return &DBQuerier{conn: conn} } const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` @@ -102,12 +71,17 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix"` } +func (r *FindAuthorByIDRow) scanRow(rows pgx.Rows) error { + return rows.Scan(&r.AuthorID, &r.FirstName, &r.LastName, &r.Suffix) +} + // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthorByID") row := q.conn.QueryRow(ctx, findAuthorByIDSQL, authorID) var item FindAuthorByIDRow - if err := row.Scan(&item.AuthorID, &item.FirstName, &item.LastName, &item.Suffix); err != nil { + s := scanner[*FindAuthorByIDRow]{&item} + if err := row.Scan(s); err != nil { return item, fmt.Errorf("query FindAuthorByID: %w", err) } return item, nil @@ -122,6 +96,10 @@ type FindAuthorsRow struct { Suffix *string `json:"suffix"` } +func (r *FindAuthorsRow) scanRow(rows pgx.Rows) error { + return rows.Scan(&r.AuthorID, &r.FirstName, &r.LastName, &r.Suffix) +} + // FindAuthors implements Querier.FindAuthors. func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAuthorsRow, error) { ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthors") @@ -131,9 +109,11 @@ func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAu } defer rows.Close() items := []FindAuthorsRow{} + s := scanner[*FindAuthorsRow]{} for rows.Next() { var item FindAuthorsRow - if err := rows.Scan(&item.AuthorID, &item.FirstName, &item.LastName, &item.Suffix); err != nil { + s.item = &item + if err := rows.Scan(s); err != nil { return nil, fmt.Errorf("scan FindAuthors row: %w", err) } items = append(items, item) @@ -151,6 +131,10 @@ type FindAuthorNamesRow struct { LastName *string `json:"last_name"` } +func (r *FindAuthorNamesRow) scanRow(rows pgx.Rows) error { + return rows.Scan(&r.FirstName, &r.LastName) +} + // FindAuthorNames implements Querier.FindAuthorNames. func (q *DBQuerier) FindAuthorNames(ctx context.Context, authorID int32) ([]FindAuthorNamesRow, error) { ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthorNames") @@ -160,9 +144,11 @@ func (q *DBQuerier) FindAuthorNames(ctx context.Context, authorID int32) ([]Find } defer rows.Close() items := []FindAuthorNamesRow{} + s := scanner[*FindAuthorNamesRow]{} for rows.Next() { var item FindAuthorNamesRow - if err := rows.Scan(&item.FirstName, &item.LastName); err != nil { + s.item = &item + if err := rows.Scan(s); err != nil { return nil, fmt.Errorf("scan FindAuthorNames row: %w", err) } items = append(items, item) @@ -275,12 +261,17 @@ type InsertAuthorSuffixRow struct { Suffix *string `json:"suffix"` } +func (r *InsertAuthorSuffixRow) scanRow(rows pgx.Rows) error { + return rows.Scan(&r.AuthorID, &r.FirstName, &r.LastName, &r.Suffix) +} + // InsertAuthorSuffix implements Querier.InsertAuthorSuffix. func (q *DBQuerier) InsertAuthorSuffix(ctx context.Context, params InsertAuthorSuffixParams) (InsertAuthorSuffixRow, error) { ctx = context.WithValue(ctx, "pggen_query_name", "InsertAuthorSuffix") row := q.conn.QueryRow(ctx, insertAuthorSuffixSQL, params.FirstName, params.LastName, params.Suffix) var item InsertAuthorSuffixRow - if err := row.Scan(&item.AuthorID, &item.FirstName, &item.LastName, &item.Suffix); err != nil { + s := scanner[*InsertAuthorSuffixRow]{&item} + if err := row.Scan(s); err != nil { return item, fmt.Errorf("query InsertAuthorSuffix: %w", err) } return item, nil @@ -312,27 +303,8 @@ func (q *DBQuerier) ArrayAggFirstName(ctx context.Context, authorID int32) ([]st return item, nil } -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } +type rowScanner interface{ scanRow(rows pgx.Rows) error } -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName -} +type scanner[T rowScanner] struct{ item T } -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 +func (s scanner[T]) ScanRow(rows pgx.Rows) error { return s.item.scanRow(rows) } diff --git a/example/author/query.sql_test.go b/example/author/query.sql_test.go index 9b4f753b..55521e45 100644 --- a/example/author/query.sql_test.go +++ b/example/author/query.sql_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" "testing" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" "github.com/jschaf/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/complex_params/query.sql.go b/example/complex_params/query.sql.go index 4315bdc5..fc0589f8 100644 --- a/example/complex_params/query.sql.go +++ b/example/complex_params/query.sql.go @@ -5,9 +5,9 @@ package complex_params import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/composite/codegen_test.go b/example/composite/codegen_test.go index 68334b1f..3972f024 100644 --- a/example/composite/codegen_test.go +++ b/example/composite/codegen_test.go @@ -28,7 +28,7 @@ func TestGenerate_Go_Example_Composite(t *testing.T) { "int8": "int", "int4": "int", "text": "string", - "citext": "github.com/jackc/pgtype.Text", + "citext": "github.com/jackc/pgx/v5/pgtype.Text", }, }) if err != nil { diff --git a/example/composite/query.sql.go b/example/composite/query.sql.go index c4b9b0b5..a62844af 100644 --- a/example/composite/query.sql.go +++ b/example/composite/query.sql.go @@ -5,9 +5,9 @@ package composite import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/composite/query.sql_test.go b/example/composite/query.sql_test.go index 1a9d0d0f..f8e8e82e 100644 --- a/example/composite/query.sql_test.go +++ b/example/composite/query.sql_test.go @@ -2,7 +2,7 @@ package composite import ( "context" - "github.com/jackc/pgtype" + "github.com/jackc/pgx/v5/pgtype" "github.com/jschaf/pggen/internal/difftest" "github.com/jschaf/pggen/internal/pgtest" "github.com/jschaf/pggen/internal/ptrs" diff --git a/example/custom_types/query.sql.go b/example/custom_types/query.sql.go index 133c424c..326d2b2d 100644 --- a/example/custom_types/query.sql.go +++ b/example/custom_types/query.sql.go @@ -5,9 +5,9 @@ package custom_types import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" "github.com/jschaf/pggen/example/custom_types/mytype" ) diff --git a/example/custom_types/query.sql_test.go b/example/custom_types/query.sql_test.go index 1912fe94..e98d7cd2 100644 --- a/example/custom_types/query.sql_test.go +++ b/example/custom_types/query.sql_test.go @@ -2,7 +2,7 @@ package custom_types import ( "context" - "github.com/jackc/pgtype" + "github.com/jackc/pgx/v5/pgtype" "github.com/jschaf/pggen/internal/pgtest" "github.com/jschaf/pggen/internal/texts" "github.com/stretchr/testify/assert" @@ -38,7 +38,7 @@ func TestQuerier_CustomMyInt(t *testing.T) { AND pn.nspname = current_schema() LIMIT 1; `)) - oidVal := pgtype.OIDValue{} + oidVal := uint32Value{} err := row.Scan(&oidVal) require.NoError(t, err) t.Logf("my_int oid: %d", oidVal.Uint) diff --git a/example/device/query.sql.go b/example/device/query.sql.go index d2b6fcd8..cdad2365 100644 --- a/example/device/query.sql.go +++ b/example/device/query.sql.go @@ -5,9 +5,9 @@ package device import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/device/query.sql_test.go b/example/device/query.sql_test.go index 3319f94e..95171f60 100644 --- a/example/device/query.sql_test.go +++ b/example/device/query.sql_test.go @@ -2,7 +2,7 @@ package device import ( "context" - "github.com/jackc/pgtype" + "github.com/jackc/pgx/v5/pgtype" "github.com/jschaf/pggen/internal/pgtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/example/domain/query.sql.go b/example/domain/query.sql.go index 01130f2c..3d1de221 100644 --- a/example/domain/query.sql.go +++ b/example/domain/query.sql.go @@ -5,9 +5,9 @@ package domain import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/enums/query.sql.go b/example/enums/query.sql.go index cbaea2ed..c72c9d9c 100644 --- a/example/enums/query.sql.go +++ b/example/enums/query.sql.go @@ -5,9 +5,9 @@ package enums import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/enums/query.sql_test.go b/example/enums/query.sql_test.go index e4d87bd3..e36f04a5 100644 --- a/example/enums/query.sql_test.go +++ b/example/enums/query.sql_test.go @@ -2,7 +2,7 @@ package enums import ( "context" - "github.com/jackc/pgtype" + "github.com/jackc/pgx/v5/pgtype" "github.com/jschaf/pggen/internal/pgtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/example/erp/order/customer.sql.go b/example/erp/order/customer.sql.go index ca372b20..45aca9c8 100644 --- a/example/erp/order/customer.sql.go +++ b/example/erp/order/customer.sql.go @@ -5,9 +5,9 @@ package order import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/erp/order/customer.sql_test.go b/example/erp/order/customer.sql_test.go index f979954d..83498397 100644 --- a/example/erp/order/customer.sql_test.go +++ b/example/erp/order/customer.sql_test.go @@ -2,7 +2,7 @@ package order import ( "context" - "github.com/jackc/pgtype" + "github.com/jackc/pgx/v5/pgtype" "github.com/jschaf/pggen/internal/pgtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/example/erp/order/price.sql.go b/example/erp/order/price.sql.go index 2395a2ca..c4c0b9ab 100644 --- a/example/erp/order/price.sql.go +++ b/example/erp/order/price.sql.go @@ -5,7 +5,7 @@ package order import ( "context" "fmt" - "github.com/jackc/pgtype" + "github.com/jackc/pgx/v5/pgtype" ) const findOrdersByPriceSQL = `SELECT * FROM orders WHERE order_total > $1;` diff --git a/example/function/query.sql.go b/example/function/query.sql.go index ebfd5e7c..c7ba5bd0 100644 --- a/example/function/query.sql.go +++ b/example/function/query.sql.go @@ -5,9 +5,9 @@ package function import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/go_pointer_types/query.sql.go b/example/go_pointer_types/query.sql.go index fce3f65d..08276e52 100644 --- a/example/go_pointer_types/query.sql.go +++ b/example/go_pointer_types/query.sql.go @@ -5,9 +5,9 @@ package go_pointer_types import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/inline_param_count/inline0/query.sql.go b/example/inline_param_count/inline0/query.sql.go index 4b4bbace..c63d4b90 100644 --- a/example/inline_param_count/inline0/query.sql.go +++ b/example/inline_param_count/inline0/query.sql.go @@ -5,9 +5,9 @@ package inline0 import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/inline_param_count/inline0/query.sql_test.go b/example/inline_param_count/inline0/query.sql_test.go index 2babf0a2..a7b1bb82 100644 --- a/example/inline_param_count/inline0/query.sql_test.go +++ b/example/inline_param_count/inline0/query.sql_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" "testing" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" "github.com/jschaf/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/inline_param_count/inline1/query.sql.go b/example/inline_param_count/inline1/query.sql.go index 0583f0df..2b55216d 100644 --- a/example/inline_param_count/inline1/query.sql.go +++ b/example/inline_param_count/inline1/query.sql.go @@ -5,9 +5,9 @@ package inline1 import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/inline_param_count/inline1/query.sql_test.go b/example/inline_param_count/inline1/query.sql_test.go index c3bbfc9a..af989259 100644 --- a/example/inline_param_count/inline1/query.sql_test.go +++ b/example/inline_param_count/inline1/query.sql_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" "testing" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" "github.com/jschaf/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/inline_param_count/inline2/query.sql.go b/example/inline_param_count/inline2/query.sql.go index b5624ca2..113ecb35 100644 --- a/example/inline_param_count/inline2/query.sql.go +++ b/example/inline_param_count/inline2/query.sql.go @@ -5,9 +5,9 @@ package inline2 import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/inline_param_count/inline2/query.sql_test.go b/example/inline_param_count/inline2/query.sql_test.go index b119f7fb..a17347f1 100644 --- a/example/inline_param_count/inline2/query.sql_test.go +++ b/example/inline_param_count/inline2/query.sql_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" "testing" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" "github.com/jschaf/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/inline_param_count/inline3/query.sql.go b/example/inline_param_count/inline3/query.sql.go index 8940776a..88f0c874 100644 --- a/example/inline_param_count/inline3/query.sql.go +++ b/example/inline_param_count/inline3/query.sql.go @@ -5,9 +5,9 @@ package inline3 import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/inline_param_count/inline3/query.sql_test.go b/example/inline_param_count/inline3/query.sql_test.go index 527a983b..b4e9121f 100644 --- a/example/inline_param_count/inline3/query.sql_test.go +++ b/example/inline_param_count/inline3/query.sql_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" "testing" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" "github.com/jschaf/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/ltree/codegen_test.go b/example/ltree/codegen_test.go index f5f1fec1..2a05d1f9 100644 --- a/example/ltree/codegen_test.go +++ b/example/ltree/codegen_test.go @@ -23,8 +23,8 @@ func TestGenerate_Go_Example_ltree(t *testing.T) { Language: pggen.LangGo, InlineParamCount: 2, TypeOverrides: map[string]string{ - "ltree": "github.com/jackc/pgtype.Text", - "_ltree": "github.com/jackc/pgtype.TextArray", + "ltree": "github.com/jackc/pgx/v5/pgtype.Text", + "_ltree": "github.com/jackc/pgx/v5/pgtype.TextArray", }, }) if err != nil { diff --git a/example/ltree/query.sql.go b/example/ltree/query.sql.go index aee3db7f..537e2343 100644 --- a/example/ltree/query.sql.go +++ b/example/ltree/query.sql.go @@ -5,9 +5,9 @@ package ltree import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/ltree/query.sql_test.go b/example/ltree/query.sql_test.go index 83d0d8d9..ce591bbc 100644 --- a/example/ltree/query.sql_test.go +++ b/example/ltree/query.sql_test.go @@ -2,7 +2,7 @@ package ltree import ( "context" - "github.com/jackc/pgtype" + "github.com/jackc/pgx/v5/pgtype" "github.com/jschaf/pggen/internal/pgtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/example/nested/query.sql.go b/example/nested/query.sql.go index 1ffb4074..f1bc80bd 100644 --- a/example/nested/query.sql.go +++ b/example/nested/query.sql.go @@ -5,9 +5,9 @@ package nested import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. @@ -57,7 +57,7 @@ type ProductImageType struct { // typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name + connInfo *pgtype.Codec // types by Postgres type name } func newTypeResolver() *typeResolver { @@ -148,6 +148,7 @@ func (tr *typeResolver) newDimensions() pgtype.ValueTranscoder { // newProductImageSetType creates a new pgtype.ValueTranscoder for the Postgres // composite type 'product_image_set_type'. func (tr *typeResolver) newProductImageSetType() pgtype.ValueTranscoder { + pgtype.CompositeCodec{} return tr.newCompositeValue( "product_image_set_type", compositeField{name: "name", typeName: "text", defaultVal: &pgtype.Text{}}, @@ -169,6 +170,7 @@ func (tr *typeResolver) newProductImageType() pgtype.ValueTranscoder { // newProductImageTypeArray creates a new pgtype.ValueTranscoder for the Postgres // '_product_image_type' array type. func (tr *typeResolver) newProductImageTypeArray() pgtype.ValueTranscoder { + pgtype.CompositeCodec{} return tr.newArrayValue("_product_image_type", "product_image_type", tr.newProductImageType) } diff --git a/example/pgcrypto/query.sql.go b/example/pgcrypto/query.sql.go index 90be999c..ff478512 100644 --- a/example/pgcrypto/query.sql.go +++ b/example/pgcrypto/query.sql.go @@ -5,9 +5,9 @@ package pgcrypto import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/separate_out_dir/out/alpha_query.sql.0.go b/example/separate_out_dir/out/alpha_query.sql.0.go index bfebba47..f99f15dc 100644 --- a/example/separate_out_dir/out/alpha_query.sql.0.go +++ b/example/separate_out_dir/out/alpha_query.sql.0.go @@ -5,9 +5,9 @@ package out import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/slices/query.sql.go b/example/slices/query.sql.go index 8ef9062c..ed0fad12 100644 --- a/example/slices/query.sql.go +++ b/example/slices/query.sql.go @@ -5,9 +5,9 @@ package slices import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" "time" ) diff --git a/example/syntax/query.sql.go b/example/syntax/query.sql.go index b221822c..85bf0661 100644 --- a/example/syntax/query.sql.go +++ b/example/syntax/query.sql.go @@ -5,9 +5,9 @@ package syntax import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/example/void/query.sql.go b/example/void/query.sql.go index 62fb382e..146b3cf1 100644 --- a/example/void/query.sql.go +++ b/example/void/query.sql.go @@ -5,9 +5,9 @@ package void import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. diff --git a/generate.go b/generate.go index 94aef72c..d7bf8699 100644 --- a/generate.go +++ b/generate.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" "github.com/jschaf/pggen/internal/ast" "github.com/jschaf/pggen/internal/codegen" "github.com/jschaf/pggen/internal/codegen/golang" diff --git a/go.mod b/go.mod index a46a9a7f..db08d690 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/jackc/pgproto3/v2 v2.3.2 github.com/jackc/pgtype v1.14.0 github.com/jackc/pgx/v4 v4.18.1 + github.com/jackc/pgx/v5 v5.5.1 github.com/peterbourgon/ff/v3 v3.4.0 github.com/shopspring/decimal v1.3.1 github.com/stretchr/testify v1.8.4 @@ -26,8 +27,7 @@ require ( github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/kr/pretty v0.2.1 // indirect + github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect github.com/kr/text v0.2.0 // indirect github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect github.com/morikuni/aec v1.0.0 // indirect @@ -35,7 +35,8 @@ require ( github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/crypto v0.16.0 // indirect + github.com/rogpeppe/go-internal v1.6.1 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.15.0 // indirect diff --git a/go.sum b/go.sum index 32ca3a76..28462699 100644 --- a/go.sum +++ b/go.sum @@ -67,8 +67,9 @@ github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwX github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= @@ -81,17 +82,22 @@ github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQ github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI= +github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -123,6 +129,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= @@ -175,8 +183,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -258,8 +266,9 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/codegen/golang/declarer_composite.go b/internal/codegen/golang/declarer_composite.go index 3dc1988f..34625c04 100644 --- a/internal/codegen/golang/declarer_composite.go +++ b/internal/codegen/golang/declarer_composite.go @@ -263,7 +263,7 @@ func (c CompositeInitDeclarer) Declare(string) (string, error) { // of a composite type as a generic array: []interface{}. Necessary because we // can only set pgtype.CompositeType from a []interface{}. // -// Revisit after https://github.com/jackc/pgtype/pull/100 to see if we can +// Revisit after https://github.com/jackc/pgx/v5/pgtype/pull/100 to see if we can // simplify. type CompositeRawDeclarer struct { typ *gotype.CompositeType diff --git a/internal/codegen/golang/gotype/known_types.go b/internal/codegen/golang/gotype/known_types.go index 6d893f52..87ed8b4b 100644 --- a/internal/codegen/golang/gotype/known_types.go +++ b/internal/codegen/golang/gotype/known_types.go @@ -1,14 +1,14 @@ package gotype import ( - "github.com/jackc/pgtype" + "github.com/jackc/pgx/v5/pgtype" "github.com/jschaf/pggen/internal/pg" "github.com/jschaf/pggen/internal/pg/pgoid" ) // FindKnownTypePgx returns the native pgx type, like pgtype.Text, if known, for // a Postgres OID. If there is no known type, returns nil. -func FindKnownTypePgx(oid pgtype.OID) (Type, bool) { +func FindKnownTypePgx(oid uint32) (Type, bool) { typ, ok := knownTypesByOID[oid] return typ.pgNative, ok } @@ -16,7 +16,7 @@ func FindKnownTypePgx(oid pgtype.OID) (Type, bool) { // FindKnownTypeNullable returns the nullable type, like *string, if known, for // a Postgres OID. Falls back to the pgNative type. If there is no known type // for the OID, returns nil. -func FindKnownTypeNullable(oid pgtype.OID) (Type, bool) { +func FindKnownTypeNullable(oid uint32) (Type, bool) { typ, ok := knownTypesByOID[oid] if !ok { return nil, false @@ -30,7 +30,7 @@ func FindKnownTypeNullable(oid pgtype.OID) (Type, bool) { // FindKnownTypeNonNullable returns the non-nullable type like string, if known, // for a Postgres OID. Falls back to the nullable type and pgNative type. If // there is no known type for the OID, returns nil. -func FindKnownTypeNonNullable(oid pgtype.OID) (Type, bool) { +func FindKnownTypeNonNullable(oid uint32) (Type, bool) { typ, ok := knownTypesByOID[oid] if !ok { return nil, false @@ -91,73 +91,73 @@ var ( // pgtype types prefixed with "pg". var ( - PgBool = MustParseKnownType("github.com/jackc/pgtype.Bool", pg.Bool) - PgQChar = MustParseKnownType("github.com/jackc/pgtype.QChar", pg.QChar) - PgName = MustParseKnownType("github.com/jackc/pgtype.Name", pg.Name) - PgInt8 = MustParseKnownType("github.com/jackc/pgtype.Int8", pg.Int8) - PgInt2 = MustParseKnownType("github.com/jackc/pgtype.Int2", pg.Int2) - PgInt4 = MustParseKnownType("github.com/jackc/pgtype.Int4", pg.Int4) - PgText = MustParseKnownType("github.com/jackc/pgtype.Text", pg.Text) - PgBytea = MustParseKnownType("github.com/jackc/pgtype.Bytea", pg.Bytea) - PgOID = MustParseKnownType("github.com/jackc/pgtype.OID", pg.OID) - PgTID = MustParseKnownType("github.com/jackc/pgtype.TID", pg.TID) - PgXID = MustParseKnownType("github.com/jackc/pgtype.XID", pg.XID) - PgCID = MustParseKnownType("github.com/jackc/pgtype.CID", pg.CID) - PgJSON = MustParseKnownType("github.com/jackc/pgtype.JSON", pg.JSON) - PgPoint = MustParseKnownType("github.com/jackc/pgtype.Point", pg.Point) - PgLseg = MustParseKnownType("github.com/jackc/pgtype.Lseg", pg.Lseg) - PgPath = MustParseKnownType("github.com/jackc/pgtype.Path", pg.Path) - PgBox = MustParseKnownType("github.com/jackc/pgtype.Box", pg.Box) - PgPolygon = MustParseKnownType("github.com/jackc/pgtype.Polygon", pg.Polygon) - PgLine = MustParseKnownType("github.com/jackc/pgtype.Line", pg.Line) - PgCIDR = MustParseKnownType("github.com/jackc/pgtype.CIDR", pg.CIDR) - PgCIDRArray = MustParseKnownType("github.com/jackc/pgtype.CIDRArray", pg.CIDRArray) - PgFloat4 = MustParseKnownType("github.com/jackc/pgtype.Float4", pg.Float4) - PgFloat8 = MustParseKnownType("github.com/jackc/pgtype.Float8", pg.Float8) - PgUnknown = MustParseKnownType("github.com/jackc/pgtype.Unknown", pg.Unknown) - PgCircle = MustParseKnownType("github.com/jackc/pgtype.Circle", pg.Circle) - PgMacaddr = MustParseKnownType("github.com/jackc/pgtype.Macaddr", pg.Macaddr) - PgInet = MustParseKnownType("github.com/jackc/pgtype.Inet", pg.Inet) - PgBoolArray = MustParseKnownType("github.com/jackc/pgtype.BoolArray", pg.BoolArray) - PgByteaArray = MustParseKnownType("github.com/jackc/pgtype.ByteaArray", pg.ByteaArray) - PgInt2Array = MustParseKnownType("github.com/jackc/pgtype.Int2Array", pg.Int2Array) - PgInt4Array = MustParseKnownType("github.com/jackc/pgtype.Int4Array", pg.Int4Array) - PgTextArray = MustParseKnownType("github.com/jackc/pgtype.TextArray", pg.TextArray) - PgBPCharArray = MustParseKnownType("github.com/jackc/pgtype.BPCharArray", pg.BPCharArray) - PgVarcharArray = MustParseKnownType("github.com/jackc/pgtype.VarcharArray", pg.VarcharArray) - PgInt8Array = MustParseKnownType("github.com/jackc/pgtype.Int8Array", pg.Int8Array) - PgFloat4Array = MustParseKnownType("github.com/jackc/pgtype.Float4Array", pg.Float4Array) - PgFloat8Array = MustParseKnownType("github.com/jackc/pgtype.Float8Array", pg.Float8Array) - PgACLItem = MustParseKnownType("github.com/jackc/pgtype.ACLItem", pg.ACLItem) - PgACLItemArray = MustParseKnownType("github.com/jackc/pgtype.ACLItemArray", pg.ACLItemArray) - PgInetArray = MustParseKnownType("github.com/jackc/pgtype.InetArray", pg.InetArray) - PgMacaddrArray = MustParseKnownType("github.com/jackc/pgtype.MacaddrArray", pg.MacaddrArray) - PgBPChar = MustParseKnownType("github.com/jackc/pgtype.BPChar", pg.BPChar) - PgVarchar = MustParseKnownType("github.com/jackc/pgtype.Varchar", pg.Varchar) - PgDate = MustParseKnownType("github.com/jackc/pgtype.Date", pg.Date) - PgTime = MustParseKnownType("github.com/jackc/pgtype.Time", pg.Time) - PgTimestamp = MustParseKnownType("github.com/jackc/pgtype.Timestamp", pg.Timestamp) - PgTimestampArray = MustParseKnownType("github.com/jackc/pgtype.TimestampArray", pg.TimestampArray) - PgDateArray = MustParseKnownType("github.com/jackc/pgtype.DateArray", pg.DateArray) - PgTimestamptz = MustParseKnownType("github.com/jackc/pgtype.Timestamptz", pg.Timestamptz) - PgTimestamptzArray = MustParseKnownType("github.com/jackc/pgtype.TimestamptzArray", pg.TimestamptzArray) - PgInterval = MustParseKnownType("github.com/jackc/pgtype.Interval", pg.Interval) - PgNumericArray = MustParseKnownType("github.com/jackc/pgtype.NumericArray", pg.NumericArray) - PgBit = MustParseKnownType("github.com/jackc/pgtype.Bit", pg.Bit) - PgVarbit = MustParseKnownType("github.com/jackc/pgtype.Varbit", pg.Varbit) + PgBool = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Bool", pg.Bool) + PgQChar = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.QChar", pg.QChar) + PgName = MustParseKnownType("string", pg.Name) + PgInt8 = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Int8", pg.Int8) + PgInt2 = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Int2", pg.Int2) + PgInt4 = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Int4", pg.Int4) + PgText = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Text", pg.Text) + PgBytea = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Bytea", pg.Bytea) + PgOID = MustParseKnownType("uint32", pg.OID) + PgTID = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.TID", pg.TID) + PgXID = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.XID", pg.XID) + PgCID = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.CID", pg.CID) + PgJSON = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.JSON", pg.JSON) + PgPoint = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Point", pg.Point) + PgLseg = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Lseg", pg.Lseg) + PgPath = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Path", pg.Path) + PgBox = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Box", pg.Box) + PgPolygon = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Polygon", pg.Polygon) + PgLine = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Line", pg.Line) + PgCIDR = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.CIDR", pg.CIDR) + PgCIDRArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.CIDRArray", pg.CIDRArray) + PgFloat4 = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Float4", pg.Float4) + PgFloat8 = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Float8", pg.Float8) + PgUnknown = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Unknown", pg.Unknown) + PgCircle = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Circle", pg.Circle) + PgMacaddr = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Macaddr", pg.Macaddr) + PgInet = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Inet", pg.Inet) + PgBoolArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.BoolArray", pg.BoolArray) + PgByteaArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.ByteaArray", pg.ByteaArray) + PgInt2Array = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Int2Array", pg.Int2Array) + PgInt4Array = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Int4Array", pg.Int4Array) + PgTextArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.TextArray", pg.TextArray) + PgBPCharArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.BPCharArray", pg.BPCharArray) + PgVarcharArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.VarcharArray", pg.VarcharArray) + PgInt8Array = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Int8Array", pg.Int8Array) + PgFloat4Array = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Float4Array", pg.Float4Array) + PgFloat8Array = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Float8Array", pg.Float8Array) + PgACLItem = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.ACLItem", pg.ACLItem) + PgACLItemArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.ACLItemArray", pg.ACLItemArray) + PgInetArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.InetArray", pg.InetArray) + PgMacaddrArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.MacaddrArray", pg.MacaddrArray) + PgBPChar = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.BPChar", pg.BPChar) + PgVarchar = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Varchar", pg.Varchar) + PgDate = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Date", pg.Date) + PgTime = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Time", pg.Time) + PgTimestamp = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Timestamp", pg.Timestamp) + PgTimestampArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.TimestampArray", pg.TimestampArray) + PgDateArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.DateArray", pg.DateArray) + PgTimestamptz = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Timestamptz", pg.Timestamptz) + PgTimestamptzArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.TimestamptzArray", pg.TimestamptzArray) + PgInterval = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Interval", pg.Interval) + PgNumericArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.NumericArray", pg.NumericArray) + PgBit = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Bit", pg.Bit) + PgVarbit = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Varbit", pg.Varbit) PgVoid = &VoidType{} - PgNumeric = MustParseKnownType("github.com/jackc/pgtype.Numeric", pg.Numeric) - PgRecord = MustParseKnownType("github.com/jackc/pgtype.Record", pg.Record) - PgUUID = MustParseKnownType("github.com/jackc/pgtype.UUID", pg.UUID) - PgUUIDArray = MustParseKnownType("github.com/jackc/pgtype.UUIDArray", pg.UUIDArray) - PgJSONB = MustParseKnownType("github.com/jackc/pgtype.JSONB", pg.JSONB) - PgJSONBArray = MustParseKnownType("github.com/jackc/pgtype.JSONBArray", pg.JSONBArray) - PgInt4range = MustParseKnownType("github.com/jackc/pgtype.Int4range", pg.Int4range) - PgNumrange = MustParseKnownType("github.com/jackc/pgtype.Numrange", pg.Numrange) - PgTsrange = MustParseKnownType("github.com/jackc/pgtype.Tsrange", pg.Tsrange) - PgTstzrange = MustParseKnownType("github.com/jackc/pgtype.Tstzrange", pg.Tstzrange) - PgDaterange = MustParseKnownType("github.com/jackc/pgtype.Daterange", pg.Daterange) - PgInt8range = MustParseKnownType("github.com/jackc/pgtype.Int8range", pg.Int8range) + PgNumeric = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Numeric", pg.Numeric) + PgRecord = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Record", pg.Record) + PgUUID = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.UUID", pg.UUID) + PgUUIDArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.UUIDArray", pg.UUIDArray) + PgJSONB = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.JSONB", pg.JSONB) + PgJSONBArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.JSONBArray", pg.JSONBArray) + PgInt4range = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Int4range", pg.Int4range) + PgNumrange = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Numrange", pg.Numrange) + PgTsrange = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Tsrange", pg.Tsrange) + PgTstzrange = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Tstzrange", pg.Tstzrange) + PgDaterange = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Daterange", pg.Daterange) + PgInt8range = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Int8range", pg.Int8range) ) // knownGoType is the native pgtype type, the nullable and non-nullable types @@ -177,7 +177,7 @@ var ( // "string" for a Postgres text type. type knownGoType struct{ pgNative, nullable, nonNullable Type } -var knownTypesByOID = map[pgtype.OID]knownGoType{ +var knownTypesByOID = map[uint32]knownGoType{ pgtype.BoolOID: {PgBool, Boolp, Bool}, pgtype.QCharOID: {PgQChar, nil, nil}, pgtype.NameOID: {PgName, nil, nil}, diff --git a/internal/codegen/golang/gotype/types.go b/internal/codegen/golang/gotype/types.go index 65e8fce6..199d1e86 100644 --- a/internal/codegen/golang/gotype/types.go +++ b/internal/codegen/golang/gotype/types.go @@ -240,7 +240,7 @@ func ParseOpaqueType(qualType string, pgType pg.Type) (Type, error) { } // MustParseKnownType creates a gotype.Type by parsing a fully qualified Go type -// that pgx supports natively like "github.com/jackc/pgtype.Int4Array", or most +// that pgx supports natively like "github.com/jackc/pgx/v5/pgtype.Int4Array", or most // builtin types like "string" and []*int16. func MustParseKnownType(qualType string, pgType pg.Type) Type { typ, err := ParseOpaqueType(qualType, pgType) diff --git a/internal/codegen/golang/query.gotemplate b/internal/codegen/golang/query.gotemplate index 720befbb..d6456671 100644 --- a/internal/codegen/golang/query.gotemplate +++ b/internal/codegen/golang/query.gotemplate @@ -28,7 +28,6 @@ var _ Querier = &DBQuerier{} type DBQuerier struct { conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -40,7 +39,7 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} + return &DBQuerier{conn: conn} } {{- range .Declarers}}{{- "\n\n" -}}{{ .Declare $.PkgPath }}{{ end -}} @@ -93,33 +92,5 @@ func (q *DBQuerier) {{ $q.Name }}(ctx context.Context {{- $q.EmitParams }}) ({{ {{- end }} } {{- end -}} - -{{- if .IsLeader -}} -{{- "\n\n" -}} -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 -{{- end -}} {{- "\n" -}} {{- end -}} diff --git a/internal/codegen/golang/templater.go b/internal/codegen/golang/templater.go index 150035bf..de73461e 100644 --- a/internal/codegen/golang/templater.go +++ b/internal/codegen/golang/templater.go @@ -73,7 +73,7 @@ func (tm Templater) TemplateAll(files []codegen.QueryFile) ([]TemplatedFile, err pgconnIdx := -1 imports := file.Imports for i, pkg := range imports { - if pkg == "github.com/jackc/pgconn" { + if pkg == "github.com/jackc/pgx/v5/pgconn" { pgconnIdx = i break } @@ -113,10 +113,10 @@ func (tm Templater) templateFile(file codegen.QueryFile, isLeader bool) (Templat imports := NewImportSet() imports.AddPackage("context") imports.AddPackage("fmt") - imports.AddPackage("github.com/jackc/pgconn") + imports.AddPackage("github.com/jackc/pgx/v5/pgconn") if isLeader { - imports.AddPackage("github.com/jackc/pgtype") - imports.AddPackage("github.com/jackc/pgx/v4") + imports.AddPackage("github.com/jackc/pgx/v5/pgtype") + imports.AddPackage("github.com/jackc/pgx/v5") } pkgPath := "" diff --git a/internal/codegen/golang/type_resolver_test.go b/internal/codegen/golang/type_resolver_test.go index e31492e3..bd371049 100644 --- a/internal/codegen/golang/type_resolver_test.go +++ b/internal/codegen/golang/type_resolver_test.go @@ -2,7 +2,7 @@ package golang import ( "github.com/google/go-cmp/cmp" - "github.com/jackc/pgtype" + "github.com/jackc/pgx/v5/pgtype" "github.com/jschaf/pggen/internal/casing" "github.com/jschaf/pggen/internal/codegen/golang/gotype" "github.com/jschaf/pggen/internal/difftest" @@ -88,7 +88,7 @@ func TestTypeResolver_Resolve(t *testing.T) { pgType: pg.BaseType{Name: "point", ID: pgtype.PointOID}, nullable: false, want: &gotype.ImportType{ - PkgPath: "github.com/jackc/pgtype", + PkgPath: "github.com/jackc/pgx/v5/pgtype", Type: &gotype.OpaqueType{ PgType: pg.BaseType{Name: "point", ID: pgtype.PointOID}, Name: "Point", @@ -100,7 +100,7 @@ func TestTypeResolver_Resolve(t *testing.T) { pgType: pg.BaseType{Name: "point", ID: pgtype.PointOID}, nullable: true, want: &gotype.ImportType{ - PkgPath: "github.com/jackc/pgtype", + PkgPath: "github.com/jackc/pgx/v5/pgtype", Type: &gotype.OpaqueType{ PgType: pg.BaseType{Name: "point", ID: pgtype.PointOID}, Name: "Point", diff --git a/internal/pg/column.go b/internal/pg/column.go index e381ec71..9215205d 100644 --- a/internal/pg/column.go +++ b/internal/pg/column.go @@ -3,8 +3,7 @@ package pg import ( "context" "fmt" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" "github.com/jschaf/pggen/internal/texts" "strconv" "strings" @@ -15,18 +14,18 @@ import ( // Column stores information about a column in a TableOID. // https://www.postgresql.org/docs/13/catalog-pg-attribute.html type Column struct { - Name string // pg_attribute.attname: column name - TableOID pgtype.OID // pg_attribute:attrelid: table the column belongs to - TableName string // pg_class.relname: name of table that owns the column - Number uint16 // pg_attribute.attnum: the number of column starting from 1 - Type Type // pg_attribute.atttypid: data type of the column - Null bool // pg_attribute.attnotnull: represents a not-null constraint + Name string // pg_attribute.attname: column name + TableOID uint32 // pg_attribute:attrelid: table the column belongs to + TableName string // pg_class.relname: name of table that owns the column + Number uint16 // pg_attribute.attnum: the number of column starting from 1 + Type Type // pg_attribute.atttypid: data type of the column + Null bool // pg_attribute.attnotnull: represents a not-null constraint } // ColumnKey is a composite key of a table OID and the number of the column // within the table. type ColumnKey struct { - TableOID pgtype.OID + TableOID uint32 Number uint16 // the number of column starting from 1 } diff --git a/internal/pg/column_test.go b/internal/pg/column_test.go index c437c89f..5465b77f 100644 --- a/internal/pg/column_test.go +++ b/internal/pg/column_test.go @@ -3,8 +3,7 @@ package pg import ( "context" "github.com/google/go-cmp/cmp" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" "github.com/jschaf/pggen/internal/pgtest" "github.com/jschaf/pggen/internal/texts" "github.com/stretchr/testify/assert" @@ -74,7 +73,7 @@ func TestFetchColumns(t *testing.T) { } } -func findTableOID(t *testing.T, conn *pgx.Conn, table string) pgtype.OID { +func findTableOID(t *testing.T, conn *pgx.Conn, table string) uint32 { sql := texts.Dedent(` SELECT oid AS table_oid FROM pg_class @@ -85,7 +84,7 @@ func findTableOID(t *testing.T, conn *pgx.Conn, table string) pgtype.OID { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() row := conn.QueryRow(ctx, sql, table) - var oid pgtype.OID = 0 + var oid uint32 = 0 if err := row.Scan(&oid); err != nil && err != pgx.ErrNoRows { t.Fatal(err) } diff --git a/internal/pg/known_types.go b/internal/pg/known_types.go index 5540ad30..a78702c8 100644 --- a/internal/pg/known_types.go +++ b/internal/pg/known_types.go @@ -1,7 +1,7 @@ package pg import ( - "github.com/jackc/pgtype" + "github.com/jackc/pgx/v5/pgtype" "github.com/jschaf/pggen/internal/pg/pgoid" ) @@ -81,7 +81,7 @@ var ( ) // All known Postgres types by OID. -var defaultKnownTypes = map[pgtype.OID]Type{ +var defaultKnownTypes = map[uint32]Type{ pgtype.BoolOID: Bool, pgtype.ByteaOID: Bytea, pgtype.QCharOID: QChar, diff --git a/internal/pg/query.sql.go b/internal/pg/query.sql.go index 61f49aa9..af7b16a3 100644 --- a/internal/pg/query.sql.go +++ b/internal/pg/query.sql.go @@ -5,9 +5,8 @@ package pg import ( "context" "fmt" - "github.com/jackc/pgconn" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" ) // Querier is a typesafe Go interface backed by SQL queries. @@ -23,11 +22,11 @@ type Querier interface { // Recursively expands all given OIDs to all descendants through composite // types. - FindDescendantOIDs(ctx context.Context, oids []uint32) ([]pgtype.OID, error) + FindDescendantOIDs(ctx context.Context, oids []uint32) ([]uint32, error) - FindOIDByName(ctx context.Context, name string) (pgtype.OID, error) + FindOIDByName(ctx context.Context, name string) (uint32, error) - FindOIDName(ctx context.Context, oid pgtype.OID) (pgtype.Name, error) + FindOIDName(ctx context.Context, oid uint32) (string, error) FindOIDNames(ctx context.Context, oid []uint32) ([]FindOIDNamesRow, error) } @@ -35,8 +34,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn // underlying Postgres transport to use } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -48,36 +46,7 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false - } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true -} - -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt + return &DBQuerier{conn: conn} } const findEnumTypesSQL = `WITH enums AS ( @@ -125,13 +94,13 @@ WHERE typ.typisdefined AND typ.oid = ANY ($1::oid[]);` type FindEnumTypesRow struct { - OID pgtype.OID `json:"oid"` - TypeName string `json:"type_name"` - ChildOIDs []int `json:"child_oids"` - Orders []float32 `json:"orders"` - Labels []string `json:"labels"` - TypeKind pgtype.QChar `json:"type_kind"` - DefaultExpr string `json:"default_expr"` + OID uint32 `json:"oid"` + TypeName string `json:"type_name"` + ChildOIDs []int `json:"child_oids"` + Orders []float32 `json:"orders"` + Labels []string `json:"labels"` + TypeKind byte `json:"type_kind"` + DefaultExpr string `json:"default_expr"` } // FindEnumTypes implements Querier.FindEnumTypes. @@ -186,10 +155,10 @@ WHERE arr_typ.typisdefined AND arr_typ.oid = ANY ($1::oid[]);` type FindArrayTypesRow struct { - OID pgtype.OID `json:"oid"` - TypeName string `json:"type_name"` - ElemOID pgtype.OID `json:"elem_oid"` - TypeKind pgtype.QChar `json:"type_kind"` + OID uint32 `json:"oid"` + TypeName string `json:"type_name"` + ElemOID uint32 `json:"elem_oid"` + TypeKind byte `json:"type_kind"` } // FindArrayTypes implements Querier.FindArrayTypes. @@ -245,14 +214,14 @@ WHERE typ.oid = ANY ($1::oid[]) AND typ.typtype = 'c';` type FindCompositeTypesRow struct { - TableTypeName string `json:"table_type_name"` - TableTypeOID pgtype.OID `json:"table_type_oid"` - TableName pgtype.Name `json:"table_name"` - ColNames []string `json:"col_names"` - ColOIDs []int `json:"col_oids"` - ColOrders []int `json:"col_orders"` - ColNotNulls pgtype.BoolArray `json:"col_not_nulls"` - ColTypeNames []string `json:"col_type_names"` + TableTypeName string `json:"table_type_name"` + TableTypeOID uint32 `json:"table_type_oid"` + TableName string `json:"table_name"` + ColNames []string `json:"col_names"` + ColOIDs []int `json:"col_oids"` + ColOrders []int `json:"col_orders"` + ColNotNulls []bool `json:"col_not_nulls"` + ColTypeNames []string `json:"col_type_names"` } // FindCompositeTypes implements Querier.FindCompositeTypes. @@ -306,16 +275,16 @@ SELECT oid FROM oid_descs;` // FindDescendantOIDs implements Querier.FindDescendantOIDs. -func (q *DBQuerier) FindDescendantOIDs(ctx context.Context, oids []uint32) ([]pgtype.OID, error) { +func (q *DBQuerier) FindDescendantOIDs(ctx context.Context, oids []uint32) ([]uint32, error) { ctx = context.WithValue(ctx, "pggen_query_name", "FindDescendantOIDs") rows, err := q.conn.Query(ctx, findDescendantOIDsSQL, oids) if err != nil { return nil, fmt.Errorf("query FindDescendantOIDs: %w", err) } defer rows.Close() - items := []pgtype.OID{} + items := []uint32{} for rows.Next() { - var item pgtype.OID + var item uint32 if err := rows.Scan(&item); err != nil { return nil, fmt.Errorf("scan FindDescendantOIDs row: %w", err) } @@ -334,10 +303,10 @@ ORDER BY oid DESC LIMIT 1;` // FindOIDByName implements Querier.FindOIDByName. -func (q *DBQuerier) FindOIDByName(ctx context.Context, name string) (pgtype.OID, error) { +func (q *DBQuerier) FindOIDByName(ctx context.Context, name string) (uint32, error) { ctx = context.WithValue(ctx, "pggen_query_name", "FindOIDByName") row := q.conn.QueryRow(ctx, findOIDByNameSQL, name) - var item pgtype.OID + var item uint32 if err := row.Scan(&item); err != nil { return item, fmt.Errorf("query FindOIDByName: %w", err) } @@ -349,10 +318,10 @@ FROM pg_type WHERE oid = $1;` // FindOIDName implements Querier.FindOIDName. -func (q *DBQuerier) FindOIDName(ctx context.Context, oid pgtype.OID) (pgtype.Name, error) { +func (q *DBQuerier) FindOIDName(ctx context.Context, oid uint32) (string, error) { ctx = context.WithValue(ctx, "pggen_query_name", "FindOIDName") row := q.conn.QueryRow(ctx, findOIDNameSQL, oid) - var item pgtype.Name + var item string if err := row.Scan(&item); err != nil { return item, fmt.Errorf("query FindOIDName: %w", err) } @@ -364,9 +333,9 @@ FROM pg_type WHERE oid = ANY ($1::oid[]);` type FindOIDNamesRow struct { - OID pgtype.OID `json:"oid"` - Name pgtype.Name `json:"name"` - Kind pgtype.QChar `json:"kind"` + OID uint32 `json:"oid"` + Name string `json:"name"` + Kind byte `json:"kind"` } // FindOIDNames implements Querier.FindOIDNames. @@ -390,28 +359,3 @@ func (q *DBQuerier) FindOIDNames(ctx context.Context, oid []uint32) ([]FindOIDNa } return items, err } - -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/internal/pg/type_cache.go b/internal/pg/type_cache.go index d21a060a..53101a37 100644 --- a/internal/pg/type_cache.go +++ b/internal/pg/type_cache.go @@ -1,18 +1,17 @@ package pg import ( - "github.com/jackc/pgtype" "sync" ) // typeCache caches a map from a Postgres pg_type.oid to a Type. type typeCache struct { - types map[pgtype.OID]Type + types map[uint32]Type mu *sync.Mutex } func newTypeCache() *typeCache { - m := make(map[pgtype.OID]Type, len(defaultKnownTypes)) + m := make(map[uint32]Type, len(defaultKnownTypes)) for oid, typ := range defaultKnownTypes { m[oid] = typ } @@ -23,16 +22,16 @@ func newTypeCache() *typeCache { } // getOIDs returns the cached OIDS (with the type) and uncached OIDs. -func (tc *typeCache) getOIDs(oids ...uint32) (map[pgtype.OID]Type, map[pgtype.OID]struct{}) { - cachedTypes := make(map[pgtype.OID]Type, len(oids)) - uncachedTypes := make(map[pgtype.OID]struct{}, len(oids)) +func (tc *typeCache) getOIDs(oids ...uint32) (map[uint32]Type, map[uint32]struct{}) { + cachedTypes := make(map[uint32]Type, len(oids)) + uncachedTypes := make(map[uint32]struct{}, len(oids)) tc.mu.Lock() defer tc.mu.Unlock() for _, oid := range oids { - if t, ok := tc.types[pgtype.OID(oid)]; ok { - cachedTypes[pgtype.OID(oid)] = t + if t, ok := tc.types[uint32(oid)]; ok { + cachedTypes[uint32(oid)] = t } else { - uncachedTypes[pgtype.OID(oid)] = struct{}{} + uncachedTypes[uint32(oid)] = struct{}{} } } return cachedTypes, uncachedTypes @@ -40,7 +39,7 @@ func (tc *typeCache) getOIDs(oids ...uint32) (map[pgtype.OID]Type, map[pgtype.OI func (tc *typeCache) getOID(oid uint32) (Type, bool) { tc.mu.Lock() - typ, ok := tc.types[pgtype.OID(oid)] + typ, ok := tc.types[uint32(oid)] tc.mu.Unlock() return typ, ok } diff --git a/internal/pg/type_fetcher.go b/internal/pg/type_fetcher.go index c0ad0d6c..8eae7034 100644 --- a/internal/pg/type_fetcher.go +++ b/internal/pg/type_fetcher.go @@ -3,8 +3,7 @@ package pg import ( "context" "fmt" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" "time" ) @@ -24,7 +23,7 @@ func NewTypeFetcher(conn *pgx.Conn) *TypeFetcher { // FindTypesByOIDs returns a map of a type OID to the Type description. The // returned map contains every unique OID in oids (oids may contain duplicates) // unless there's an error. -func (tf *TypeFetcher) FindTypesByOIDs(oids ...uint32) (map[pgtype.OID]Type, error) { +func (tf *TypeFetcher) FindTypesByOIDs(oids ...uint32) (map[uint32]Type, error) { if types, uncached := tf.cache.getOIDs(oids...); len(uncached) == 0 { return types, nil } @@ -96,7 +95,7 @@ func (tf *TypeFetcher) FindTypesByOIDs(oids ...uint32) (map[pgtype.OID]Type, err return types, nil } -func (tf *TypeFetcher) findEnumTypes(ctx context.Context, uncached map[pgtype.OID]struct{}) ([]EnumType, error) { +func (tf *TypeFetcher) findEnumTypes(ctx context.Context, uncached map[uint32]struct{}) ([]EnumType, error) { oids := oidKeys(uncached) rows, err := tf.querier.FindEnumTypes(ctx, oids) if err != nil { @@ -104,9 +103,9 @@ func (tf *TypeFetcher) findEnumTypes(ctx context.Context, uncached map[pgtype.OI } types := make([]EnumType, len(rows)) for i, enum := range rows { - childOIDs := make([]pgtype.OID, len(enum.ChildOIDs)) + childOIDs := make([]uint32, len(enum.ChildOIDs)) for i, oidUint32 := range enum.ChildOIDs { - childOIDs[i] = pgtype.OID(oidUint32) + childOIDs[i] = uint32(oidUint32) } types[i] = EnumType{ ID: enum.OID, @@ -119,14 +118,14 @@ func (tf *TypeFetcher) findEnumTypes(ctx context.Context, uncached map[pgtype.OI return types, nil } -func (tf *TypeFetcher) findCompositeTypes(ctx context.Context, uncached map[pgtype.OID]struct{}) ([]CompositeType, error) { +func (tf *TypeFetcher) findCompositeTypes(ctx context.Context, uncached map[uint32]struct{}) ([]CompositeType, error) { oids := oidKeys(uncached) rows, err := tf.querier.FindCompositeTypes(ctx, oids) if err != nil { return nil, fmt.Errorf("find composite types: %w", err) } // Record all composite types to fake a topological sort by repeated iteration. - allComposites := make(map[pgtype.OID]struct{}, len(rows)) + allComposites := make(map[uint32]struct{}, len(rows)) for _, row := range rows { allComposites[row.TableTypeOID] = struct{}{} } @@ -148,13 +147,13 @@ func (tf *TypeFetcher) findCompositeTypes(ctx context.Context, uncached map[pgty // We might resolve this type in a future pass like findArrayTypes. At // the end, we'll attempt to to replace the placeholder with the // resolved type. - colTypes[i] = placeholderType{ID: pgtype.OID(colOID)} + colTypes[i] = placeholderType{ID: uint32(colOID)} colNames[i] = row.ColNames[i] } } typ := CompositeType{ ID: row.TableTypeOID, - Name: row.TableName.String, + Name: row.TableName, ColumnNames: colNames, ColumnTypes: colTypes, } @@ -164,7 +163,7 @@ func (tf *TypeFetcher) findCompositeTypes(ctx context.Context, uncached map[pgty return types, nil } -func (tf *TypeFetcher) findUnknownTypes(ctx context.Context, uncached map[pgtype.OID]struct{}) ([]UnknownType, error) { +func (tf *TypeFetcher) findUnknownTypes(ctx context.Context, uncached map[uint32]struct{}) ([]UnknownType, error) { oids := oidKeys(uncached) rows, err := tf.querier.FindOIDNames(ctx, oids) if err != nil { @@ -174,14 +173,14 @@ func (tf *TypeFetcher) findUnknownTypes(ctx context.Context, uncached map[pgtype for i, row := range rows { types[i] = UnknownType{ ID: row.OID, - Name: row.Name.String, - PgKind: TypeKind(row.Kind.Int), + Name: row.Name, + PgKind: TypeKind(row.Kind), } } return types, nil } -func (tf *TypeFetcher) findArrayTypes(ctx context.Context, uncached map[pgtype.OID]struct{}) ([]ArrayType, error) { +func (tf *TypeFetcher) findArrayTypes(ctx context.Context, uncached map[uint32]struct{}) ([]ArrayType, error) { oids := oidKeys(uncached) rows, err := tf.querier.FindArrayTypes(ctx, oids) if err != nil { @@ -204,7 +203,7 @@ func (tf *TypeFetcher) findArrayTypes(ctx context.Context, uncached map[pgtype.O // resolvePlaceholderTypes resolves all placeholder types or errors if we can't // resolve a placeholderType using all known types. -func (tf *TypeFetcher) resolvePlaceholderTypes(knownTypes map[pgtype.OID]Type) error { +func (tf *TypeFetcher) resolvePlaceholderTypes(knownTypes map[uint32]Type) error { // resolveType walks down type, replacing placeholderType with a known type. var resolveType func(typ Type) (Type, error) resolveType = func(typ Type) (Type, error) { @@ -246,7 +245,7 @@ func (tf *TypeFetcher) resolvePlaceholderTypes(knownTypes map[pgtype.OID]Type) e return nil } -func oidKeys(os map[pgtype.OID]struct{}) []uint32 { +func oidKeys(os map[uint32]struct{}) []uint32 { oids := make([]uint32, 0, len(os)) for oid := range os { oids = append(oids, uint32(oid)) diff --git a/internal/pg/type_fetcher_test.go b/internal/pg/type_fetcher_test.go index 08e7456f..43fc0077 100644 --- a/internal/pg/type_fetcher_test.go +++ b/internal/pg/type_fetcher_test.go @@ -4,7 +4,6 @@ import ( "context" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/jackc/pgtype" "github.com/jschaf/pggen/internal/pg/pgoid" "github.com/jschaf/pggen/internal/pgtest" "github.com/jschaf/pggen/internal/texts" @@ -248,7 +247,7 @@ func TestNewTypeFetcher(t *testing.T) { } // Get the OID by name if fetchOID was a string, or just return the OID. -func findOIDVal(t *testing.T, fetchOID interface{}, querier *DBQuerier) pgtype.OID { +func findOIDVal(t *testing.T, fetchOID interface{}, querier *DBQuerier) uint32 { switch rawOID := fetchOID.(type) { case string: oid, err := querier.FindOIDByName(context.Background(), rawOID) @@ -256,10 +255,10 @@ func findOIDVal(t *testing.T, fetchOID interface{}, querier *DBQuerier) pgtype.O t.Fatalf("find oid by name %s: %s", rawOID, err) } return oid - case pgtype.OID: + case uint32: return rawOID case int: - return pgtype.OID(rawOID) + return uint32(rawOID) default: t.Fatalf("unhandled oid test value type %T: %v", rawOID, rawOID) return 0 diff --git a/internal/pg/types.go b/internal/pg/types.go index b5b07af4..b662df8a 100644 --- a/internal/pg/types.go +++ b/internal/pg/types.go @@ -1,15 +1,14 @@ package pg import ( - "github.com/jackc/pgtype" "github.com/jschaf/pggen/internal/pg/pgoid" "strconv" ) // Type is a Postgres type. type Type interface { - OID() pgtype.OID // pg_type.oid: row identifier - String() string // pg_type.typname: data type name + OID() uint32 // pg_type.oid: row identifier + String() string // pg_type.typname: data type name Kind() TypeKind } @@ -49,8 +48,8 @@ type ( // BaseType is a fundamental Postgres type like text and bool. // https://www.postgresql.org/docs/13/catalog-pg-type.html BaseType struct { - ID pgtype.OID // pg_type.oid: row identifier - Name string // pg_type.typname: data type name + ID uint32 // pg_type.oid: row identifier + Name string // pg_type.typname: data type name } // VoidType is an empty type. A void type doesn't appear in output, but it's @@ -60,7 +59,7 @@ type ( // ArrayType is an array type where pg_type.typelem != 0 and the name begins // with an underscore. ArrayType struct { - ID pgtype.OID // pg_type.oid: row identifier + ID uint32 // pg_type.oid: row identifier // The name of the type, like _int4. Array types in Postgres typically // begin with an underscore. From pg_type.typname. Name string @@ -69,7 +68,7 @@ type ( } EnumType struct { - ID pgtype.OID // pg_type.oid: row identifier + ID uint32 // pg_type.oid: row identifier // The name of the enum, like 'device_type' in: // CREATE TYPE device_type AS ENUM ('foo'); // From pg_type.typname. @@ -82,26 +81,26 @@ type ( // values is that they be correctly ordered and unique within each enum // type. Orders []float32 - ChildOIDs []pgtype.OID + ChildOIDs []uint32 } // DomainType is a user-create domain type. DomainType struct { - ID pgtype.OID // pg_type.oid: row identifier - Name string // pg_type.typname: data type name - IsNotNull bool // pg_type.typnotnull: domains only, not null constraint for domains - HasDefault bool // pg_type.typdefault: domains only, if there's a default value - BaseType BaseType // pg_type.typbasetype: domains only, the base type - Dimensions int // pg_type.typndims: domains on array type only, 0 otherwise, number of array dimensions + ID uint32 // pg_type.oid: row identifier + Name string // pg_type.typname: data type name + IsNotNull bool // pg_type.typnotnull: domains only, not null constraint for domains + HasDefault bool // pg_type.typdefault: domains only, if there's a default value + BaseType BaseType // pg_type.typbasetype: domains only, the base type + Dimensions int // pg_type.typndims: domains on array type only, 0 otherwise, number of array dimensions } // CompositeType is a type containing multiple columns and is represented as // a class. https://www.postgresql.org/docs/13/catalog-pg-class.html CompositeType struct { - ID pgtype.OID // pg_class.oid: row identifier - Name string // pg_class.relname: name of the composite type - ColumnNames []string // pg_attribute.attname: names of the column, in order - ColumnTypes []Type // pg_attribute JOIN pg_type: information about columns of the composite type + ID uint32 // pg_class.oid: row identifier + Name string // pg_class.relname: name of the composite type + ColumnNames []string // pg_attribute.attname: names of the column, in order + ColumnTypes []Type // pg_attribute JOIN pg_type: information about columns of the composite type } // UnknownType is a Postgres type that's not a well-known type in @@ -109,8 +108,8 @@ type ( // generator might be able to resolve this type from a user-provided mapping // like --go-type my_int=int. UnknownType struct { - ID pgtype.OID // pg_type.oid: row identifier - Name string // pg_type.typname: data type name + ID uint32 // pg_type.oid: row identifier + Name string // pg_type.typname: data type name PgKind TypeKind } @@ -120,38 +119,38 @@ type ( // requires two passes for cases like when a composite type has a child type // that's an array. placeholderType struct { - ID pgtype.OID // pg_type.oid: row identifier + ID uint32 // pg_type.oid: row identifier } ) -func (b BaseType) OID() pgtype.OID { return b.ID } -func (b BaseType) String() string { return b.Name } -func (b BaseType) Kind() TypeKind { return KindBaseType } +func (b BaseType) OID() uint32 { return b.ID } +func (b BaseType) String() string { return b.Name } +func (b BaseType) Kind() TypeKind { return KindBaseType } -func (b VoidType) OID() pgtype.OID { return pgoid.Void } -func (b VoidType) String() string { return "void" } -func (b VoidType) Kind() TypeKind { return KindPseudoType } +func (b VoidType) OID() uint32 { return pgoid.Void } +func (b VoidType) String() string { return "void" } +func (b VoidType) Kind() TypeKind { return KindPseudoType } -func (b ArrayType) OID() pgtype.OID { return b.ID } -func (b ArrayType) String() string { return b.Name } -func (b ArrayType) Kind() TypeKind { return KindBaseType } +func (b ArrayType) OID() uint32 { return b.ID } +func (b ArrayType) String() string { return b.Name } +func (b ArrayType) Kind() TypeKind { return KindBaseType } -func (e EnumType) OID() pgtype.OID { return e.ID } -func (e EnumType) String() string { return e.Name } -func (e EnumType) Kind() TypeKind { return KindEnumType } +func (e EnumType) OID() uint32 { return e.ID } +func (e EnumType) String() string { return e.Name } +func (e EnumType) Kind() TypeKind { return KindEnumType } -func (e DomainType) OID() pgtype.OID { return e.ID } -func (e DomainType) String() string { return e.Name } -func (e DomainType) Kind() TypeKind { return KindDomainType } +func (e DomainType) OID() uint32 { return e.ID } +func (e DomainType) String() string { return e.Name } +func (e DomainType) Kind() TypeKind { return KindDomainType } -func (e CompositeType) OID() pgtype.OID { return e.ID } -func (e CompositeType) String() string { return e.Name } -func (e CompositeType) Kind() TypeKind { return KindCompositeType } +func (e CompositeType) OID() uint32 { return e.ID } +func (e CompositeType) String() string { return e.Name } +func (e CompositeType) Kind() TypeKind { return KindCompositeType } -func (e UnknownType) OID() pgtype.OID { return e.ID } -func (e UnknownType) String() string { return e.Name } -func (e UnknownType) Kind() TypeKind { return e.PgKind } +func (e UnknownType) OID() uint32 { return e.ID } +func (e UnknownType) String() string { return e.Name } +func (e UnknownType) Kind() TypeKind { return e.PgKind } -func (p placeholderType) OID() pgtype.OID { return p.ID } -func (p placeholderType) String() string { return "placeholder-" + strconv.Itoa(int(p.ID)) } -func (p placeholderType) Kind() TypeKind { return kindPlaceholderType } +func (p placeholderType) OID() uint32 { return p.ID } +func (p placeholderType) String() string { return "placeholder-" + strconv.Itoa(int(p.ID)) } +func (p placeholderType) Kind() TypeKind { return kindPlaceholderType } diff --git a/internal/pgdocker/pgdocker.go b/internal/pgdocker/pgdocker.go index 1f4a1ce6..0d629932 100644 --- a/internal/pgdocker/pgdocker.go +++ b/internal/pgdocker/pgdocker.go @@ -12,7 +12,7 @@ import ( "github.com/docker/docker/api/types/container" dockerClient "github.com/docker/docker/client" "github.com/docker/go-connections/nat" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" "github.com/jschaf/pggen/internal/errs" "github.com/jschaf/pggen/internal/ports" "io" diff --git a/internal/pginfer/pginfer.go b/internal/pginfer/pginfer.go index 82de7f24..bcb46d70 100644 --- a/internal/pginfer/pginfer.go +++ b/internal/pginfer/pginfer.go @@ -3,13 +3,11 @@ package pginfer import ( "context" "fmt" - "github.com/jackc/pgconn" + "github.com/jackc/pgx/v5/pgconn" "strings" "time" - "github.com/jackc/pgproto3/v2" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" "github.com/jschaf/pggen/internal/ast" "github.com/jschaf/pggen/internal/pg" ) @@ -157,7 +155,7 @@ func (inf *Inferrer) prepareTypes(query *ast.SourceQuery) (_a []InputParam, _ [] return nil, nil, fmt.Errorf("fetch oid types: %w", err) } for i, oid := range stmtDesc.ParamOIDs { - inputType, ok := types[pgtype.OID(oid)] + inputType, ok := types[uint32(oid)] if !ok { return nil, nil, fmt.Errorf("no postgres type name found for parameter %s with oid %d", query.ParamNames[i], oid) } @@ -187,7 +185,7 @@ func (inf *Inferrer) prepareTypes(query *ast.SourceQuery) (_a []InputParam, _ [] // Create output columns var outputColumns []OutputColumn for i, desc := range stmtDesc.Fields { - pgType, ok := outputTypes[pgtype.OID(desc.DataTypeOID)] + pgType, ok := outputTypes[uint32(desc.DataTypeOID)] if !ok { return nil, nil, fmt.Errorf("no postgrestype name found for column %s with oid %d", string(desc.Name), desc.DataTypeOID) } @@ -202,7 +200,7 @@ func (inf *Inferrer) prepareTypes(query *ast.SourceQuery) (_a []InputParam, _ [] // inferOutputNullability infers which of the output columns produced by the // query and described by descs can be null. -func (inf *Inferrer) inferOutputNullability(query *ast.SourceQuery, descs []pgproto3.FieldDescription) ([]bool, error) { +func (inf *Inferrer) inferOutputNullability(query *ast.SourceQuery, descs []pgconn.FieldDescription) ([]bool, error) { if len(descs) == 0 { return nil, nil } @@ -215,7 +213,7 @@ func (inf *Inferrer) inferOutputNullability(query *ast.SourceQuery, descs []pgpr for i, desc := range descs { if desc.TableOID > 0 { columnKeys[i] = pg.ColumnKey{ - TableOID: pgtype.OID(desc.TableOID), + TableOID: uint32(desc.TableOID), Number: desc.TableAttributeNumber, } } diff --git a/internal/pgplan/pgplan.go b/internal/pgplan/pgplan.go index 18294505..d7e69db3 100644 --- a/internal/pgplan/pgplan.go +++ b/internal/pgplan/pgplan.go @@ -3,7 +3,7 @@ package pgplan import ( "context" "fmt" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" "time" ) diff --git a/internal/pgtest/pg_test_db.go b/internal/pgtest/pg_test_db.go index e0905fd0..62c1a986 100644 --- a/internal/pgtest/pg_test_db.go +++ b/internal/pgtest/pg_test_db.go @@ -2,7 +2,7 @@ package pgtest import ( "context" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" "math/rand" "os" "strconv" From 4bb40e2b95c380ce9d386688af28a408521121d8 Mon Sep 17 00:00:00 2001 From: Joe Schafer Date: Fri, 5 Jan 2024 15:12:55 -0800 Subject: [PATCH 02/27] experiment with codecs --- example/author/query.sql.go | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/example/author/query.sql.go b/example/author/query.sql.go index 2b892bec..46224828 100644 --- a/example/author/query.sql.go +++ b/example/author/query.sql.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // Querier is a typesafe Go interface backed by SQL queries. @@ -80,6 +81,7 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthorByID") row := q.conn.QueryRow(ctx, findAuthorByIDSQL, authorID) var item FindAuthorByIDRow + s := scanner[*FindAuthorByIDRow]{&item} if err := row.Scan(s); err != nil { return item, fmt.Errorf("query FindAuthorByID: %w", err) @@ -108,13 +110,32 @@ func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAu return nil, fmt.Errorf("query FindAuthors: %w", err) } defer rows.Close() + + fds := rows.FieldDescriptions() + + int4Codec := pgtype.Int4Codec{} + textCodec := &pgtype.TextCodec{} + + scan0 := int4Codec.PlanScan(nil, fds[0].DataTypeOID, fds[0].Format, (*int32)(nil)) + scan1 := textCodec.PlanScan(nil, fds[1].DataTypeOID, fds[1].Format, (*string)(nil)) + scan2 := textCodec.PlanScan(nil, fds[2].DataTypeOID, fds[2].Format, (*string)(nil)) + scan3 := textCodec.PlanScan(nil, fds[2].DataTypeOID, fds[3].Format, (*string)(nil)) + items := []FindAuthorsRow{} - s := scanner[*FindAuthorsRow]{} for rows.Next() { + vals := rows.RawValues() var item FindAuthorsRow - s.item = &item - if err := rows.Scan(s); err != nil { - return nil, fmt.Errorf("scan FindAuthors row: %w", err) + if err := scan0.Scan(vals[0], &item.AuthorID); err != nil { + return nil, fmt.Errorf("scan FindAuthors row author_id column: %w", err) + } + if err := scan1.Scan(vals[1], &item.FirstName); err != nil { + return nil, fmt.Errorf("scan FindAuthors row first_name column: %w", err) + } + if err := scan2.Scan(vals[2], &item.LastName); err != nil { + return nil, fmt.Errorf("scan FindAuthors row last_name column: %w", err) + } + if err := scan3.Scan(vals[3], item.Suffix); err != nil { + return nil, fmt.Errorf("scan FindAuthors row suffix column: %w", err) } items = append(items, item) } From 500b5c6be650536cc5b4cb45a13c45002d6d04ca Mon Sep 17 00:00:00 2001 From: Joe Schafer Date: Fri, 5 Jan 2024 17:45:35 -0800 Subject: [PATCH 03/27] More experiments with codecs --- example/author/query.sql.go | 86 ++++++++++++----- example/author/query.sql_test.go | 19 ++++ go.mod | 7 -- go.sum | 159 ------------------------------- 4 files changed, 81 insertions(+), 190 deletions(-) diff --git a/example/author/query.sql.go b/example/author/query.sql.go index 46224828..283cfa9a 100644 --- a/example/author/query.sql.go +++ b/example/author/query.sql.go @@ -102,6 +102,54 @@ func (r *FindAuthorsRow) scanRow(rows pgx.Rows) error { return rows.Scan(&r.AuthorID, &r.FirstName, &r.LastName, &r.Suffix) } +type scanCacheKey struct { + oid uint32 + format int16 + typeName string +} + +type scanPlan[T any] interface { + Scan([]byte, T) error +} + +var scanCache = map[string]scanPlan[any]{} + +func planScan[T any](codec pgtype.Codec, fd pgconn.FieldDescription) scanPlan[*T] { + var target *T + key := scanCacheKey{fd.DataTypeOID, fd.Format, fmt.Sprintf("%T", target)} + // TODO: synchronize + if plan, ok := scanCache[key.typeName]; ok { + return plan.(scanPlan[*T]) + } + plan := codec.PlanScan(nil, fd.DataTypeOID, fd.Format, target) + scanCache[key.typeName] = plan + return plan.(scanPlan[*T]) +} + +type ptrScanner[T any] struct { + basePlan scanPlan[T] +} + +func (s ptrScanner[T]) Scan(src []byte, dst **T) error { + if src == nil { + return nil + } + *dst = new(T) + return s.basePlan.Scan(src, *dst) +} + +func planPtrScan[T any](codec pgtype.Codec, fd pgconn.FieldDescription) scanPlan[**T] { + var target *T + key := scanCacheKey{fd.DataTypeOID, fd.Format, fmt.Sprintf("*%T", target)} + if scan, ok := scanCache[key.typeName]; ok { + return scan.(scanPlan[**T]) + } + basePlan := planScan[T](codec, fd) + var ptrPlan scanPlan[**T] = ptrScanner[T]{basePlan: basePlan.(scanPlan[T])} + scanCache[key.typeName] = ptrPlan.(scanPlan[any]) + return ptrPlan +} + // FindAuthors implements Querier.FindAuthors. func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAuthorsRow, error) { ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthors") @@ -112,30 +160,26 @@ func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAu defer rows.Close() fds := rows.FieldDescriptions() - - int4Codec := pgtype.Int4Codec{} - textCodec := &pgtype.TextCodec{} - - scan0 := int4Codec.PlanScan(nil, fds[0].DataTypeOID, fds[0].Format, (*int32)(nil)) - scan1 := textCodec.PlanScan(nil, fds[1].DataTypeOID, fds[1].Format, (*string)(nil)) - scan2 := textCodec.PlanScan(nil, fds[2].DataTypeOID, fds[2].Format, (*string)(nil)) - scan3 := textCodec.PlanScan(nil, fds[2].DataTypeOID, fds[3].Format, (*string)(nil)) + var plan0 = planScan[int32](pgtype.Int4Codec{}, fds[0]) + var plan1 = planScan[string](pgtype.TextCodec{}, fds[1]) + var plan2 = plan1 + var plan3 = planPtrScan[string](pgtype.TextCodec{}, fds[3]) items := []FindAuthorsRow{} for rows.Next() { vals := rows.RawValues() var item FindAuthorsRow - if err := scan0.Scan(vals[0], &item.AuthorID); err != nil { - return nil, fmt.Errorf("scan FindAuthors row author_id column: %w", err) + if err := plan0.Scan(vals[0], &item.AuthorID); err != nil { + return nil, fmt.Errorf("scan FindAuthors.author_id column: %w", err) } - if err := scan1.Scan(vals[1], &item.FirstName); err != nil { - return nil, fmt.Errorf("scan FindAuthors row first_name column: %w", err) + if err := plan1.Scan(vals[1], &item.FirstName); err != nil { + return nil, fmt.Errorf("scan FindAuthors.first_name column: %w", err) } - if err := scan2.Scan(vals[2], &item.LastName); err != nil { - return nil, fmt.Errorf("scan FindAuthors row last_name column: %w", err) + if err := plan2.Scan(vals[2], &item.LastName); err != nil { + return nil, fmt.Errorf("scan FindAuthors.last_name column: %w", err) } - if err := scan3.Scan(vals[3], item.Suffix); err != nil { - return nil, fmt.Errorf("scan FindAuthors row suffix column: %w", err) + if err := plan3.Scan(vals[3], &item.Suffix); err != nil { + return nil, fmt.Errorf("scan FindAuthors.suffix column: %w", err) } items = append(items, item) } @@ -298,7 +342,7 @@ func (q *DBQuerier) InsertAuthorSuffix(ctx context.Context, params InsertAuthorS return item, nil } -const stringAggFirstNameSQL = `SELECT string_agg(first_name, ',') AS names FROM author WHERE author_id = $1;` +const stringAggFirstNameSQL = `SELECT string_agg(first_name, ',') AS NAMES FROM author WHERE author_id = $1;` // StringAggFirstName implements Querier.StringAggFirstName. func (q *DBQuerier) StringAggFirstName(ctx context.Context, authorID int32) (*string, error) { @@ -311,7 +355,7 @@ func (q *DBQuerier) StringAggFirstName(ctx context.Context, authorID int32) (*st return item, nil } -const arrayAggFirstNameSQL = `SELECT array_agg(first_name) AS names FROM author WHERE author_id = $1;` +const arrayAggFirstNameSQL = `SELECT array_agg(first_name) AS NAMES FROM author WHERE author_id = $1;` // ArrayAggFirstName implements Querier.ArrayAggFirstName. func (q *DBQuerier) ArrayAggFirstName(ctx context.Context, authorID int32) ([]string, error) { @@ -323,9 +367,3 @@ func (q *DBQuerier) ArrayAggFirstName(ctx context.Context, authorID int32) ([]st } return item, nil } - -type rowScanner interface{ scanRow(rows pgx.Rows) error } - -type scanner[T rowScanner] struct{ item T } - -func (s scanner[T]) ScanRow(rows pgx.Rows) error { return s.item.scanRow(rows) } diff --git a/example/author/query.sql_test.go b/example/author/query.sql_test.go index 55521e45..fc1199e7 100644 --- a/example/author/query.sql_test.go +++ b/example/author/query.sql_test.go @@ -63,6 +63,25 @@ func TestNewQuerier_FindAuthors(t *testing.T) { assert.Equal(t, want, authors) }) + t.Run("FindAuthors - 1 row suffix - bill", func(t *testing.T) { + insRow, err := q.InsertAuthorSuffix(context.Background(), InsertAuthorSuffixParams{ + FirstName: "bill", + LastName: "clinton", + Suffix: "jr", + }) + authors, err := q.FindAuthors(context.Background(), "bill") + require.NoError(t, err) + want := []FindAuthorsRow{ + { + AuthorID: insRow.AuthorID, + FirstName: "bill", + LastName: "clinton", + Suffix: ptrs.String("jr"), + }, + } + assert.Equal(t, want, authors) + }) + t.Run("FindAuthors - 2 rows - george", func(t *testing.T) { authors, err := q.FindAuthors(context.Background(), "george") require.NoError(t, err) diff --git a/go.mod b/go.mod index db08d690..af5672d9 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,8 @@ require ( github.com/docker/docker v24.0.7+incompatible github.com/docker/go-connections v0.4.0 github.com/google/go-cmp v0.6.0 - github.com/jackc/pgconn v1.14.1 - github.com/jackc/pgproto3/v2 v2.3.2 - github.com/jackc/pgtype v1.14.0 - github.com/jackc/pgx/v4 v4.18.1 github.com/jackc/pgx/v5 v5.5.1 github.com/peterbourgon/ff/v3 v3.4.0 - github.com/shopspring/decimal v1.3.1 github.com/stretchr/testify v1.8.4 golang.org/x/mod v0.11.0 ) @@ -24,8 +19,6 @@ require ( github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect github.com/kr/text v0.2.0 // indirect diff --git a/go.sum b/go.sum index 28462699..5bd0bf93 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,9 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -24,95 +17,29 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= -github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= -github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= -github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI= github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -128,139 +55,56 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -270,12 +114,9 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= From 8e58ee07478620e863ba20506fb978877d24a060 Mon Sep 17 00:00:00 2001 From: Joe Schafer Date: Wed, 24 Jan 2024 22:16:37 -0800 Subject: [PATCH 04/27] Simplify codec --- example/author/query.sql.go | 152 ++++++++++++++++++++---------------- 1 file changed, 85 insertions(+), 67 deletions(-) diff --git a/example/author/query.sql.go b/example/author/query.sql.go index 283cfa9a..f84070f3 100644 --- a/example/author/query.sql.go +++ b/example/author/query.sql.go @@ -8,6 +8,7 @@ import ( "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgtype" + "sync" ) // Querier is a typesafe Go interface backed by SQL queries. @@ -79,13 +80,14 @@ func (r *FindAuthorByIDRow) scanRow(rows pgx.Rows) error { // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthorByID") - row := q.conn.QueryRow(ctx, findAuthorByIDSQL, authorID) + //row := q.conn.QueryRow(ctx, findAuthorByIDSQL, authorID) var item FindAuthorByIDRow - s := scanner[*FindAuthorByIDRow]{&item} - if err := row.Scan(s); err != nil { - return item, fmt.Errorf("query FindAuthorByID: %w", err) - } + //s := scanner[*FindAuthorByIDRow]{&item} + //if err := row.Scan(s); err != nil { + // return item, fmt.Errorf("query FindAuthorByID: %w", err) + //} + //return item, nil return item, nil } @@ -102,54 +104,6 @@ func (r *FindAuthorsRow) scanRow(rows pgx.Rows) error { return rows.Scan(&r.AuthorID, &r.FirstName, &r.LastName, &r.Suffix) } -type scanCacheKey struct { - oid uint32 - format int16 - typeName string -} - -type scanPlan[T any] interface { - Scan([]byte, T) error -} - -var scanCache = map[string]scanPlan[any]{} - -func planScan[T any](codec pgtype.Codec, fd pgconn.FieldDescription) scanPlan[*T] { - var target *T - key := scanCacheKey{fd.DataTypeOID, fd.Format, fmt.Sprintf("%T", target)} - // TODO: synchronize - if plan, ok := scanCache[key.typeName]; ok { - return plan.(scanPlan[*T]) - } - plan := codec.PlanScan(nil, fd.DataTypeOID, fd.Format, target) - scanCache[key.typeName] = plan - return plan.(scanPlan[*T]) -} - -type ptrScanner[T any] struct { - basePlan scanPlan[T] -} - -func (s ptrScanner[T]) Scan(src []byte, dst **T) error { - if src == nil { - return nil - } - *dst = new(T) - return s.basePlan.Scan(src, *dst) -} - -func planPtrScan[T any](codec pgtype.Codec, fd pgconn.FieldDescription) scanPlan[**T] { - var target *T - key := scanCacheKey{fd.DataTypeOID, fd.Format, fmt.Sprintf("*%T", target)} - if scan, ok := scanCache[key.typeName]; ok { - return scan.(scanPlan[**T]) - } - basePlan := planScan[T](codec, fd) - var ptrPlan scanPlan[**T] = ptrScanner[T]{basePlan: basePlan.(scanPlan[T])} - scanCache[key.typeName] = ptrPlan.(scanPlan[any]) - return ptrPlan -} - // FindAuthors implements Querier.FindAuthors. func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAuthorsRow, error) { ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthors") @@ -160,10 +114,10 @@ func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAu defer rows.Close() fds := rows.FieldDescriptions() - var plan0 = planScan[int32](pgtype.Int4Codec{}, fds[0]) - var plan1 = planScan[string](pgtype.TextCodec{}, fds[1]) - var plan2 = plan1 - var plan3 = planPtrScan[string](pgtype.TextCodec{}, fds[3]) + plan0 := planScan(pgtype.Int4Codec{}, fds[0], (*int32)(nil)) + plan1 := planScan(pgtype.TextCodec{}, fds[1], (*string)(nil)) + plan2 := planScan(pgtype.TextCodec{}, fds[2], (*string)(nil)) + plan3 := planPtrScan(pgtype.TextCodec{}, fds[3], (*string)(nil)) items := []FindAuthorsRow{} for rows.Next() { @@ -208,14 +162,21 @@ func (q *DBQuerier) FindAuthorNames(ctx context.Context, authorID int32) ([]Find return nil, fmt.Errorf("query FindAuthorNames: %w", err) } defer rows.Close() + + //fds := rows.FieldDescriptions() + //plan0 := planPtrScan[string](pgtype.TextCodec{}, fds[0]) + //plan1 := planPtrScan[string](pgtype.TextCodec{}, fds[1]) + items := []FindAuthorNamesRow{} - s := scanner[*FindAuthorNamesRow]{} for rows.Next() { + //vals := rows.RawValues() var item FindAuthorNamesRow - s.item = &item - if err := rows.Scan(s); err != nil { - return nil, fmt.Errorf("scan FindAuthorNames row: %w", err) - } + //if err := plan0.Scan(vals[0], &item.FirstName); err != nil { + // return nil, fmt.Errorf("scan FindAuthors.first_name column: %w", err) + //} + //if err := plan1.Scan(vals[1], &item.FirstName); err != nil { + // return nil, fmt.Errorf("scan FindAuthors.last_name column: %w", err) + //} items = append(items, item) } if err := rows.Err(); err != nil { @@ -333,13 +294,15 @@ func (r *InsertAuthorSuffixRow) scanRow(rows pgx.Rows) error { // InsertAuthorSuffix implements Querier.InsertAuthorSuffix. func (q *DBQuerier) InsertAuthorSuffix(ctx context.Context, params InsertAuthorSuffixParams) (InsertAuthorSuffixRow, error) { ctx = context.WithValue(ctx, "pggen_query_name", "InsertAuthorSuffix") - row := q.conn.QueryRow(ctx, insertAuthorSuffixSQL, params.FirstName, params.LastName, params.Suffix) + //row := q.conn.QueryRow(ctx, insertAuthorSuffixSQL, params.FirstName, params.LastName, params.Suffix) var item InsertAuthorSuffixRow - s := scanner[*InsertAuthorSuffixRow]{&item} - if err := row.Scan(s); err != nil { - return item, fmt.Errorf("query InsertAuthorSuffix: %w", err) - } return item, nil + // + //s := scanner[*InsertAuthorSuffixRow]{&item} + //if err := row.Scan(s); err != nil { + // return item, fmt.Errorf("query InsertAuthorSuffix: %w", err) + //} + //return item, nil } const stringAggFirstNameSQL = `SELECT string_agg(first_name, ',') AS NAMES FROM author WHERE author_id = $1;` @@ -367,3 +330,58 @@ func (q *DBQuerier) ArrayAggFirstName(ctx context.Context, authorID int32) ([]st } return item, nil } + +type scanCacheKey struct { + oid uint32 + format int16 + typeName string +} + +var ( + plans = make(map[scanCacheKey]pgtype.ScanPlan, 16) + plansMu sync.RWMutex +) + +func planScan(codec pgtype.Codec, fd pgconn.FieldDescription, target any) pgtype.ScanPlan { + key := scanCacheKey{fd.DataTypeOID, fd.Format, fmt.Sprintf("%T", target)} + plansMu.RLock() + plan := plans[key] + plansMu.RUnlock() + if plan != nil { + return plan + } + plan = codec.PlanScan(nil, fd.DataTypeOID, fd.Format, target) + plansMu.Lock() + plans[key] = plan + plansMu.Unlock() + return plan +} + +type ptrScanner[T any] struct { + basePlan pgtype.ScanPlan +} + +func (s ptrScanner[T]) Scan(src []byte, dst any) error { + if src == nil { + return nil + } + d := dst.(**T) + *d = new(T) + return s.basePlan.Scan(src, **d) +} + +func planPtrScan[T any](codec pgtype.Codec, fd pgconn.FieldDescription, target *T) pgtype.ScanPlan { + key := scanCacheKey{fd.DataTypeOID, fd.Format, fmt.Sprintf("*%T", target)} + plansMu.RLock() + plan := plans[key] + plansMu.RUnlock() + if plan != nil { + return plan + } + basePlan := planScan(codec, fd, target) + ptrPlan := ptrScanner[T]{basePlan} + plansMu.Lock() + plans[key] = plan + plansMu.Unlock() + return ptrPlan +} From 1b29c21c3998c76af787ed3cd089593aaaaa4f68 Mon Sep 17 00:00:00 2001 From: Joe Schafer Date: Wed, 24 Jan 2024 23:21:16 -0800 Subject: [PATCH 05/27] fix most author example tests --- example/author/query.sql.go | 217 ++++++++++++++++++++++-------------- 1 file changed, 132 insertions(+), 85 deletions(-) diff --git a/example/author/query.sql.go b/example/author/query.sql.go index f84070f3..6dd22144 100644 --- a/example/author/query.sql.go +++ b/example/author/query.sql.go @@ -73,22 +73,37 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix"` } -func (r *FindAuthorByIDRow) scanRow(rows pgx.Rows) error { - return rows.Scan(&r.AuthorID, &r.FirstName, &r.LastName, &r.Suffix) -} - // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthorByID") - //row := q.conn.QueryRow(ctx, findAuthorByIDSQL, authorID) - var item FindAuthorByIDRow - - //s := scanner[*FindAuthorByIDRow]{&item} - //if err := row.Scan(s); err != nil { - // return item, fmt.Errorf("query FindAuthorByID: %w", err) - //} - //return item, nil - return item, nil + rows, err := q.conn.Query(ctx, findAuthorByIDSQL, authorID) + if err != nil { + return FindAuthorByIDRow{}, fmt.Errorf("query FindAuthorByID: %w", err) + } + + fds := rows.FieldDescriptions() + plan0 := planScan(pgtype.Int4Codec{}, fds[0], (*int32)(nil)) + plan1 := planScan(pgtype.TextCodec{}, fds[1], (*string)(nil)) + plan2 := planScan(pgtype.TextCodec{}, fds[2], (*string)(nil)) + plan3 := planPtrScan(pgtype.TextCodec{}, fds[3], (*string)(nil)) + + return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (FindAuthorByIDRow, error) { + vals := row.RawValues() + var item FindAuthorByIDRow + if err := plan0.Scan(vals[0], &item.AuthorID); err != nil { + return item, fmt.Errorf("scan FindAuthors.author_id column: %w", err) + } + if err := plan1.Scan(vals[1], &item.FirstName); err != nil { + return item, fmt.Errorf("scan FindAuthors.first_name column: %w", err) + } + if err := plan2.Scan(vals[2], &item.LastName); err != nil { + return item, fmt.Errorf("scan FindAuthors.last_name column: %w", err) + } + if err := plan3.Scan(vals[3], &item.Suffix); err != nil { + return item, fmt.Errorf("scan FindAuthors.suffix column: %w", err) + } + return item, nil + }) } const findAuthorsSQL = `SELECT * FROM author WHERE first_name = $1;` @@ -100,10 +115,6 @@ type FindAuthorsRow struct { Suffix *string `json:"suffix"` } -func (r *FindAuthorsRow) scanRow(rows pgx.Rows) error { - return rows.Scan(&r.AuthorID, &r.FirstName, &r.LastName, &r.Suffix) -} - // FindAuthors implements Querier.FindAuthors. func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAuthorsRow, error) { ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthors") @@ -111,7 +122,6 @@ func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAu if err != nil { return nil, fmt.Errorf("query FindAuthors: %w", err) } - defer rows.Close() fds := rows.FieldDescriptions() plan0 := planScan(pgtype.Int4Codec{}, fds[0], (*int32)(nil)) @@ -119,28 +129,23 @@ func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAu plan2 := planScan(pgtype.TextCodec{}, fds[2], (*string)(nil)) plan3 := planPtrScan(pgtype.TextCodec{}, fds[3], (*string)(nil)) - items := []FindAuthorsRow{} - for rows.Next() { - vals := rows.RawValues() + return pgx.CollectRows(rows, func(row pgx.CollectableRow) (FindAuthorsRow, error) { + vals := row.RawValues() var item FindAuthorsRow if err := plan0.Scan(vals[0], &item.AuthorID); err != nil { - return nil, fmt.Errorf("scan FindAuthors.author_id column: %w", err) + return item, fmt.Errorf("scan FindAuthors.author_id column: %w", err) } if err := plan1.Scan(vals[1], &item.FirstName); err != nil { - return nil, fmt.Errorf("scan FindAuthors.first_name column: %w", err) + return item, fmt.Errorf("scan FindAuthors.first_name column: %w", err) } if err := plan2.Scan(vals[2], &item.LastName); err != nil { - return nil, fmt.Errorf("scan FindAuthors.last_name column: %w", err) + return item, fmt.Errorf("scan FindAuthors.last_name column: %w", err) } if err := plan3.Scan(vals[3], &item.Suffix); err != nil { - return nil, fmt.Errorf("scan FindAuthors.suffix column: %w", err) + return item, fmt.Errorf("scan FindAuthors.suffix column: %w", err) } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindAuthors rows: %w", err) - } - return items, err + return item, nil + }) } const findAuthorNamesSQL = `SELECT first_name, last_name FROM author ORDER BY author_id = $1;` @@ -161,28 +166,22 @@ func (q *DBQuerier) FindAuthorNames(ctx context.Context, authorID int32) ([]Find if err != nil { return nil, fmt.Errorf("query FindAuthorNames: %w", err) } - defer rows.Close() - //fds := rows.FieldDescriptions() - //plan0 := planPtrScan[string](pgtype.TextCodec{}, fds[0]) - //plan1 := planPtrScan[string](pgtype.TextCodec{}, fds[1]) + fds := rows.FieldDescriptions() + plan0 := planScan(pgtype.TextCodec{}, fds[0], (*string)(nil)) + plan1 := planScan(pgtype.TextCodec{}, fds[1], (*string)(nil)) - items := []FindAuthorNamesRow{} - for rows.Next() { - //vals := rows.RawValues() + return pgx.CollectRows(rows, func(row pgx.CollectableRow) (FindAuthorNamesRow, error) { + vals := row.RawValues() var item FindAuthorNamesRow - //if err := plan0.Scan(vals[0], &item.FirstName); err != nil { - // return nil, fmt.Errorf("scan FindAuthors.first_name column: %w", err) - //} - //if err := plan1.Scan(vals[1], &item.FirstName); err != nil { - // return nil, fmt.Errorf("scan FindAuthors.last_name column: %w", err) - //} - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindAuthorNames rows: %w", err) - } - return items, err + if err := plan0.Scan(vals[0], &item.FirstName); err != nil { + return item, fmt.Errorf("scan FindAuthors.first_name column: %w", err) + } + if err := plan1.Scan(vals[1], &item.LastName); err != nil { + return item, fmt.Errorf("scan FindAuthors.last_name column: %w", err) + } + return item, nil + }) } const findFirstNamesSQL = `SELECT first_name FROM author ORDER BY author_id = $1;` @@ -194,19 +193,18 @@ func (q *DBQuerier) FindFirstNames(ctx context.Context, authorID int32) ([]*stri if err != nil { return nil, fmt.Errorf("query FindFirstNames: %w", err) } - defer rows.Close() - items := []*string{} - for rows.Next() { + + fds := rows.FieldDescriptions() + plan0 := planScan(pgtype.TextCodec{}, fds[0], (*string)(nil)) + + return pgx.CollectRows(rows, func(row pgx.CollectableRow) (*string, error) { + vals := row.RawValues() var item string - if err := rows.Scan(&item); err != nil { + if err := plan0.Scan(vals[0], &item); err != nil { return nil, fmt.Errorf("scan FindFirstNames row: %w", err) } - items = append(items, &item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindFirstNames rows: %w", err) - } - return items, err + return &item, nil + }) } const deleteAuthorsSQL = `DELETE FROM author WHERE first_name = 'joe';` @@ -262,12 +260,22 @@ RETURNING author_id;` // InsertAuthor implements Querier.InsertAuthor. func (q *DBQuerier) InsertAuthor(ctx context.Context, firstName string, lastName string) (int32, error) { ctx = context.WithValue(ctx, "pggen_query_name", "InsertAuthor") - row := q.conn.QueryRow(ctx, insertAuthorSQL, firstName, lastName) - var item int32 - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query InsertAuthor: %w", err) + rows, err := q.conn.Query(ctx, insertAuthorSQL, firstName, lastName) + if err != nil { + return 0, fmt.Errorf("query InsertAuthor: %w", err) } - return item, nil + + fds := rows.FieldDescriptions() + plan0 := planScan(pgtype.Int4Codec{}, fds[0], (*int32)(nil)) + + return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (int32, error) { + vals := row.RawValues() + var item int32 + if err := plan0.Scan(vals[0], &item); err != nil { + return item, fmt.Errorf("scan InsertAuthor: %w", err) + } + return item, nil + }) } const insertAuthorSuffixSQL = `INSERT INTO author (first_name, last_name, suffix) @@ -294,15 +302,34 @@ func (r *InsertAuthorSuffixRow) scanRow(rows pgx.Rows) error { // InsertAuthorSuffix implements Querier.InsertAuthorSuffix. func (q *DBQuerier) InsertAuthorSuffix(ctx context.Context, params InsertAuthorSuffixParams) (InsertAuthorSuffixRow, error) { ctx = context.WithValue(ctx, "pggen_query_name", "InsertAuthorSuffix") - //row := q.conn.QueryRow(ctx, insertAuthorSuffixSQL, params.FirstName, params.LastName, params.Suffix) - var item InsertAuthorSuffixRow - return item, nil - // - //s := scanner[*InsertAuthorSuffixRow]{&item} - //if err := row.Scan(s); err != nil { - // return item, fmt.Errorf("query InsertAuthorSuffix: %w", err) - //} - //return item, nil + rows, err := q.conn.Query(ctx, insertAuthorSuffixSQL, params.FirstName, params.LastName, params.Suffix) + if err != nil { + return InsertAuthorSuffixRow{}, fmt.Errorf("query InsertAuthorSuffix: %w", err) + } + + fds := rows.FieldDescriptions() + plan0 := planScan(pgtype.Int4Codec{}, fds[0], (*int32)(nil)) + plan1 := planScan(pgtype.TextCodec{}, fds[1], (*string)(nil)) + plan2 := planScan(pgtype.TextCodec{}, fds[2], (*string)(nil)) + plan3 := planPtrScan(pgtype.TextCodec{}, fds[3], (*string)(nil)) + + return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (InsertAuthorSuffixRow, error) { + vals := row.RawValues() + var item InsertAuthorSuffixRow + if err := plan0.Scan(vals[0], &item.AuthorID); err != nil { + return item, fmt.Errorf("scan InsertAuthorSuffixRow.author_id column: %w", err) + } + if err := plan1.Scan(vals[1], &item.FirstName); err != nil { + return item, fmt.Errorf("scan InsertAuthorSuffixRow.first_name column: %w", err) + } + if err := plan2.Scan(vals[2], &item.LastName); err != nil { + return item, fmt.Errorf("scan InsertAuthorSuffixRow.last_name column: %w", err) + } + if err := plan3.Scan(vals[3], &item.Suffix); err != nil { + return item, fmt.Errorf("scan InsertAuthorSuffixRow.suffix column: %w", err) + } + return item, nil + }) } const stringAggFirstNameSQL = `SELECT string_agg(first_name, ',') AS NAMES FROM author WHERE author_id = $1;` @@ -310,12 +337,22 @@ const stringAggFirstNameSQL = `SELECT string_agg(first_name, ',') AS NAMES FROM // StringAggFirstName implements Querier.StringAggFirstName. func (q *DBQuerier) StringAggFirstName(ctx context.Context, authorID int32) (*string, error) { ctx = context.WithValue(ctx, "pggen_query_name", "StringAggFirstName") - row := q.conn.QueryRow(ctx, stringAggFirstNameSQL, authorID) - var item *string - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query StringAggFirstName: %w", err) + rows, err := q.conn.Query(ctx, stringAggFirstNameSQL, authorID) + if err != nil { + return nil, fmt.Errorf("query StringAggFirstName: %w", err) } - return item, nil + + fds := rows.FieldDescriptions() + plan0 := planPtrScan(pgtype.TextCodec{}, fds[0], (*string)(nil)) + + return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (*string, error) { + vals := row.RawValues() + var item *string + if err := plan0.Scan(vals[0], &item); err != nil { + return item, fmt.Errorf("scan StringAggFirstName column: %w", err) + } + return item, nil + }) } const arrayAggFirstNameSQL = `SELECT array_agg(first_name) AS NAMES FROM author WHERE author_id = $1;` @@ -323,12 +360,22 @@ const arrayAggFirstNameSQL = `SELECT array_agg(first_name) AS NAMES FROM author // ArrayAggFirstName implements Querier.ArrayAggFirstName. func (q *DBQuerier) ArrayAggFirstName(ctx context.Context, authorID int32) ([]string, error) { ctx = context.WithValue(ctx, "pggen_query_name", "ArrayAggFirstName") - row := q.conn.QueryRow(ctx, arrayAggFirstNameSQL, authorID) - item := []string{} - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query ArrayAggFirstName: %w", err) + rows, err := q.conn.Query(ctx, arrayAggFirstNameSQL, authorID) + if err != nil { + return nil, fmt.Errorf("query ArrayAggFirstName: %w", err) } - return item, nil + + fds := rows.FieldDescriptions() + plan0 := planScan(pgtype.TextCodec{}, fds[0], (*string)(nil)) + + return pgx.CollectRows(rows, func(row pgx.CollectableRow) (string, error) { + vals := row.RawValues() + var item string + if err := plan0.Scan(vals[0], &item); err != nil { + return item, fmt.Errorf("scan StringAggFirstName column: %w", err) + } + return item, nil + }) } type scanCacheKey struct { @@ -367,7 +414,7 @@ func (s ptrScanner[T]) Scan(src []byte, dst any) error { } d := dst.(**T) *d = new(T) - return s.basePlan.Scan(src, **d) + return s.basePlan.Scan(src, *d) } func planPtrScan[T any](codec pgtype.Codec, fd pgconn.FieldDescription, target *T) pgtype.ScanPlan { From 2b41dd18069be924125027b2c63c7362243ffe4a Mon Sep 17 00:00:00 2001 From: Joe Schafer Date: Thu, 25 Jan 2024 17:27:34 -0800 Subject: [PATCH 06/27] move codecs into template --- internal/codegen/golang/query.gotemplate | 112 +++++++++++++++++----- internal/codegen/golang/templated_file.go | 59 +++++++++++- 2 files changed, 141 insertions(+), 30 deletions(-) diff --git a/internal/codegen/golang/query.gotemplate b/internal/codegen/golang/query.gotemplate index d6456671..852c1b84 100644 --- a/internal/codegen/golang/query.gotemplate +++ b/internal/codegen/golang/query.gotemplate @@ -42,7 +42,6 @@ func NewQuerier(conn genericConn) *DBQuerier { return &DBQuerier{conn: conn} } -{{- range .Declarers}}{{- "\n\n" -}}{{ .Declare $.PkgPath }}{{ end -}} {{- end -}} {{- range $i, $q := .Queries -}} @@ -55,34 +54,41 @@ const {{ $q.SQLVarName }} = {{ $q.EmitPreparedSQL }} func (q *DBQuerier) {{ $q.Name }}(ctx context.Context {{- $q.EmitParams }}) ({{ $q.EmitResultType }}, error) { ctx = context.WithValue(ctx, "pggen_query_name", "{{ $q.Name }}") {{- if eq $q.ResultKind ":one" }} - row := q.conn.QueryRow(ctx, {{ $q.SQLVarName }} {{- $q.EmitParamNames }}) - {{ $q.EmitResultTypeInit "item" }} - {{- $q.EmitResultDecoders }} - if err := row.Scan({{ $q.EmitRowScanArgs }}); err != nil { - return {{ $q.EmitResultExpr "item" }}, fmt.Errorf("query {{ $q.Name }}: %w", err) - } - {{- $q.EmitResultAssigns "item" }} - return {{ $q.EmitResultExpr "item" }}, nil -{{- else if eq $q.ResultKind ":many" }} - rows, err := q.conn.Query(ctx, {{ $q.SQLVarName }} {{- $q.EmitParamNames }}) + row, err := q.conn.Query(ctx, {{ $q.SQLVarName }} {{- $q.EmitParamNames }}) if err != nil { return nil, fmt.Errorf("query {{ $q.Name }}: %w", err) } - defer rows.Close() - {{ $q.EmitResultTypeInit "items" }} - {{- $q.EmitResultDecoders }} - for rows.Next() { - var item {{ $q.EmitResultElem }} - if err := rows.Scan({{- $q.EmitRowScanArgs -}}); err != nil { - return nil, fmt.Errorf("scan {{ $q.Name }} row: %w", err) - } - {{- $q.EmitResultAssigns "nil" }} - items = append(items, {{ $q.EmitResultExpr "item" }}) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close {{ $q.Name }} rows: %w", err) + + fds := rows.FieldDescriptions() + {{- range $i, $col := $q.Outputs -}} + {{ $q.EmitPlanScan $i $col}} + {{- end -}} + + return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) ({{ $q.EmitSingularResultType }}, error) { + vals := row.RawValues() + {{ $q.EmitResultTypeInit "item" }} + {{- range $i, $col := $q.Outputs -}} + {{- $q.EmitScanColumn $i $col -}} + {{- end -}} + }) +{{- else if eq $q.ResultKind ":many" }} + row, err := q.conn.Query(ctx, {{ $q.SQLVarName }} {{- $q.EmitParamNames }}) + if err != nil { + return nil, fmt.Errorf("query {{ $q.Name }}: %w", err) } - return items, err + + fds := rows.FieldDescriptions() + {{- range $i, $col := $q.Outputs -}} + {{ $q.EmitPlanScan $i $col}} + {{- end -}} + + return pgx.CollectRows(rows, func(row pgx.CollectableRow) ({{ $q.EmitSingularResultType }}, error) { + vals := row.RawValues() + {{ $q.EmitResultTypeInit "item" }} + {{- range $i, $col := $q.Outputs -}} + {{- $q.EmitScanColumn $i $col -}} + {{- end -}} + }) {{- else if eq $q.ResultKind ":exec" }} cmdTag, err := q.conn.Exec(ctx, {{ $q.SQLVarName }} {{- $q.EmitParamNames }}) if err != nil { @@ -93,4 +99,60 @@ func (q *DBQuerier) {{ $q.Name }}(ctx context.Context {{- $q.EmitParams }}) ({{ } {{- end -}} {{- "\n" -}} +{{ if .IsLeader }} +type scanCacheKey struct { + oid uint32 + format int16 + typeName string +} + +var ( + plans = make(map[scanCacheKey]pgtype.ScanPlan, 16) + plansMu sync.RWMutex +) + +func planScan(codec pgtype.Codec, fd pgconn.FieldDescription, target any) pgtype.ScanPlan { + key := scanCacheKey{fd.DataTypeOID, fd.Format, fmt.Sprintf("%T", target)} + plansMu.RLock() + plan := plans[key] + plansMu.RUnlock() + if plan != nil { + return plan + } + plan = codec.PlanScan(nil, fd.DataTypeOID, fd.Format, target) + plansMu.Lock() + plans[key] = plan + plansMu.Unlock() + return plan +} + +type ptrScanner[T any] struct { + basePlan pgtype.ScanPlan +} + +func (s ptrScanner[T]) Scan(src []byte, dst any) error { + if src == nil { + return nil + } + d := dst.(**T) + *d = new(T) + return s.basePlan.Scan(src, *d) +} + +func planPtrScan[T any](codec pgtype.Codec, fd pgconn.FieldDescription, target *T) pgtype.ScanPlan { + key := scanCacheKey{fd.DataTypeOID, fd.Format, fmt.Sprintf("*%T", target)} + plansMu.RLock() + plan := plans[key] + plansMu.RUnlock() + if plan != nil { + return plan + } + basePlan := planScan(codec, fd, target) + ptrPlan := ptrScanner[T]{basePlan} + plansMu.Lock() + plans[key] = plan + plansMu.Unlock() + return ptrPlan +} +{{- end -}} {{- end -}} diff --git a/internal/codegen/golang/templated_file.go b/internal/codegen/golang/templated_file.go index d2a54309..715f7737 100644 --- a/internal/codegen/golang/templated_file.go +++ b/internal/codegen/golang/templated_file.go @@ -200,6 +200,31 @@ func (tq TemplatedQuery) isInlineParams() bool { return len(tq.Inputs) <= tq.InlineParamCount } +// EmitPlanScan emits the variable that hold the pgtype.ScanPlan for a query +// output. +func (tq TemplatedQuery) EmitPlanScan(idx int, out TemplatedColumn) (string, error) { + switch tq.ResultKind { + case ast.ResultKindExec: + return "", fmt.Errorf("cannot EmitPlanScanArgs for :exec query %s", tq.Name) + case ast.ResultKindMany, ast.ResultKindOne: + break // okay + default: + return "", fmt.Errorf("unhandled EmitPlanScanArgs type: %s", tq.ResultKind) + } + return fmt.Sprintf("planScan%d = pgtype.TODOCodec{}, fs[%d], (*%s)(nil))", idx, idx, out.Type.BaseName()), nil +} + +// EmitScanColumn emits scan call for a single TemplatedColumn. +func (tq TemplatedQuery) EmitScanColumn(idx int, out TemplatedColumn) (string, error) { + sb := &strings.Builder{} + _, _ = fmt.Fprintf(sb, "if err := plan%d.Scan(vals[%d], &item); err != nil\n", idx, idx) + sb.WriteString("\t\t\t") + _, _ = fmt.Fprintf(sb, `return item, fmt.Errorf("scan %s.%s: %%w", err)`, tq.Name, out.PgName) + sb.WriteString("\n") + sb.WriteString("\t\t}\n") + return sb.String(), nil +} + // EmitRowScanArgs emits the args to scan a single row from a pgx.Row or // pgx.Rows. func (tq TemplatedQuery) EmitRowScanArgs() (string, error) { @@ -256,9 +281,10 @@ func (tq TemplatedQuery) EmitRowScanArgs() (string, error) { return sb.String(), nil } -// EmitResultType returns the string representing the overall query result type, -// meaning the return result. -func (tq TemplatedQuery) EmitResultType() (string, error) { +// EmitSingularResultType returns the string representing a single element +// of the overall query result type, like FindAuthorsRow when the overall return +// type is []FindAuthorsRow. +func (tq TemplatedQuery) EmitSingularResultType() (string, error) { outs := removeVoidColumns(tq.Outputs) switch tq.ResultKind { case ast.ResultKindExec: @@ -268,9 +294,9 @@ func (tq TemplatedQuery) EmitResultType() (string, error) { case 0: return "pgconn.CommandTag", nil case 1: - return "[]" + outs[0].QualType, nil + return outs[0].QualType, nil default: - return "[]" + tq.Name + "Row", nil + return tq.Name + "Row", nil } case ast.ResultKindOne: switch len(outs) { @@ -281,6 +307,29 @@ func (tq TemplatedQuery) EmitResultType() (string, error) { default: return tq.Name + "Row", nil } + default: + return "", fmt.Errorf("unhandled EmitSingularResultType kind: %s", tq.ResultKind) + } +} + +// EmitResultType returns the string representing the overall query result type, +// meaning the return result. +func (tq TemplatedQuery) EmitResultType() (string, error) { + rt, err := tq.EmitSingularResultType() + if err != nil { + return "", fmt.Errorf("unhandled EmitResultType: %w", err) + } + switch tq.ResultKind { + case ast.ResultKindExec: + return rt, nil + case ast.ResultKindMany: + outs := removeVoidColumns(tq.Outputs) + if len(outs) == 0 { + return rt, nil + } + return "[]" + rt, nil + case ast.ResultKindOne: + return rt, nil default: return "", fmt.Errorf("unhandled EmitResultType kind: %s", tq.ResultKind) } From 7ca7c671954af85d8e511ec15c7d2417be335e15 Mon Sep 17 00:00:00 2001 From: Joe Schafer Date: Fri, 26 Jan 2024 10:13:40 -0800 Subject: [PATCH 07/27] update template for code --- example/author/query.sql.go | 124 ++++++------ internal/codegen/golang/query.gotemplate | 54 ++---- internal/codegen/golang/templated_file.go | 221 ++++++---------------- internal/codegen/golang/templater.go | 25 ++- internal/pg/type_cache.go | 19 +- 5 files changed, 173 insertions(+), 270 deletions(-) diff --git a/example/author/query.sql.go b/example/author/query.sql.go index 6dd22144..5af26d39 100644 --- a/example/author/query.sql.go +++ b/example/author/query.sql.go @@ -49,7 +49,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -80,27 +80,26 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut if err != nil { return FindAuthorByIDRow{}, fmt.Errorf("query FindAuthorByID: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planScan(pgtype.Int4Codec{}, fds[0], (*int32)(nil)) + plan0 := planScan(pgtype.TextCodec{}, fds[0], (*int32)(nil)) plan1 := planScan(pgtype.TextCodec{}, fds[1], (*string)(nil)) plan2 := planScan(pgtype.TextCodec{}, fds[2], (*string)(nil)) - plan3 := planPtrScan(pgtype.TextCodec{}, fds[3], (*string)(nil)) + plan3 := planScan(pgtype.TextCodec{}, fds[3], (**string)(nil)) return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (FindAuthorByIDRow, error) { vals := row.RawValues() var item FindAuthorByIDRow - if err := plan0.Scan(vals[0], &item.AuthorID); err != nil { - return item, fmt.Errorf("scan FindAuthors.author_id column: %w", err) + if err := plan0.Scan(vals[0], &item); err != nil { + return item, fmt.Errorf("scan FindAuthorByID.author_id: %w", err) } - if err := plan1.Scan(vals[1], &item.FirstName); err != nil { - return item, fmt.Errorf("scan FindAuthors.first_name column: %w", err) + if err := plan1.Scan(vals[1], &item); err != nil { + return item, fmt.Errorf("scan FindAuthorByID.first_name: %w", err) } - if err := plan2.Scan(vals[2], &item.LastName); err != nil { - return item, fmt.Errorf("scan FindAuthors.last_name column: %w", err) + if err := plan2.Scan(vals[2], &item); err != nil { + return item, fmt.Errorf("scan FindAuthorByID.last_name: %w", err) } - if err := plan3.Scan(vals[3], &item.Suffix); err != nil { - return item, fmt.Errorf("scan FindAuthors.suffix column: %w", err) + if err := plan3.Scan(vals[3], &item); err != nil { + return item, fmt.Errorf("scan FindAuthorByID.suffix: %w", err) } return item, nil }) @@ -122,27 +121,26 @@ func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAu if err != nil { return nil, fmt.Errorf("query FindAuthors: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planScan(pgtype.Int4Codec{}, fds[0], (*int32)(nil)) + plan0 := planScan(pgtype.TextCodec{}, fds[0], (*int32)(nil)) plan1 := planScan(pgtype.TextCodec{}, fds[1], (*string)(nil)) plan2 := planScan(pgtype.TextCodec{}, fds[2], (*string)(nil)) - plan3 := planPtrScan(pgtype.TextCodec{}, fds[3], (*string)(nil)) + plan3 := planScan(pgtype.TextCodec{}, fds[3], (**string)(nil)) return pgx.CollectRows(rows, func(row pgx.CollectableRow) (FindAuthorsRow, error) { vals := row.RawValues() var item FindAuthorsRow - if err := plan0.Scan(vals[0], &item.AuthorID); err != nil { - return item, fmt.Errorf("scan FindAuthors.author_id column: %w", err) + if err := plan0.Scan(vals[0], &item); err != nil { + return item, fmt.Errorf("scan FindAuthors.author_id: %w", err) } - if err := plan1.Scan(vals[1], &item.FirstName); err != nil { - return item, fmt.Errorf("scan FindAuthors.first_name column: %w", err) + if err := plan1.Scan(vals[1], &item); err != nil { + return item, fmt.Errorf("scan FindAuthors.first_name: %w", err) } - if err := plan2.Scan(vals[2], &item.LastName); err != nil { - return item, fmt.Errorf("scan FindAuthors.last_name column: %w", err) + if err := plan2.Scan(vals[2], &item); err != nil { + return item, fmt.Errorf("scan FindAuthors.last_name: %w", err) } - if err := plan3.Scan(vals[3], &item.Suffix); err != nil { - return item, fmt.Errorf("scan FindAuthors.suffix column: %w", err) + if err := plan3.Scan(vals[3], &item); err != nil { + return item, fmt.Errorf("scan FindAuthors.suffix: %w", err) } return item, nil }) @@ -155,10 +153,6 @@ type FindAuthorNamesRow struct { LastName *string `json:"last_name"` } -func (r *FindAuthorNamesRow) scanRow(rows pgx.Rows) error { - return rows.Scan(&r.FirstName, &r.LastName) -} - // FindAuthorNames implements Querier.FindAuthorNames. func (q *DBQuerier) FindAuthorNames(ctx context.Context, authorID int32) ([]FindAuthorNamesRow, error) { ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthorNames") @@ -166,19 +160,18 @@ func (q *DBQuerier) FindAuthorNames(ctx context.Context, authorID int32) ([]Find if err != nil { return nil, fmt.Errorf("query FindAuthorNames: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planScan(pgtype.TextCodec{}, fds[0], (*string)(nil)) - plan1 := planScan(pgtype.TextCodec{}, fds[1], (*string)(nil)) + plan0 := planScan(pgtype.TextCodec{}, fds[0], (**string)(nil)) + plan1 := planScan(pgtype.TextCodec{}, fds[1], (**string)(nil)) return pgx.CollectRows(rows, func(row pgx.CollectableRow) (FindAuthorNamesRow, error) { vals := row.RawValues() var item FindAuthorNamesRow - if err := plan0.Scan(vals[0], &item.FirstName); err != nil { - return item, fmt.Errorf("scan FindAuthors.first_name column: %w", err) + if err := plan0.Scan(vals[0], &item); err != nil { + return item, fmt.Errorf("scan FindAuthorNames.first_name: %w", err) } - if err := plan1.Scan(vals[1], &item.LastName); err != nil { - return item, fmt.Errorf("scan FindAuthors.last_name column: %w", err) + if err := plan1.Scan(vals[1], &item); err != nil { + return item, fmt.Errorf("scan FindAuthorNames.last_name: %w", err) } return item, nil }) @@ -193,17 +186,16 @@ func (q *DBQuerier) FindFirstNames(ctx context.Context, authorID int32) ([]*stri if err != nil { return nil, fmt.Errorf("query FindFirstNames: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planScan(pgtype.TextCodec{}, fds[0], (*string)(nil)) + plan0 := planScan(pgtype.TextCodec{}, fds[0], (**string)(nil)) return pgx.CollectRows(rows, func(row pgx.CollectableRow) (*string, error) { vals := row.RawValues() - var item string + var item *string if err := plan0.Scan(vals[0], &item); err != nil { - return nil, fmt.Errorf("scan FindFirstNames row: %w", err) + return item, fmt.Errorf("scan FindFirstNames.first_name: %w", err) } - return &item, nil + return item, nil }) } @@ -214,7 +206,7 @@ func (q *DBQuerier) DeleteAuthors(ctx context.Context) (pgconn.CommandTag, error ctx = context.WithValue(ctx, "pggen_query_name", "DeleteAuthors") cmdTag, err := q.conn.Exec(ctx, deleteAuthorsSQL) if err != nil { - return cmdTag, fmt.Errorf("exec query DeleteAuthors: %w", err) + return pgconn.CommandTag{}, fmt.Errorf("exec query DeleteAuthors: %w", err) } return cmdTag, err } @@ -226,7 +218,7 @@ func (q *DBQuerier) DeleteAuthorsByFirstName(ctx context.Context, firstName stri ctx = context.WithValue(ctx, "pggen_query_name", "DeleteAuthorsByFirstName") cmdTag, err := q.conn.Exec(ctx, deleteAuthorsByFirstNameSQL, firstName) if err != nil { - return cmdTag, fmt.Errorf("exec query DeleteAuthorsByFirstName: %w", err) + return pgconn.CommandTag{}, fmt.Errorf("exec query DeleteAuthorsByFirstName: %w", err) } return cmdTag, err } @@ -248,7 +240,7 @@ func (q *DBQuerier) DeleteAuthorsByFullName(ctx context.Context, params DeleteAu ctx = context.WithValue(ctx, "pggen_query_name", "DeleteAuthorsByFullName") cmdTag, err := q.conn.Exec(ctx, deleteAuthorsByFullNameSQL, params.FirstName, params.LastName, params.Suffix) if err != nil { - return cmdTag, fmt.Errorf("exec query DeleteAuthorsByFullName: %w", err) + return pgconn.CommandTag{}, fmt.Errorf("exec query DeleteAuthorsByFullName: %w", err) } return cmdTag, err } @@ -264,15 +256,14 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, firstName string, lastName if err != nil { return 0, fmt.Errorf("query InsertAuthor: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planScan(pgtype.Int4Codec{}, fds[0], (*int32)(nil)) + plan0 := planScan(pgtype.TextCodec{}, fds[0], (*int32)(nil)) return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (int32, error) { vals := row.RawValues() var item int32 if err := plan0.Scan(vals[0], &item); err != nil { - return item, fmt.Errorf("scan InsertAuthor: %w", err) + return item, fmt.Errorf("scan InsertAuthor.author_id: %w", err) } return item, nil }) @@ -295,10 +286,6 @@ type InsertAuthorSuffixRow struct { Suffix *string `json:"suffix"` } -func (r *InsertAuthorSuffixRow) scanRow(rows pgx.Rows) error { - return rows.Scan(&r.AuthorID, &r.FirstName, &r.LastName, &r.Suffix) -} - // InsertAuthorSuffix implements Querier.InsertAuthorSuffix. func (q *DBQuerier) InsertAuthorSuffix(ctx context.Context, params InsertAuthorSuffixParams) (InsertAuthorSuffixRow, error) { ctx = context.WithValue(ctx, "pggen_query_name", "InsertAuthorSuffix") @@ -306,33 +293,32 @@ func (q *DBQuerier) InsertAuthorSuffix(ctx context.Context, params InsertAuthorS if err != nil { return InsertAuthorSuffixRow{}, fmt.Errorf("query InsertAuthorSuffix: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planScan(pgtype.Int4Codec{}, fds[0], (*int32)(nil)) + plan0 := planScan(pgtype.TextCodec{}, fds[0], (*int32)(nil)) plan1 := planScan(pgtype.TextCodec{}, fds[1], (*string)(nil)) plan2 := planScan(pgtype.TextCodec{}, fds[2], (*string)(nil)) - plan3 := planPtrScan(pgtype.TextCodec{}, fds[3], (*string)(nil)) + plan3 := planScan(pgtype.TextCodec{}, fds[3], (**string)(nil)) return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (InsertAuthorSuffixRow, error) { vals := row.RawValues() var item InsertAuthorSuffixRow - if err := plan0.Scan(vals[0], &item.AuthorID); err != nil { - return item, fmt.Errorf("scan InsertAuthorSuffixRow.author_id column: %w", err) + if err := plan0.Scan(vals[0], &item); err != nil { + return item, fmt.Errorf("scan InsertAuthorSuffix.author_id: %w", err) } - if err := plan1.Scan(vals[1], &item.FirstName); err != nil { - return item, fmt.Errorf("scan InsertAuthorSuffixRow.first_name column: %w", err) + if err := plan1.Scan(vals[1], &item); err != nil { + return item, fmt.Errorf("scan InsertAuthorSuffix.first_name: %w", err) } - if err := plan2.Scan(vals[2], &item.LastName); err != nil { - return item, fmt.Errorf("scan InsertAuthorSuffixRow.last_name column: %w", err) + if err := plan2.Scan(vals[2], &item); err != nil { + return item, fmt.Errorf("scan InsertAuthorSuffix.last_name: %w", err) } - if err := plan3.Scan(vals[3], &item.Suffix); err != nil { - return item, fmt.Errorf("scan InsertAuthorSuffixRow.suffix column: %w", err) + if err := plan3.Scan(vals[3], &item); err != nil { + return item, fmt.Errorf("scan InsertAuthorSuffix.suffix: %w", err) } return item, nil }) } -const stringAggFirstNameSQL = `SELECT string_agg(first_name, ',') AS NAMES FROM author WHERE author_id = $1;` +const stringAggFirstNameSQL = `SELECT string_agg(first_name, ',') AS names FROM author WHERE author_id = $1;` // StringAggFirstName implements Querier.StringAggFirstName. func (q *DBQuerier) StringAggFirstName(ctx context.Context, authorID int32) (*string, error) { @@ -341,21 +327,20 @@ func (q *DBQuerier) StringAggFirstName(ctx context.Context, authorID int32) (*st if err != nil { return nil, fmt.Errorf("query StringAggFirstName: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planPtrScan(pgtype.TextCodec{}, fds[0], (*string)(nil)) + plan0 := planScan(pgtype.TextCodec{}, fds[0], (**string)(nil)) return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (*string, error) { vals := row.RawValues() var item *string if err := plan0.Scan(vals[0], &item); err != nil { - return item, fmt.Errorf("scan StringAggFirstName column: %w", err) + return item, fmt.Errorf("scan StringAggFirstName.names: %w", err) } return item, nil }) } -const arrayAggFirstNameSQL = `SELECT array_agg(first_name) AS NAMES FROM author WHERE author_id = $1;` +const arrayAggFirstNameSQL = `SELECT array_agg(first_name) AS names FROM author WHERE author_id = $1;` // ArrayAggFirstName implements Querier.ArrayAggFirstName. func (q *DBQuerier) ArrayAggFirstName(ctx context.Context, authorID int32) ([]string, error) { @@ -364,15 +349,14 @@ func (q *DBQuerier) ArrayAggFirstName(ctx context.Context, authorID int32) ([]st if err != nil { return nil, fmt.Errorf("query ArrayAggFirstName: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planScan(pgtype.TextCodec{}, fds[0], (*string)(nil)) + plan0 := planScan(pgtype.TextCodec{}, fds[0], (*[]string)(nil)) - return pgx.CollectRows(rows, func(row pgx.CollectableRow) (string, error) { + return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) ([]string, error) { vals := row.RawValues() - var item string + var item []string if err := plan0.Scan(vals[0], &item); err != nil { - return item, fmt.Errorf("scan StringAggFirstName column: %w", err) + return item, fmt.Errorf("scan ArrayAggFirstName.names: %w", err) } return item, nil }) diff --git a/internal/codegen/golang/query.gotemplate b/internal/codegen/golang/query.gotemplate index 852c1b84..d6c823f0 100644 --- a/internal/codegen/golang/query.gotemplate +++ b/internal/codegen/golang/query.gotemplate @@ -27,7 +27,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -53,48 +53,30 @@ const {{ $q.SQLVarName }} = {{ $q.EmitPreparedSQL }} // {{ $q.Name }} implements Querier.{{ $q.Name }}. func (q *DBQuerier) {{ $q.Name }}(ctx context.Context {{- $q.EmitParams }}) ({{ $q.EmitResultType }}, error) { ctx = context.WithValue(ctx, "pggen_query_name", "{{ $q.Name }}") -{{- if eq $q.ResultKind ":one" }} - row, err := q.conn.Query(ctx, {{ $q.SQLVarName }} {{- $q.EmitParamNames }}) +{{- if eq $q.ResultKind ":exec" }} + cmdTag, err := q.conn.Exec(ctx, {{ $q.SQLVarName }} {{- $q.EmitParamNames }}) if err != nil { - return nil, fmt.Errorf("query {{ $q.Name }}: %w", err) + return {{ $q.EmitZeroResult }}, fmt.Errorf("exec query {{ $q.Name }}: %w", err) + } + return cmdTag, err +{{- else }} + rows, err := q.conn.Query(ctx, {{ $q.SQLVarName }} {{- $q.EmitParamNames }}) + if err != nil { + return {{ $q.EmitZeroResult }}, fmt.Errorf("query {{ $q.Name }}: %w", err) } - fds := rows.FieldDescriptions() - {{- range $i, $col := $q.Outputs -}} + {{- range $i, $col := $q.Outputs }} {{ $q.EmitPlanScan $i $col}} - {{- end -}} + {{- end }} - return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) ({{ $q.EmitSingularResultType }}, error) { + return {{ $q.EmitCollectionFunc }}(rows, func(row pgx.CollectableRow) ({{ $q.EmitSingularResultType }}, error) { vals := row.RawValues() - {{ $q.EmitResultTypeInit "item" }} - {{- range $i, $col := $q.Outputs -}} - {{- $q.EmitScanColumn $i $col -}} - {{- end -}} - }) -{{- else if eq $q.ResultKind ":many" }} - row, err := q.conn.Query(ctx, {{ $q.SQLVarName }} {{- $q.EmitParamNames }}) - if err != nil { - return nil, fmt.Errorf("query {{ $q.Name }}: %w", err) - } - - fds := rows.FieldDescriptions() - {{- range $i, $col := $q.Outputs -}} - {{ $q.EmitPlanScan $i $col}} - {{- end -}} - - return pgx.CollectRows(rows, func(row pgx.CollectableRow) ({{ $q.EmitSingularResultType }}, error) { - vals := row.RawValues() - {{ $q.EmitResultTypeInit "item" }} - {{- range $i, $col := $q.Outputs -}} - {{- $q.EmitScanColumn $i $col -}} - {{- end -}} + var item {{ $q.EmitSingularResultType }} + {{- range $i, $col := $q.Outputs }} + {{ $q.EmitScanColumn $i $col }} + {{- end }} + return item, nil }) -{{- else if eq $q.ResultKind ":exec" }} - cmdTag, err := q.conn.Exec(ctx, {{ $q.SQLVarName }} {{- $q.EmitParamNames }}) - if err != nil { - return cmdTag, fmt.Errorf("exec query {{ $q.Name }}: %w", err) - } - return cmdTag, err {{- end }} } {{- end -}} diff --git a/internal/codegen/golang/templated_file.go b/internal/codegen/golang/templated_file.go index 715f7737..6b593806 100644 --- a/internal/codegen/golang/templated_file.go +++ b/internal/codegen/golang/templated_file.go @@ -40,7 +40,8 @@ type TemplatedQuery struct { Doc string // doc from the source query file, formatted for Go PreparedSQL string // SQL query, ready to run with PREPARE statement Inputs []TemplatedParam // input parameters to the query - Outputs []TemplatedColumn // output columns of the query + Outputs []TemplatedColumn // non-void output columns of the query + ScanCols []TemplatedColumn // all columns of the query, including void columns InlineParamCount int // inclusive count of params that will be inlined } @@ -211,17 +212,17 @@ func (tq TemplatedQuery) EmitPlanScan(idx int, out TemplatedColumn) (string, err default: return "", fmt.Errorf("unhandled EmitPlanScanArgs type: %s", tq.ResultKind) } - return fmt.Sprintf("planScan%d = pgtype.TODOCodec{}, fs[%d], (*%s)(nil))", idx, idx, out.Type.BaseName()), nil + return fmt.Sprintf("plan%d := planScan(pgtype.TextCodec{}, fds[%d], (*%s)(nil))", idx, idx, out.Type.BaseName()), nil } // EmitScanColumn emits scan call for a single TemplatedColumn. func (tq TemplatedQuery) EmitScanColumn(idx int, out TemplatedColumn) (string, error) { sb := &strings.Builder{} - _, _ = fmt.Fprintf(sb, "if err := plan%d.Scan(vals[%d], &item); err != nil\n", idx, idx) + _, _ = fmt.Fprintf(sb, "if err := plan%d.Scan(vals[%d], &item); err != nil {\n", idx, idx) sb.WriteString("\t\t\t") _, _ = fmt.Fprintf(sb, `return item, fmt.Errorf("scan %s.%s: %%w", err)`, tq.Name, out.PgName) sb.WriteString("\n") - sb.WriteString("\t\t}\n") + sb.WriteString("\t\t}") return sb.String(), nil } @@ -237,7 +238,7 @@ func (tq TemplatedQuery) EmitRowScanArgs() (string, error) { return "", fmt.Errorf("unhandled EmitRowScanArgs type: %s", tq.ResultKind) } - hasOnlyOneNonVoid := len(removeVoidColumns(tq.Outputs)) == 1 + hasOnlyOneOutput := len(tq.Outputs) == 1 sb := strings.Builder{} sb.Grow(15 * len(tq.Outputs)) for i, out := range tq.Outputs { @@ -248,7 +249,7 @@ func (tq TemplatedQuery) EmitRowScanArgs() (string, error) { sb.WriteString(out.LowerName) sb.WriteString("Array") default: - if hasOnlyOneNonVoid { + if hasOnlyOneOutput { sb.WriteString("&item") } else { sb.WriteString("&item.") @@ -261,7 +262,7 @@ func (tq TemplatedQuery) EmitRowScanArgs() (string, error) { sb.WriteString("Row") case *gotype.EnumType, *gotype.OpaqueType: - if hasOnlyOneNonVoid { + if hasOnlyOneOutput { sb.WriteString("&item") } else { sb.WriteString("&item.") @@ -281,55 +282,45 @@ func (tq TemplatedQuery) EmitRowScanArgs() (string, error) { return sb.String(), nil } -// EmitSingularResultType returns the string representing a single element -// of the overall query result type, like FindAuthorsRow when the overall return -// type is []FindAuthorsRow. -func (tq TemplatedQuery) EmitSingularResultType() (string, error) { - outs := removeVoidColumns(tq.Outputs) +func (tq TemplatedQuery) EmitCollectionFunc() (string, error) { switch tq.ResultKind { case ast.ResultKindExec: - return "pgconn.CommandTag", nil + return "", fmt.Errorf("cannot EmitCollectionFunc for :exec query %s", tq.Name) case ast.ResultKindMany: - switch len(outs) { - case 0: - return "pgconn.CommandTag", nil - case 1: - return outs[0].QualType, nil - default: - return tq.Name + "Row", nil - } + return "pgx.CollectRows", nil case ast.ResultKindOne: - switch len(outs) { - case 0: - return "pgconn.CommandTag", nil - case 1: - return outs[0].QualType, nil - default: - return tq.Name + "Row", nil - } + return "pgx.CollectExactlyOneRow", nil default: - return "", fmt.Errorf("unhandled EmitSingularResultType kind: %s", tq.ResultKind) + return "", fmt.Errorf("unhandled EmitCollectionFunc type: %s", tq.ResultKind) + } +} + +// EmitSingularResultType returns the string representing a single element +// of the overall query result type, like FindAuthorsRow when the overall return +// type is []FindAuthorsRow. +func (tq TemplatedQuery) EmitSingularResultType() string { + if tq.ResultKind == ast.ResultKindExec { + return "pgconn.CommandTag" + } + if len(tq.Outputs) == 0 { + return "pgconn.CommandTag" + } + if len(tq.Outputs) == 1 { + return tq.Outputs[0].QualType } + return tq.Name + "Row" } // EmitResultType returns the string representing the overall query result type, // meaning the return result. func (tq TemplatedQuery) EmitResultType() (string, error) { - rt, err := tq.EmitSingularResultType() - if err != nil { - return "", fmt.Errorf("unhandled EmitResultType: %w", err) - } switch tq.ResultKind { case ast.ResultKindExec: - return rt, nil + return "pgconn.CommandTag", nil case ast.ResultKindMany: - outs := removeVoidColumns(tq.Outputs) - if len(outs) == 0 { - return rt, nil - } - return "[]" + rt, nil + return "[]" + tq.EmitSingularResultType(), nil case ast.ResultKindOne: - return rt, nil + return tq.EmitSingularResultType(), nil default: return "", fmt.Errorf("unhandled EmitResultType kind: %s", tq.ResultKind) } @@ -371,108 +362,37 @@ func (tq TemplatedQuery) EmitResultTypeInit(name string) (string, error) { } } -// EmitResultDecoders declares all initialization required for output types. -func (tq TemplatedQuery) EmitResultDecoders() (string, error) { - sb := &strings.Builder{} - const indent = "\n\t" // 1 level indent inside querier method - for _, out := range tq.Outputs { - switch typ := gotype.UnwrapNestedType(out.Type).(type) { - case *gotype.CompositeType: - sb.WriteString(indent) - sb.WriteString(out.LowerName) - sb.WriteString("Row := q.types.") - sb.WriteString(NameCompositeTranscoderFunc(typ)) - sb.WriteString("()") - case *gotype.ArrayType: - switch gotype.UnwrapNestedType(typ.Elem).(type) { - case *gotype.EnumType, *gotype.CompositeType: - // For all other array elems, a normal array works. - sb.WriteString(indent) - sb.WriteString(out.LowerName) - sb.WriteString("Array := q.types.") - sb.WriteString(NameArrayTranscoderFunc(typ)) - sb.WriteString("()") - } - default: - continue +// EmitZeroResult returns the string representing the zero value of a result. +func (tq TemplatedQuery) EmitZeroResult() (string, error) { + switch tq.ResultKind { + case ast.ResultKindExec: + return "pgconn.CommandTag{}", nil + case ast.ResultKindMany: + return "nil", nil // empty slice + case ast.ResultKindOne: + if len(tq.Outputs) > 1 { + return tq.Name + "Row{}", nil } - } - return sb.String(), nil -} - -// EmitResultAssigns writes all the assign statements after scanning the result -// from pgx. -// -// Copies pgtype.CompositeFields representing a Postgres composite type into the -// output struct. -// -// Copies pgtype.EnumArray fields into Go enum array types. -func (tq TemplatedQuery) EmitResultAssigns(zeroVal string) (string, error) { - sb := &strings.Builder{} - indent := "\n\t" - if tq.ResultKind == ast.ResultKindMany { - indent += "\t" // a :many query processes items in a for loop - } - for _, out := range tq.Outputs { - switch typ := gotype.UnwrapNestedType(out.Type).(type) { - case *gotype.CompositeType: - sb.WriteString(indent) - sb.WriteString("if err := ") - sb.WriteString(out.LowerName) - sb.WriteString("Row.AssignTo(&item") - if len(removeVoidColumns(tq.Outputs)) > 1 { - sb.WriteRune('.') - sb.WriteString(out.UpperName) - } - sb.WriteString("); err != nil {") - sb.WriteString(indent) - sb.WriteString("\treturn ") - sb.WriteString(zeroVal) - sb.WriteString(", fmt.Errorf(\"assign ") - sb.WriteString(tq.Name) - sb.WriteString(" row: %w\", err)") - sb.WriteString(indent) - sb.WriteString("}") - case *gotype.ArrayType: - switch gotype.UnwrapNestedType(typ.Elem).(type) { - case *gotype.CompositeType, *gotype.EnumType: - sb.WriteString(indent) - sb.WriteString("if err := ") - sb.WriteString(out.LowerName) - sb.WriteString("Array.AssignTo(&item") - if len(removeVoidColumns(tq.Outputs)) > 1 { - sb.WriteRune('.') - sb.WriteString(out.UpperName) - } - sb.WriteString("); err != nil {") - sb.WriteString(indent) - sb.WriteString("\treturn ") - sb.WriteString(zeroVal) - sb.WriteString(", fmt.Errorf(\"assign ") - sb.WriteString(tq.Name) - sb.WriteString(" row: %w\", err)") - sb.WriteString(indent) - sb.WriteString("}") - } + typ := tq.Outputs[0].Type.BaseName() + switch { + case strings.HasPrefix(typ, "[]"): + return "nil", nil // empty slice + case strings.HasPrefix(typ, "*"): + return "nil", nil // nil pointer } + switch typ { + case "int", "int32", "int64", "float32", "float64": + return "0", nil + case "string": + return `""`, nil + case "bool": + return "false", nil + default: + return typ + "{}", nil // won't work for type Foo int + } + default: + return "", fmt.Errorf("unhandled EmitZeroResult kind: %s", tq.ResultKind) } - return sb.String(), nil -} - -// EmitResultElem returns the string representing a single item in the overall -// query result type. For :one and :exec queries, this is the same as -// EmitResultType. For :many queries, this is the element type of the slice -// result type. -func (tq TemplatedQuery) EmitResultElem() (string, error) { - result, err := tq.EmitResultType() - if err != nil { - return "", fmt.Errorf("unhandled EmitResultElem type: %w", err) - } - // Unwrap arrays because we build the array with append. - arr := strings.TrimPrefix(result, "[]") - // Unwrap pointers because we add "&" to return the correct types. - ptr := strings.TrimPrefix(arr, "*") - return ptr, nil } // EmitResultExpr returns the string representation of a single item to return @@ -531,16 +451,15 @@ func (tq TemplatedQuery) EmitRowStruct() string { case ast.ResultKindExec: return "" case ast.ResultKindOne, ast.ResultKindMany: - outs := removeVoidColumns(tq.Outputs) - if len(outs) <= 1 { + if len(tq.Outputs) == 1 { return "" // if there's only 1 output column, return it directly } sb := &strings.Builder{} sb.WriteString("\n\ntype ") sb.WriteString(tq.Name) sb.WriteString("Row struct {\n") - maxNameLen, maxTypeLen := getLongestOutput(outs) - for _, out := range outs { + maxNameLen, maxTypeLen := getLongestOutput(tq.Outputs) + for _, out := range tq.Outputs { // Name sb.WriteString("\t") sb.WriteString(out.UpperName) @@ -560,17 +479,3 @@ func (tq TemplatedQuery) EmitRowStruct() string { panic("unhandled result type: " + tq.ResultKind) } } - -// removeVoidColumns makes a copy of cols with all VoidType columns removed. -// Useful because return types shouldn't contain the void type, but we need -// to use a nil placeholder for void types when scanning a pgx.Row. -func removeVoidColumns(cols []TemplatedColumn) []TemplatedColumn { - outs := make([]TemplatedColumn, 0, len(cols)) - for _, col := range cols { - if _, ok := col.Type.(*gotype.VoidType); ok { - continue - } - outs = append(outs, col) - } - return outs -} diff --git a/internal/codegen/golang/templater.go b/internal/codegen/golang/templater.go index de73461e..4d56e40c 100644 --- a/internal/codegen/golang/templater.go +++ b/internal/codegen/golang/templater.go @@ -2,6 +2,7 @@ package golang import ( "fmt" + "github.com/jschaf/pggen/internal/ast" "github.com/jschaf/pggen/internal/casing" "github.com/jschaf/pggen/internal/codegen" "github.com/jschaf/pggen/internal/codegen/golang/gotype" @@ -183,14 +184,20 @@ func (tm Templater) templateFile(file codegen.QueryFile, isLeader bool) (Templat declarers.AddAll(ds...) } + nonVoidCols := removeVoidColumns(outputs) + resultKind := query.ResultKind + if len(nonVoidCols) == 0 { + resultKind = ast.ResultKindExec + } queries = append(queries, TemplatedQuery{ Name: tm.caser.ToUpperGoIdent(query.Name), SQLVarName: tm.caser.ToLowerGoIdent(query.Name) + "SQL", - ResultKind: query.ResultKind, + ResultKind: resultKind, Doc: docs.String(), PreparedSQL: query.PreparedSQL, Inputs: inputs, - Outputs: outputs, + Outputs: nonVoidCols, + ScanCols: outputs, InlineParamCount: tm.inlineParamCount, }) } @@ -232,3 +239,17 @@ func (tm Templater) chooseLowerName(pgName string, fallback string, idx int, num } return fallback + suffix } + +// removeVoidColumns makes a copy of cols with all VoidType columns removed. +// Useful because return types shouldn't contain the void type, but we need +// to use a nil placeholder for void types when scanning a pgx.Row. +func removeVoidColumns(cols []TemplatedColumn) []TemplatedColumn { + outs := make([]TemplatedColumn, 0, len(cols)) + for _, col := range cols { + if _, ok := col.Type.(*gotype.VoidType); ok { + continue + } + outs = append(outs, col) + } + return outs +} diff --git a/internal/pg/type_cache.go b/internal/pg/type_cache.go index 53101a37..dc18549e 100644 --- a/internal/pg/type_cache.go +++ b/internal/pg/type_cache.go @@ -1,6 +1,7 @@ package pg import ( + "github.com/jackc/pgx/v5/pgtype" "sync" ) @@ -21,6 +22,16 @@ func newTypeCache() *typeCache { } } +func lookup() { + !!!fixme + m := pgtype.NewMap() // !!! + m.TypeForValue() // looks up pg type for go type + m.FormatCodeForOID() + m.TypeForOID() + m.TypeForName() + +} + // getOIDs returns the cached OIDS (with the type) and uncached OIDs. func (tc *typeCache) getOIDs(oids ...uint32) (map[uint32]Type, map[uint32]struct{}) { cachedTypes := make(map[uint32]Type, len(oids)) @@ -28,10 +39,10 @@ func (tc *typeCache) getOIDs(oids ...uint32) (map[uint32]Type, map[uint32]struct tc.mu.Lock() defer tc.mu.Unlock() for _, oid := range oids { - if t, ok := tc.types[uint32(oid)]; ok { - cachedTypes[uint32(oid)] = t + if t, ok := tc.types[oid]; ok { + cachedTypes[oid] = t } else { - uncachedTypes[uint32(oid)] = struct{}{} + uncachedTypes[oid] = struct{}{} } } return cachedTypes, uncachedTypes @@ -39,7 +50,7 @@ func (tc *typeCache) getOIDs(oids ...uint32) (map[uint32]Type, map[uint32]struct func (tc *typeCache) getOID(oid uint32) (Type, bool) { tc.mu.Lock() - typ, ok := tc.types[uint32(oid)] + typ, ok := tc.types[oid] tc.mu.Unlock() return typ, ok } From 6d5a5fff2bdbd6867544dd0aa1de7d63fde65364 Mon Sep 17 00:00:00 2001 From: meoyawn Date: Wed, 6 May 2026 12:55:57 +0000 Subject: [PATCH 08/27] Run tests through dockerized Postgres wrapper --- Makefile | 2 +- internal/pgdocker/cmd/pgdocker/main.go | 59 ++++++++++++++++++++++++++ internal/pgtest/pg_test_db.go | 26 ++++++++++-- 3 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 internal/pgdocker/cmd/pgdocker/main.go diff --git a/Makefile b/Makefile index 823c3480..6de01999 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ psql: .PHONY: test test: - go test ./... + go run ./internal/pgdocker/cmd/pgdocker -- go test ./... .PHONY: acceptance-test acceptance-test: diff --git a/internal/pgdocker/cmd/pgdocker/main.go b/internal/pgdocker/cmd/pgdocker/main.go new file mode 100644 index 00000000..765acecd --- /dev/null +++ b/internal/pgdocker/cmd/pgdocker/main.go @@ -0,0 +1,59 @@ +// pgdocker starts a Postgres Docker container and exposes it to a wrapped +// command through the PGURL environment variable. +package main + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "os/exec" + "time" + + "github.com/jschaf/pggen/internal/errs" + "github.com/jschaf/pggen/internal/pgdocker" +) + +func main() { + if err := run(); err != nil { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + fmt.Fprintln(os.Stderr, err) + os.Exit(exitErr.ExitCode()) + } + log.Fatal(err) + } +} + +func run() (mErr error) { + if len(os.Args) < 3 || os.Args[1] != "--" { + return fmt.Errorf("usage: %s -- [args...]", os.Args[0]) + } + + ctx := context.Background() + docker, err := pgdocker.Start(ctx, nil) + if err != nil { + return fmt.Errorf("start postgres: %w", err) + } + defer errs.Capture(&mErr, func() error { + stopCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + return docker.Stop(stopCtx) + }, "stop postgres") + + connStr, err := docker.ConnString() + if err != nil { + return fmt.Errorf("postgres connection string: %w", err) + } + + wrapped := exec.Command(os.Args[2], os.Args[3:]...) //nolint:gosec // Runs the command explicitly supplied after --. + wrapped.Stdin = os.Stdin + wrapped.Stdout = os.Stdout + wrapped.Stderr = os.Stderr + wrapped.Env = append(os.Environ(), "PGURL="+connStr) + if err := wrapped.Run(); err != nil { + return fmt.Errorf("%v: %w", wrapped.Args, err) + } + return nil +} diff --git a/internal/pgtest/pg_test_db.go b/internal/pgtest/pg_test_db.go index 1ee8388c..42e4172c 100644 --- a/internal/pgtest/pg_test_db.go +++ b/internal/pgtest/pg_test_db.go @@ -3,6 +3,7 @@ package pgtest import ( "context" "math/rand/v2" + "net/url" "os" "strconv" "strings" @@ -22,7 +23,7 @@ type Option func(config *pgx.ConnConfig) func NewPostgresSchemaString(t *testing.T, sql string, opts ...Option) (*pgx.Conn, CleanupFunc) { t.Helper() // Create a new schema. - connStr := "user=postgres password=hunter2 host=localhost port=5555 dbname=pggen" + connStr := postgresConnString() ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() conn, err := pgx.Connect(ctx, connStr) @@ -36,10 +37,10 @@ func NewPostgresSchemaString(t *testing.T, sql string, opts ...Option) (*pgx.Con t.Logf("created schema: %s", schema) // Load SQL files into new schema. - connStr += " search_path=" + schema - connCfg, err := pgx.ParseConfig(connStr) + schemaConnStr := postgresSchemaConnString(connStr, schema) + connCfg, err := pgx.ParseConfig(schemaConnStr) if err != nil { - t.Fatalf("parse config: %q: %s", connStr, err) + t.Fatalf("parse config: %q: %s", schemaConnStr, err) } for _, opt := range opts { opt(connCfg) @@ -69,6 +70,23 @@ func NewPostgresSchemaString(t *testing.T, sql string, opts ...Option) (*pgx.Con return schemaConn, cleanup } +func postgresConnString() string { + if connStr := os.Getenv("PGURL"); connStr != "" { + return connStr + } + return "user=postgres password=hunter2 host=localhost port=5555 dbname=pggen" +} + +func postgresSchemaConnString(connStr string, schema string) string { + if u, err := url.Parse(connStr); err == nil && (u.Scheme == "postgres" || u.Scheme == "postgresql") { + q := u.Query() + q.Set("search_path", schema) + u.RawQuery = q.Encode() + return u.String() + } + return connStr + " search_path=" + schema +} + // NewPostgresSchema opens a connection with search_path set to a randomly // named, new schema and loads all sqlFiles. func NewPostgresSchema(t *testing.T, sqlFiles []string, opts ...Option) (*pgx.Conn, CleanupFunc) { From 79e1b92769af8f70e18ca21b546189711a62456c Mon Sep 17 00:00:00 2001 From: meoyawn Date: Wed, 6 May 2026 14:40:57 +0000 Subject: [PATCH 09/27] Finish pgx v5 generated query support --- example/acceptance_test.go | 2 +- example/author/query.sql.go | 285 ++++----------- example/author/query.sql_test.go | 1 + example/complex_params/query.sql.go | 324 +++++------------- example/composite/query.sql.go | 310 ++++------------- example/composite/query.sql_test.go | 2 +- example/custom_types/query.sql.go | 129 +++---- example/custom_types/query.sql_test.go | 12 +- example/device/query.sql.go | 269 ++++----------- example/device/query.sql_test.go | 24 +- example/domain/query.sql.go | 96 ++---- example/enums/query.sql.go | 277 ++++----------- example/enums/query.sql_test.go | 7 +- example/erp/order/customer.sql.go | 193 ++++------- example/erp/order/customer.sql_test.go | 4 +- example/erp/order/price.sql.go | 47 +-- example/function/query.sql.go | 198 +++-------- example/go_pointer_types/query.sql.go | 175 ++++------ .../inline_param_count/inline0/query.sql.go | 138 ++++---- .../inline_param_count/inline1/query.sql.go | 138 ++++---- .../inline_param_count/inline2/query.sql.go | 138 ++++---- .../inline_param_count/inline3/query.sql.go | 138 ++++---- example/ltree/codegen_test.go | 2 +- example/ltree/query.sql.go | 140 +++----- example/ltree/query.sql_test.go | 41 +-- example/nested/query.sql.go | 225 +++--------- example/pgcrypto/query.sql.go | 104 +++--- .../separate_out_dir/out/alpha_query.sql.0.go | 180 +++------- .../separate_out_dir/out/alpha_query.sql.1.go | 13 +- .../separate_out_dir/out/bravo_query.sql.go | 13 +- example/slices/query.sql.go | 203 +++-------- example/syntax/query.sql.go | 196 +++++------ example/void/query.sql.go | 147 ++++---- internal/codegen/golang/declarer.go | 125 +++---- internal/codegen/golang/declarer_array.go | 143 +------- internal/codegen/golang/declarer_composite.go | 203 +---------- internal/codegen/golang/declarer_enum.go | 36 +- internal/codegen/golang/gotype/known_types.go | 73 ++-- internal/codegen/golang/import_set.go | 21 +- internal/codegen/golang/query.gotemplate | 76 +--- internal/codegen/golang/templated_file.go | 93 +++-- internal/codegen/golang/templater.go | 4 +- .../testdata/declarer_composite.input.golden | 132 ++----- .../testdata/declarer_composite.output.golden | 118 ++----- .../declarer_composite_array.input.golden | 150 ++------ .../declarer_composite_array.output.golden | 124 ++----- .../declarer_composite_enum.input.golden | 136 ++------ .../declarer_composite_enum.output.golden | 125 ++----- .../declarer_composite_nested.input.golden | 149 ++------ .../declarer_composite_nested.output.golden | 129 ++----- .../declarer_enum_escaping.input.golden | 53 +-- .../declarer_enum_escaping.output.golden | 53 +-- .../declarer_enum_simple.input.golden | 53 +-- .../declarer_enum_simple.output.golden | 53 +-- internal/pg/query.sql.go | 193 +++++------ internal/pg/type_fetcher.go | 8 +- internal/pg/type_fetcher_test.go | 2 +- internal/pginfer/pginfer.go | 10 +- internal/pgtest/pg_test_db.go | 80 +++++ 59 files changed, 2100 insertions(+), 4413 deletions(-) diff --git a/example/acceptance_test.go b/example/acceptance_test.go index 5a8cb469..8fac5cf5 100644 --- a/example/acceptance_test.go +++ b/example/acceptance_test.go @@ -180,7 +180,7 @@ func TestExamples(t *testing.T) { "--schema-glob", "example/ltree/schema.sql", "--query-glob", "example/ltree/query.sql", "--go-type", "ltree=github.com/jackc/pgx/v5/pgtype.Text", - "--go-type", "_ltree=github.com/jackc/pgx/v5/pgtype.TextArray", + "--go-type", "_ltree=[]string", }, }, { diff --git a/example/author/query.sql.go b/example/author/query.sql.go index 5af26d39..f950e443 100644 --- a/example/author/query.sql.go +++ b/example/author/query.sql.go @@ -7,10 +7,10 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" - "sync" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { // FindAuthorById finds one (or zero) authors by ID. @@ -64,146 +64,116 @@ func NewQuerier(conn genericConn) *DBQuerier { return &DBQuerier{conn: conn} } +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining + } + return nil +} + +var typesToRegister = []string{} + +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} +} + const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` type FindAuthorByIDRow struct { - AuthorID int32 `json:"author_id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Suffix *string `json:"suffix"` + AuthorID int32 `json:"author_id" db:"author_id"` + FirstName string `json:"first_name" db:"first_name"` + LastName string `json:"last_name" db:"last_name"` + Suffix *string `json:"suffix" db:"suffix"` } // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthorByID") + ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") rows, err := q.conn.Query(ctx, findAuthorByIDSQL, authorID) if err != nil { return FindAuthorByIDRow{}, fmt.Errorf("query FindAuthorByID: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planScan(pgtype.TextCodec{}, fds[0], (*int32)(nil)) - plan1 := planScan(pgtype.TextCodec{}, fds[1], (*string)(nil)) - plan2 := planScan(pgtype.TextCodec{}, fds[2], (*string)(nil)) - plan3 := planScan(pgtype.TextCodec{}, fds[3], (**string)(nil)) - - return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (FindAuthorByIDRow, error) { - vals := row.RawValues() - var item FindAuthorByIDRow - if err := plan0.Scan(vals[0], &item); err != nil { - return item, fmt.Errorf("scan FindAuthorByID.author_id: %w", err) - } - if err := plan1.Scan(vals[1], &item); err != nil { - return item, fmt.Errorf("scan FindAuthorByID.first_name: %w", err) - } - if err := plan2.Scan(vals[2], &item); err != nil { - return item, fmt.Errorf("scan FindAuthorByID.last_name: %w", err) - } - if err := plan3.Scan(vals[3], &item); err != nil { - return item, fmt.Errorf("scan FindAuthorByID.suffix: %w", err) - } - return item, nil - }) + + return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) } const findAuthorsSQL = `SELECT * FROM author WHERE first_name = $1;` type FindAuthorsRow struct { - AuthorID int32 `json:"author_id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Suffix *string `json:"suffix"` + AuthorID int32 `json:"author_id" db:"author_id"` + FirstName string `json:"first_name" db:"first_name"` + LastName string `json:"last_name" db:"last_name"` + Suffix *string `json:"suffix" db:"suffix"` } // FindAuthors implements Querier.FindAuthors. func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAuthorsRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthors") + ctx = context.WithValue(ctx, QueryName{}, "FindAuthors") rows, err := q.conn.Query(ctx, findAuthorsSQL, firstName) if err != nil { return nil, fmt.Errorf("query FindAuthors: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planScan(pgtype.TextCodec{}, fds[0], (*int32)(nil)) - plan1 := planScan(pgtype.TextCodec{}, fds[1], (*string)(nil)) - plan2 := planScan(pgtype.TextCodec{}, fds[2], (*string)(nil)) - plan3 := planScan(pgtype.TextCodec{}, fds[3], (**string)(nil)) - - return pgx.CollectRows(rows, func(row pgx.CollectableRow) (FindAuthorsRow, error) { - vals := row.RawValues() - var item FindAuthorsRow - if err := plan0.Scan(vals[0], &item); err != nil { - return item, fmt.Errorf("scan FindAuthors.author_id: %w", err) - } - if err := plan1.Scan(vals[1], &item); err != nil { - return item, fmt.Errorf("scan FindAuthors.first_name: %w", err) - } - if err := plan2.Scan(vals[2], &item); err != nil { - return item, fmt.Errorf("scan FindAuthors.last_name: %w", err) - } - if err := plan3.Scan(vals[3], &item); err != nil { - return item, fmt.Errorf("scan FindAuthors.suffix: %w", err) - } - return item, nil - }) + + return pgx.CollectRows(rows, pgx.RowToStructByName[FindAuthorsRow]) } const findAuthorNamesSQL = `SELECT first_name, last_name FROM author ORDER BY author_id = $1;` type FindAuthorNamesRow struct { - FirstName *string `json:"first_name"` - LastName *string `json:"last_name"` + FirstName *string `json:"first_name" db:"first_name"` + LastName *string `json:"last_name" db:"last_name"` } // FindAuthorNames implements Querier.FindAuthorNames. func (q *DBQuerier) FindAuthorNames(ctx context.Context, authorID int32) ([]FindAuthorNamesRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthorNames") + ctx = context.WithValue(ctx, QueryName{}, "FindAuthorNames") rows, err := q.conn.Query(ctx, findAuthorNamesSQL, authorID) if err != nil { return nil, fmt.Errorf("query FindAuthorNames: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planScan(pgtype.TextCodec{}, fds[0], (**string)(nil)) - plan1 := planScan(pgtype.TextCodec{}, fds[1], (**string)(nil)) - - return pgx.CollectRows(rows, func(row pgx.CollectableRow) (FindAuthorNamesRow, error) { - vals := row.RawValues() - var item FindAuthorNamesRow - if err := plan0.Scan(vals[0], &item); err != nil { - return item, fmt.Errorf("scan FindAuthorNames.first_name: %w", err) - } - if err := plan1.Scan(vals[1], &item); err != nil { - return item, fmt.Errorf("scan FindAuthorNames.last_name: %w", err) - } - return item, nil - }) + + return pgx.CollectRows(rows, pgx.RowToStructByName[FindAuthorNamesRow]) } const findFirstNamesSQL = `SELECT first_name FROM author ORDER BY author_id = $1;` // FindFirstNames implements Querier.FindFirstNames. func (q *DBQuerier) FindFirstNames(ctx context.Context, authorID int32) ([]*string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindFirstNames") + ctx = context.WithValue(ctx, QueryName{}, "FindFirstNames") rows, err := q.conn.Query(ctx, findFirstNamesSQL, authorID) if err != nil { return nil, fmt.Errorf("query FindFirstNames: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planScan(pgtype.TextCodec{}, fds[0], (**string)(nil)) - - return pgx.CollectRows(rows, func(row pgx.CollectableRow) (*string, error) { - vals := row.RawValues() - var item *string - if err := plan0.Scan(vals[0], &item); err != nil { - return item, fmt.Errorf("scan FindFirstNames.first_name: %w", err) - } - return item, nil - }) + + return pgx.CollectRows(rows, pgx.RowTo[*string]) } const deleteAuthorsSQL = `DELETE FROM author WHERE first_name = 'joe';` // DeleteAuthors implements Querier.DeleteAuthors. func (q *DBQuerier) DeleteAuthors(ctx context.Context) (pgconn.CommandTag, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "DeleteAuthors") + ctx = context.WithValue(ctx, QueryName{}, "DeleteAuthors") cmdTag, err := q.conn.Exec(ctx, deleteAuthorsSQL) if err != nil { return pgconn.CommandTag{}, fmt.Errorf("exec query DeleteAuthors: %w", err) @@ -215,7 +185,7 @@ const deleteAuthorsByFirstNameSQL = `DELETE FROM author WHERE first_name = $1;` // DeleteAuthorsByFirstName implements Querier.DeleteAuthorsByFirstName. func (q *DBQuerier) DeleteAuthorsByFirstName(ctx context.Context, firstName string) (pgconn.CommandTag, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "DeleteAuthorsByFirstName") + ctx = context.WithValue(ctx, QueryName{}, "DeleteAuthorsByFirstName") cmdTag, err := q.conn.Exec(ctx, deleteAuthorsByFirstNameSQL, firstName) if err != nil { return pgconn.CommandTag{}, fmt.Errorf("exec query DeleteAuthorsByFirstName: %w", err) @@ -237,7 +207,7 @@ type DeleteAuthorsByFullNameParams struct { // DeleteAuthorsByFullName implements Querier.DeleteAuthorsByFullName. func (q *DBQuerier) DeleteAuthorsByFullName(ctx context.Context, params DeleteAuthorsByFullNameParams) (pgconn.CommandTag, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "DeleteAuthorsByFullName") + ctx = context.WithValue(ctx, QueryName{}, "DeleteAuthorsByFullName") cmdTag, err := q.conn.Exec(ctx, deleteAuthorsByFullNameSQL, params.FirstName, params.LastName, params.Suffix) if err != nil { return pgconn.CommandTag{}, fmt.Errorf("exec query DeleteAuthorsByFullName: %w", err) @@ -251,22 +221,13 @@ RETURNING author_id;` // InsertAuthor implements Querier.InsertAuthor. func (q *DBQuerier) InsertAuthor(ctx context.Context, firstName string, lastName string) (int32, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "InsertAuthor") + ctx = context.WithValue(ctx, QueryName{}, "InsertAuthor") rows, err := q.conn.Query(ctx, insertAuthorSQL, firstName, lastName) if err != nil { return 0, fmt.Errorf("query InsertAuthor: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planScan(pgtype.TextCodec{}, fds[0], (*int32)(nil)) - - return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (int32, error) { - vals := row.RawValues() - var item int32 - if err := plan0.Scan(vals[0], &item); err != nil { - return item, fmt.Errorf("scan InsertAuthor.author_id: %w", err) - } - return item, nil - }) + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int32]) } const insertAuthorSuffixSQL = `INSERT INTO author (first_name, last_name, suffix) @@ -280,139 +241,45 @@ type InsertAuthorSuffixParams struct { } type InsertAuthorSuffixRow struct { - AuthorID int32 `json:"author_id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Suffix *string `json:"suffix"` + AuthorID int32 `json:"author_id" db:"author_id"` + FirstName string `json:"first_name" db:"first_name"` + LastName string `json:"last_name" db:"last_name"` + Suffix *string `json:"suffix" db:"suffix"` } // InsertAuthorSuffix implements Querier.InsertAuthorSuffix. func (q *DBQuerier) InsertAuthorSuffix(ctx context.Context, params InsertAuthorSuffixParams) (InsertAuthorSuffixRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "InsertAuthorSuffix") + ctx = context.WithValue(ctx, QueryName{}, "InsertAuthorSuffix") rows, err := q.conn.Query(ctx, insertAuthorSuffixSQL, params.FirstName, params.LastName, params.Suffix) if err != nil { return InsertAuthorSuffixRow{}, fmt.Errorf("query InsertAuthorSuffix: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planScan(pgtype.TextCodec{}, fds[0], (*int32)(nil)) - plan1 := planScan(pgtype.TextCodec{}, fds[1], (*string)(nil)) - plan2 := planScan(pgtype.TextCodec{}, fds[2], (*string)(nil)) - plan3 := planScan(pgtype.TextCodec{}, fds[3], (**string)(nil)) - - return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (InsertAuthorSuffixRow, error) { - vals := row.RawValues() - var item InsertAuthorSuffixRow - if err := plan0.Scan(vals[0], &item); err != nil { - return item, fmt.Errorf("scan InsertAuthorSuffix.author_id: %w", err) - } - if err := plan1.Scan(vals[1], &item); err != nil { - return item, fmt.Errorf("scan InsertAuthorSuffix.first_name: %w", err) - } - if err := plan2.Scan(vals[2], &item); err != nil { - return item, fmt.Errorf("scan InsertAuthorSuffix.last_name: %w", err) - } - if err := plan3.Scan(vals[3], &item); err != nil { - return item, fmt.Errorf("scan InsertAuthorSuffix.suffix: %w", err) - } - return item, nil - }) + + return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[InsertAuthorSuffixRow]) } const stringAggFirstNameSQL = `SELECT string_agg(first_name, ',') AS names FROM author WHERE author_id = $1;` // StringAggFirstName implements Querier.StringAggFirstName. func (q *DBQuerier) StringAggFirstName(ctx context.Context, authorID int32) (*string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "StringAggFirstName") + ctx = context.WithValue(ctx, QueryName{}, "StringAggFirstName") rows, err := q.conn.Query(ctx, stringAggFirstNameSQL, authorID) if err != nil { return nil, fmt.Errorf("query StringAggFirstName: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planScan(pgtype.TextCodec{}, fds[0], (**string)(nil)) - - return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (*string, error) { - vals := row.RawValues() - var item *string - if err := plan0.Scan(vals[0], &item); err != nil { - return item, fmt.Errorf("scan StringAggFirstName.names: %w", err) - } - return item, nil - }) + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*string]) } const arrayAggFirstNameSQL = `SELECT array_agg(first_name) AS names FROM author WHERE author_id = $1;` // ArrayAggFirstName implements Querier.ArrayAggFirstName. func (q *DBQuerier) ArrayAggFirstName(ctx context.Context, authorID int32) ([]string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "ArrayAggFirstName") + ctx = context.WithValue(ctx, QueryName{}, "ArrayAggFirstName") rows, err := q.conn.Query(ctx, arrayAggFirstNameSQL, authorID) if err != nil { return nil, fmt.Errorf("query ArrayAggFirstName: %w", err) } - fds := rows.FieldDescriptions() - plan0 := planScan(pgtype.TextCodec{}, fds[0], (*[]string)(nil)) - - return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) ([]string, error) { - vals := row.RawValues() - var item []string - if err := plan0.Scan(vals[0], &item); err != nil { - return item, fmt.Errorf("scan ArrayAggFirstName.names: %w", err) - } - return item, nil - }) -} - -type scanCacheKey struct { - oid uint32 - format int16 - typeName string -} - -var ( - plans = make(map[scanCacheKey]pgtype.ScanPlan, 16) - plansMu sync.RWMutex -) - -func planScan(codec pgtype.Codec, fd pgconn.FieldDescription, target any) pgtype.ScanPlan { - key := scanCacheKey{fd.DataTypeOID, fd.Format, fmt.Sprintf("%T", target)} - plansMu.RLock() - plan := plans[key] - plansMu.RUnlock() - if plan != nil { - return plan - } - plan = codec.PlanScan(nil, fd.DataTypeOID, fd.Format, target) - plansMu.Lock() - plans[key] = plan - plansMu.Unlock() - return plan -} - -type ptrScanner[T any] struct { - basePlan pgtype.ScanPlan -} - -func (s ptrScanner[T]) Scan(src []byte, dst any) error { - if src == nil { - return nil - } - d := dst.(**T) - *d = new(T) - return s.basePlan.Scan(src, *d) -} -func planPtrScan[T any](codec pgtype.Codec, fd pgconn.FieldDescription, target *T) pgtype.ScanPlan { - key := scanCacheKey{fd.DataTypeOID, fd.Format, fmt.Sprintf("*%T", target)} - plansMu.RLock() - plan := plans[key] - plansMu.RUnlock() - if plan != nil { - return plan - } - basePlan := planScan(codec, fd, target) - ptrPlan := ptrScanner[T]{basePlan} - plansMu.Lock() - plans[key] = plan - plansMu.Unlock() - return ptrPlan + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]string]) } diff --git a/example/author/query.sql_test.go b/example/author/query.sql_test.go index 8565f9ff..2e15de91 100644 --- a/example/author/query.sql_test.go +++ b/example/author/query.sql_test.go @@ -69,6 +69,7 @@ func TestNewQuerier_FindAuthors(t *testing.T) { LastName: "clinton", Suffix: "jr", }) + require.NoError(t, err) authors, err := q.FindAuthors(t.Context(), "bill") require.NoError(t, err) want := []FindAuthorsRow{ diff --git a/example/complex_params/query.sql.go b/example/complex_params/query.sql.go index fc0589f8..6f4c9e84 100644 --- a/example/complex_params/query.sql.go +++ b/example/complex_params/query.sql.go @@ -7,9 +7,10 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { ParamArrayInt(ctx context.Context, ints []int) ([]int, error) @@ -26,8 +27,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -39,313 +39,149 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} + return &DBQuerier{conn: conn} } // Dimensions represents the Postgres composite type "dimensions". type Dimensions struct { - Width int `json:"width"` - Height int `json:"height"` + Width int `json:"width" db:"width"` + Height int `json:"height" db:"height"` } // ProductImageSetType represents the Postgres composite type "product_image_set_type". type ProductImageSetType struct { - Name string `json:"name"` - OrigImage ProductImageType `json:"orig_image"` - Images []ProductImageType `json:"images"` + Name string `json:"name" db:"name"` + OrigImage ProductImageType `json:"orig_image" db:"orig_image"` + Images []ProductImageType `json:"images" db:"images"` } // ProductImageType represents the Postgres composite type "product_image_type". type ProductImageType struct { - Source string `json:"source"` - Dimensions Dimensions `json:"dimensions"` + Source string `json:"source" db:"source"` + Dimensions Dimensions `json:"dimensions" db:"dimensions"` +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining + } + return nil } -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} +var typesToRegister = []string{} -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false - } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true -} -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use -} +var _ = addTypeToRegister("\"dimensions\"") -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -// newDimensions creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'dimensions'. -func (tr *typeResolver) newDimensions() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "dimensions", - compositeField{name: "width", typeName: "int4", defaultVal: &pgtype.Int4{}}, - compositeField{name: "height", typeName: "int4", defaultVal: &pgtype.Int4{}}, - ) -} -// newDimensionsInit creates an initialized pgtype.ValueTranscoder for the -// Postgres composite type 'dimensions' to encode query parameters. -func (tr *typeResolver) newDimensionsInit(v Dimensions) pgtype.ValueTranscoder { - return tr.setValue(tr.newDimensions(), tr.newDimensionsRaw(v)) -} -// newDimensionsRaw returns all composite fields for the Postgres composite -// type 'dimensions' as a slice of interface{} to encode query parameters. -func (tr *typeResolver) newDimensionsRaw(v Dimensions) []interface{} { - return []interface{}{ - v.Width, - v.Height, - } -} +var _ = addTypeToRegister("\"product_image_set_type\"") -// newProductImageSetType creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'product_image_set_type'. -func (tr *typeResolver) newProductImageSetType() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "product_image_set_type", - compositeField{name: "name", typeName: "text", defaultVal: &pgtype.Text{}}, - compositeField{name: "orig_image", typeName: "product_image_type", defaultVal: tr.newProductImageType()}, - compositeField{name: "images", typeName: "_product_image_type", defaultVal: tr.newProductImageTypeArray()}, - ) -} -// newProductImageSetTypeInit creates an initialized pgtype.ValueTranscoder for the -// Postgres composite type 'product_image_set_type' to encode query parameters. -func (tr *typeResolver) newProductImageSetTypeInit(v ProductImageSetType) pgtype.ValueTranscoder { - return tr.setValue(tr.newProductImageSetType(), tr.newProductImageSetTypeRaw(v)) -} -// newProductImageSetTypeRaw returns all composite fields for the Postgres composite -// type 'product_image_set_type' as a slice of interface{} to encode query parameters. -func (tr *typeResolver) newProductImageSetTypeRaw(v ProductImageSetType) []interface{} { - return []interface{}{ - v.Name, - tr.newProductImageTypeRaw(v.OrigImage), - tr.newProductImageTypeArrayRaw(v.Images), - } -} -// newProductImageType creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'product_image_type'. -func (tr *typeResolver) newProductImageType() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "product_image_type", - compositeField{name: "source", typeName: "text", defaultVal: &pgtype.Text{}}, - compositeField{name: "dimensions", typeName: "dimensions", defaultVal: tr.newDimensions()}, - ) -} -// newProductImageTypeInit creates an initialized pgtype.ValueTranscoder for the -// Postgres composite type 'product_image_type' to encode query parameters. -func (tr *typeResolver) newProductImageTypeInit(v ProductImageType) pgtype.ValueTranscoder { - return tr.setValue(tr.newProductImageType(), tr.newProductImageTypeRaw(v)) -} +var _ = addTypeToRegister("\"product_image_type\"") + + + + + +var _ = addTypeToRegister("\"_product_image_type\"") -// newProductImageTypeRaw returns all composite fields for the Postgres composite -// type 'product_image_type' as a slice of interface{} to encode query parameters. -func (tr *typeResolver) newProductImageTypeRaw(v ProductImageType) []interface{} { - return []interface{}{ - v.Source, - tr.newDimensionsRaw(v.Dimensions), - } -} -// newProductImageTypeArray creates a new pgtype.ValueTranscoder for the Postgres -// '_product_image_type' array type. -func (tr *typeResolver) newProductImageTypeArray() pgtype.ValueTranscoder { - return tr.newArrayValue("_product_image_type", "product_image_type", tr.newProductImageType) -} -// newProductImageTypeArrayInit creates an initialized pgtype.ValueTranscoder for the -// Postgres array type '_product_image_type' to encode query parameters. -func (tr *typeResolver) newProductImageTypeArrayInit(ps []ProductImageType) pgtype.ValueTranscoder { - dec := tr.newProductImageTypeArray() - if err := dec.Set(tr.newProductImageTypeArrayRaw(ps)); err != nil { - panic("encode []ProductImageType: " + err.Error()) // should always succeed - } - return textPreferrer{ValueTranscoder: dec, typeName: "_product_image_type"} -} -// newProductImageTypeArrayRaw returns all elements for the Postgres array type '_product_image_type' -// as a slice of interface{} for use with the pgtype.Value Set method. -func (tr *typeResolver) newProductImageTypeArrayRaw(vs []ProductImageType) []interface{} { - elems := make([]interface{}, len(vs)) - for i, v := range vs { - elems[i] = tr.newProductImageTypeRaw(v) - } - return elems -} const paramArrayIntSQL = `SELECT $1::bigint[];` // ParamArrayInt implements Querier.ParamArrayInt. func (q *DBQuerier) ParamArrayInt(ctx context.Context, ints []int) ([]int, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "ParamArrayInt") - row := q.conn.QueryRow(ctx, paramArrayIntSQL, ints) - item := []int{} - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query ParamArrayInt: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "ParamArrayInt") + rows, err := q.conn.Query(ctx, paramArrayIntSQL, ints) + if err != nil { + return nil, fmt.Errorf("query ParamArrayInt: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]int]) } const paramNested1SQL = `SELECT $1::dimensions;` // ParamNested1 implements Querier.ParamNested1. func (q *DBQuerier) ParamNested1(ctx context.Context, dimensions Dimensions) (Dimensions, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "ParamNested1") - row := q.conn.QueryRow(ctx, paramNested1SQL, q.types.newDimensionsInit(dimensions)) - var item Dimensions - dimensionsRow := q.types.newDimensions() - if err := row.Scan(dimensionsRow); err != nil { - return item, fmt.Errorf("query ParamNested1: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "ParamNested1") + rows, err := q.conn.Query(ctx, paramNested1SQL, dimensions) + if err != nil { + return Dimensions{}, fmt.Errorf("query ParamNested1: %w", err) } - if err := dimensionsRow.AssignTo(&item); err != nil { - return item, fmt.Errorf("assign ParamNested1 row: %w", err) - } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[Dimensions]) } const paramNested2SQL = `SELECT $1::product_image_type;` // ParamNested2 implements Querier.ParamNested2. func (q *DBQuerier) ParamNested2(ctx context.Context, image ProductImageType) (ProductImageType, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "ParamNested2") - row := q.conn.QueryRow(ctx, paramNested2SQL, q.types.newProductImageTypeInit(image)) - var item ProductImageType - productImageTypeRow := q.types.newProductImageType() - if err := row.Scan(productImageTypeRow); err != nil { - return item, fmt.Errorf("query ParamNested2: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "ParamNested2") + rows, err := q.conn.Query(ctx, paramNested2SQL, image) + if err != nil { + return ProductImageType{}, fmt.Errorf("query ParamNested2: %w", err) } - if err := productImageTypeRow.AssignTo(&item); err != nil { - return item, fmt.Errorf("assign ParamNested2 row: %w", err) - } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[ProductImageType]) } const paramNested2ArraySQL = `SELECT $1::product_image_type[];` // ParamNested2Array implements Querier.ParamNested2Array. func (q *DBQuerier) ParamNested2Array(ctx context.Context, images []ProductImageType) ([]ProductImageType, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "ParamNested2Array") - row := q.conn.QueryRow(ctx, paramNested2ArraySQL, q.types.newProductImageTypeArrayInit(images)) - item := []ProductImageType{} - productImageTypeArray := q.types.newProductImageTypeArray() - if err := row.Scan(productImageTypeArray); err != nil { - return item, fmt.Errorf("query ParamNested2Array: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "ParamNested2Array") + rows, err := q.conn.Query(ctx, paramNested2ArraySQL, images) + if err != nil { + return nil, fmt.Errorf("query ParamNested2Array: %w", err) } - if err := productImageTypeArray.AssignTo(&item); err != nil { - return item, fmt.Errorf("assign ParamNested2Array row: %w", err) - } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]ProductImageType]) } const paramNested3SQL = `SELECT $1::product_image_set_type;` // ParamNested3 implements Querier.ParamNested3. func (q *DBQuerier) ParamNested3(ctx context.Context, imageSet ProductImageSetType) (ProductImageSetType, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "ParamNested3") - row := q.conn.QueryRow(ctx, paramNested3SQL, q.types.newProductImageSetTypeInit(imageSet)) - var item ProductImageSetType - productImageSetTypeRow := q.types.newProductImageSetType() - if err := row.Scan(productImageSetTypeRow); err != nil { - return item, fmt.Errorf("query ParamNested3: %w", err) - } - if err := productImageSetTypeRow.AssignTo(&item); err != nil { - return item, fmt.Errorf("assign ParamNested3 row: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "ParamNested3") + rows, err := q.conn.Query(ctx, paramNested3SQL, imageSet) + if err != nil { + return ProductImageSetType{}, fmt.Errorf("query ParamNested3: %w", err) } - return item, nil -} - -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[ProductImageSetType]) } - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/composite/query.sql.go b/example/composite/query.sql.go index a62844af..87632eb1 100644 --- a/example/composite/query.sql.go +++ b/example/composite/query.sql.go @@ -10,6 +10,8 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { SearchScreenshots(ctx context.Context, params SearchScreenshotsParams) ([]SearchScreenshotsRow, error) @@ -26,8 +28,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -39,175 +40,79 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} + return &DBQuerier{conn: conn} } // Arrays represents the Postgres composite type "arrays". type Arrays struct { - Texts []string `json:"texts"` - Int8s []*int `json:"int8s"` - Bools []bool `json:"bools"` - Floats []*float64 `json:"floats"` + Texts []string `json:"texts" db:"texts"` + Int8s []*int `json:"int8s" db:"int8s"` + Bools []bool `json:"bools" db:"bools"` + Floats []*float64 `json:"floats" db:"floats"` } // Blocks represents the Postgres composite type "blocks". type Blocks struct { - ID int `json:"id"` - ScreenshotID int `json:"screenshot_id"` - Body string `json:"body"` + ID int `json:"id" db:"id"` + ScreenshotID int `json:"screenshot_id" db:"screenshot_id"` + Body string `json:"body" db:"body"` } // UserEmail represents the Postgres composite type "user_email". type UserEmail struct { - ID string `json:"id"` - Email pgtype.Text `json:"email"` + ID string `json:"id" db:"id"` + Email pgtype.Text `json:"email" db:"email"` +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining + } + return nil } -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} +var typesToRegister = []string{} -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false - } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true -} -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use -} +var _ = addTypeToRegister("\"arrays\"") -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -// newArrays creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'arrays'. -func (tr *typeResolver) newArrays() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "arrays", - compositeField{name: "texts", typeName: "_text", defaultVal: &pgtype.TextArray{}}, - compositeField{name: "int8s", typeName: "_int8", defaultVal: &pgtype.Int8Array{}}, - compositeField{name: "bools", typeName: "_bool", defaultVal: &pgtype.BoolArray{}}, - compositeField{name: "floats", typeName: "_float8", defaultVal: &pgtype.Float8Array{}}, - ) -} -// newArraysInit creates an initialized pgtype.ValueTranscoder for the -// Postgres composite type 'arrays' to encode query parameters. -func (tr *typeResolver) newArraysInit(v Arrays) pgtype.ValueTranscoder { - return tr.setValue(tr.newArrays(), tr.newArraysRaw(v)) -} -// newArraysRaw returns all composite fields for the Postgres composite -// type 'arrays' as a slice of interface{} to encode query parameters. -func (tr *typeResolver) newArraysRaw(v Arrays) []interface{} { - return []interface{}{ - v.Texts, - v.Int8s, - v.Bools, - v.Floats, - } -} +var _ = addTypeToRegister("\"blocks\"") -// newBlocks creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'blocks'. -func (tr *typeResolver) newBlocks() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "blocks", - compositeField{name: "id", typeName: "int4", defaultVal: &pgtype.Int4{}}, - compositeField{name: "screenshot_id", typeName: "int8", defaultVal: &pgtype.Int8{}}, - compositeField{name: "body", typeName: "text", defaultVal: &pgtype.Text{}}, - ) -} +var _ = addTypeToRegister("\"user_email\"") -// newUserEmail creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'user_email'. -func (tr *typeResolver) newUserEmail() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "user_email", - compositeField{name: "id", typeName: "text", defaultVal: &pgtype.Text{}}, - compositeField{name: "email", typeName: "citext", defaultVal: &pgtype.Text{}}, - ) -} +var _ = addTypeToRegister("\"_blocks\"") -// newBlocksArray creates a new pgtype.ValueTranscoder for the Postgres -// '_blocks' array type. -func (tr *typeResolver) newBlocksArray() pgtype.ValueTranscoder { - return tr.newArrayValue("_blocks", "blocks", tr.newBlocks) -} -// newboolArrayRaw returns all elements for the Postgres array type '_bool' -// as a slice of interface{} for use with the pgtype.Value Set method. -func (tr *typeResolver) newboolArrayRaw(vs []bool) []interface{} { - elems := make([]interface{}, len(vs)) - for i, v := range vs { - elems[i] = v - } - return elems -} const searchScreenshotsSQL = `SELECT ss.id, @@ -226,34 +131,19 @@ type SearchScreenshotsParams struct { } type SearchScreenshotsRow struct { - ID int `json:"id"` - Blocks []Blocks `json:"blocks"` + ID int `json:"id" db:"id"` + Blocks []Blocks `json:"blocks" db:"blocks"` } // SearchScreenshots implements Querier.SearchScreenshots. func (q *DBQuerier) SearchScreenshots(ctx context.Context, params SearchScreenshotsParams) ([]SearchScreenshotsRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "SearchScreenshots") + ctx = context.WithValue(ctx, QueryName{}, "SearchScreenshots") rows, err := q.conn.Query(ctx, searchScreenshotsSQL, params.Body, params.Limit, params.Offset) if err != nil { return nil, fmt.Errorf("query SearchScreenshots: %w", err) } - defer rows.Close() - items := []SearchScreenshotsRow{} - blocksArray := q.types.newBlocksArray() - for rows.Next() { - var item SearchScreenshotsRow - if err := rows.Scan(&item.ID, blocksArray); err != nil { - return nil, fmt.Errorf("scan SearchScreenshots row: %w", err) - } - if err := blocksArray.AssignTo(&item.Blocks); err != nil { - return nil, fmt.Errorf("assign SearchScreenshots row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close SearchScreenshots rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowToStructByName[SearchScreenshotsRow]) } const searchScreenshotsOneColSQL = `SELECT @@ -273,28 +163,13 @@ type SearchScreenshotsOneColParams struct { // SearchScreenshotsOneCol implements Querier.SearchScreenshotsOneCol. func (q *DBQuerier) SearchScreenshotsOneCol(ctx context.Context, params SearchScreenshotsOneColParams) ([][]Blocks, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "SearchScreenshotsOneCol") + ctx = context.WithValue(ctx, QueryName{}, "SearchScreenshotsOneCol") rows, err := q.conn.Query(ctx, searchScreenshotsOneColSQL, params.Body, params.Limit, params.Offset) if err != nil { return nil, fmt.Errorf("query SearchScreenshotsOneCol: %w", err) } - defer rows.Close() - items := [][]Blocks{} - blocksArray := q.types.newBlocksArray() - for rows.Next() { - var item []Blocks - if err := rows.Scan(blocksArray); err != nil { - return nil, fmt.Errorf("scan SearchScreenshotsOneCol row: %w", err) - } - if err := blocksArray.AssignTo(&item); err != nil { - return nil, fmt.Errorf("assign SearchScreenshotsOneCol row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close SearchScreenshotsOneCol rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowTo[[]Blocks]) } const insertScreenshotBlocksSQL = `WITH screens AS ( @@ -307,77 +182,44 @@ VALUES ($1, $2) RETURNING id, screenshot_id, body;` type InsertScreenshotBlocksRow struct { - ID int `json:"id"` - ScreenshotID int `json:"screenshot_id"` - Body string `json:"body"` + ID int `json:"id" db:"id"` + ScreenshotID int `json:"screenshot_id" db:"screenshot_id"` + Body string `json:"body" db:"body"` } // InsertScreenshotBlocks implements Querier.InsertScreenshotBlocks. func (q *DBQuerier) InsertScreenshotBlocks(ctx context.Context, screenshotID int, body string) (InsertScreenshotBlocksRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "InsertScreenshotBlocks") - row := q.conn.QueryRow(ctx, insertScreenshotBlocksSQL, screenshotID, body) - var item InsertScreenshotBlocksRow - if err := row.Scan(&item.ID, &item.ScreenshotID, &item.Body); err != nil { - return item, fmt.Errorf("query InsertScreenshotBlocks: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "InsertScreenshotBlocks") + rows, err := q.conn.Query(ctx, insertScreenshotBlocksSQL, screenshotID, body) + if err != nil { + return InsertScreenshotBlocksRow{}, fmt.Errorf("query InsertScreenshotBlocks: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[InsertScreenshotBlocksRow]) } const arraysInputSQL = `SELECT $1::arrays;` // ArraysInput implements Querier.ArraysInput. func (q *DBQuerier) ArraysInput(ctx context.Context, arrays Arrays) (Arrays, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "ArraysInput") - row := q.conn.QueryRow(ctx, arraysInputSQL, q.types.newArraysInit(arrays)) - var item Arrays - arraysRow := q.types.newArrays() - if err := row.Scan(arraysRow); err != nil { - return item, fmt.Errorf("query ArraysInput: %w", err) - } - if err := arraysRow.AssignTo(&item); err != nil { - return item, fmt.Errorf("assign ArraysInput row: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "ArraysInput") + rows, err := q.conn.Query(ctx, arraysInputSQL, arrays) + if err != nil { + return Arrays{}, fmt.Errorf("query ArraysInput: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[Arrays]) } const userEmailsSQL = `SELECT ('foo', 'bar@example.com')::user_email;` // UserEmails implements Querier.UserEmails. func (q *DBQuerier) UserEmails(ctx context.Context) (UserEmail, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "UserEmails") - row := q.conn.QueryRow(ctx, userEmailsSQL) - var item UserEmail - rowRow := q.types.newUserEmail() - if err := row.Scan(rowRow); err != nil { - return item, fmt.Errorf("query UserEmails: %w", err) - } - if err := rowRow.AssignTo(&item); err != nil { - return item, fmt.Errorf("assign UserEmails row: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "UserEmails") + rows, err := q.conn.Query(ctx, userEmailsSQL) + if err != nil { + return UserEmail{}, fmt.Errorf("query UserEmails: %w", err) } - return item, nil -} -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[UserEmail]) } - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/composite/query.sql_test.go b/example/composite/query.sql_test.go index a1d657c4..1724785a 100644 --- a/example/composite/query.sql_test.go +++ b/example/composite/query.sql_test.go @@ -87,7 +87,7 @@ func TestNewQuerier_UserEmails(t *testing.T) { require.NoError(t, err) want := UserEmail{ ID: "foo", - Email: pgtype.Text{String: "bar@example.com", Status: pgtype.Present}, + Email: pgtype.Text{String: "bar@example.com", Valid: true}, } difftest.AssertSame(t, want, got) } diff --git a/example/custom_types/query.sql.go b/example/custom_types/query.sql.go index 326d2b2d..6b4dbc74 100644 --- a/example/custom_types/query.sql.go +++ b/example/custom_types/query.sql.go @@ -7,10 +7,11 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" "github.com/jschaf/pggen/example/custom_types/mytype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { CustomTypes(ctx context.Context) (CustomTypesRow, error) @@ -23,8 +24,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -36,114 +36,83 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name + return &DBQuerier{conn: conn} } -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt +var typesToRegister = []string{} + +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } const customTypesSQL = `SELECT 'some_text', 1::bigint;` type CustomTypesRow struct { - Column mytype.String `json:"?column?"` - Int8 CustomInt `json:"int8"` + Column mytype.String `json:"?column?" db:"?column?"` + Int8 CustomInt `json:"int8" db:"int8"` } // CustomTypes implements Querier.CustomTypes. func (q *DBQuerier) CustomTypes(ctx context.Context) (CustomTypesRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "CustomTypes") - row := q.conn.QueryRow(ctx, customTypesSQL) - var item CustomTypesRow - if err := row.Scan(&item.Column, &item.Int8); err != nil { - return item, fmt.Errorf("query CustomTypes: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "CustomTypes") + rows, err := q.conn.Query(ctx, customTypesSQL) + if err != nil { + return CustomTypesRow{}, fmt.Errorf("query CustomTypes: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CustomTypesRow]) } const customMyIntSQL = `SELECT '5'::my_int as int5;` // CustomMyInt implements Querier.CustomMyInt. func (q *DBQuerier) CustomMyInt(ctx context.Context) (int, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "CustomMyInt") - row := q.conn.QueryRow(ctx, customMyIntSQL) - var item int - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query CustomMyInt: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "CustomMyInt") + rows, err := q.conn.Query(ctx, customMyIntSQL) + if err != nil { + return 0, fmt.Errorf("query CustomMyInt: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int]) } const intArraySQL = `SELECT ARRAY ['5', '6', '7']::int[] as ints;` // IntArray implements Querier.IntArray. func (q *DBQuerier) IntArray(ctx context.Context) ([][]int32, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "IntArray") + ctx = context.WithValue(ctx, QueryName{}, "IntArray") rows, err := q.conn.Query(ctx, intArraySQL) if err != nil { return nil, fmt.Errorf("query IntArray: %w", err) } - defer rows.Close() - items := [][]int32{} - for rows.Next() { - var item []int32 - if err := rows.Scan(&item); err != nil { - return nil, fmt.Errorf("scan IntArray row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close IntArray rows: %w", err) - } - return items, err -} -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string + return pgx.CollectRows(rows, pgx.RowTo[[]int32]) } - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/custom_types/query.sql_test.go b/example/custom_types/query.sql_test.go index fdc132a0..e2153007 100644 --- a/example/custom_types/query.sql_test.go +++ b/example/custom_types/query.sql_test.go @@ -38,15 +38,15 @@ func TestQuerier_CustomMyInt(t *testing.T) { AND pn.nspname = current_schema() LIMIT 1; `)) - oidVal := uint32Value{} - err := row.Scan(&oidVal) + var oid uint32 + err := row.Scan(&oid) require.NoError(t, err) - t.Logf("my_int oid: %d", oidVal.Uint) + t.Logf("my_int oid: %d", oid) - conn.ConnInfo().RegisterDataType(pgtype.DataType{ - Value: &pgtype.Int2{}, + conn.TypeMap().RegisterType(&pgtype.Type{ Name: "my_int", - OID: oidVal.Uint, + OID: oid, + Codec: pgtype.Int2Codec{}, }) q := NewQuerier(conn) diff --git a/example/device/query.sql.go b/example/device/query.sql.go index cdad2365..85b9ee91 100644 --- a/example/device/query.sql.go +++ b/example/device/query.sql.go @@ -7,9 +7,11 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" + "net" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { FindDevicesByUser(ctx context.Context, id int) ([]FindDevicesByUserRow, error) @@ -24,14 +26,13 @@ type Querier interface { InsertUser(ctx context.Context, userID int, name string) (pgconn.CommandTag, error) - InsertDevice(ctx context.Context, mac pgtype.Macaddr, owner int) (pgconn.CommandTag, error) + InsertDevice(ctx context.Context, mac net.HardwareAddr, owner int) (pgconn.CommandTag, error) } var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -43,13 +44,13 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} + return &DBQuerier{conn: conn} } // User represents the Postgres composite type "user". type User struct { - ID *int `json:"id"` - Name *string `json:"name"` + ID *int `json:"id" db:"id"` + Name *string `json:"name" db:"name"` } // DeviceType represents the Postgres enum "device_type". @@ -66,95 +67,45 @@ const ( func (d DeviceType) String() string { return string(d) } -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} +var typesToRegister = []string{} -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -// newUser creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'user'. -func (tr *typeResolver) newUser() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "user", - compositeField{name: "id", typeName: "int8", defaultVal: &pgtype.Int8{}}, - compositeField{name: "name", typeName: "text", defaultVal: &pgtype.Text{}}, - ) -} +var _ = addTypeToRegister("\"device_type\"") + +var _ = addTypeToRegister("\"user\"") const findDevicesByUserSQL = `SELECT id, @@ -164,31 +115,20 @@ FROM "user" WHERE id = $1;` type FindDevicesByUserRow struct { - ID int `json:"id"` - Name string `json:"name"` - MacAddrs pgtype.MacaddrArray `json:"mac_addrs"` + ID int `json:"id" db:"id"` + Name string `json:"name" db:"name"` + MacAddrs []net.HardwareAddr `json:"mac_addrs" db:"mac_addrs"` } // FindDevicesByUser implements Querier.FindDevicesByUser. func (q *DBQuerier) FindDevicesByUser(ctx context.Context, id int) ([]FindDevicesByUserRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindDevicesByUser") + ctx = context.WithValue(ctx, QueryName{}, "FindDevicesByUser") rows, err := q.conn.Query(ctx, findDevicesByUserSQL, id) if err != nil { return nil, fmt.Errorf("query FindDevicesByUser: %w", err) } - defer rows.Close() - items := []FindDevicesByUserRow{} - for rows.Next() { - var item FindDevicesByUserRow - if err := rows.Scan(&item.ID, &item.Name, &item.MacAddrs); err != nil { - return nil, fmt.Errorf("scan FindDevicesByUser row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindDevicesByUser rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowToStructByName[FindDevicesByUserRow]) } const compositeUserSQL = `SELECT @@ -199,102 +139,64 @@ FROM device d LEFT JOIN "user" u ON u.id = d.owner;` type CompositeUserRow struct { - Mac pgtype.Macaddr `json:"mac"` - Type DeviceType `json:"type"` - User User `json:"user"` + Mac net.HardwareAddr `json:"mac" db:"mac"` + Type DeviceType `json:"type" db:"type"` + User User `json:"user" db:"user"` } // CompositeUser implements Querier.CompositeUser. func (q *DBQuerier) CompositeUser(ctx context.Context) ([]CompositeUserRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "CompositeUser") + ctx = context.WithValue(ctx, QueryName{}, "CompositeUser") rows, err := q.conn.Query(ctx, compositeUserSQL) if err != nil { return nil, fmt.Errorf("query CompositeUser: %w", err) } - defer rows.Close() - items := []CompositeUserRow{} - userRow := q.types.newUser() - for rows.Next() { - var item CompositeUserRow - if err := rows.Scan(&item.Mac, &item.Type, userRow); err != nil { - return nil, fmt.Errorf("scan CompositeUser row: %w", err) - } - if err := userRow.AssignTo(&item.User); err != nil { - return nil, fmt.Errorf("assign CompositeUser row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close CompositeUser rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowToStructByName[CompositeUserRow]) } const compositeUserOneSQL = `SELECT ROW (15, 'qux')::"user" AS "user";` // CompositeUserOne implements Querier.CompositeUserOne. func (q *DBQuerier) CompositeUserOne(ctx context.Context) (User, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "CompositeUserOne") - row := q.conn.QueryRow(ctx, compositeUserOneSQL) - var item User - userRow := q.types.newUser() - if err := row.Scan(userRow); err != nil { - return item, fmt.Errorf("query CompositeUserOne: %w", err) - } - if err := userRow.AssignTo(&item); err != nil { - return item, fmt.Errorf("assign CompositeUserOne row: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "CompositeUserOne") + rows, err := q.conn.Query(ctx, compositeUserOneSQL) + if err != nil { + return User{}, fmt.Errorf("query CompositeUserOne: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[User]) } const compositeUserOneTwoColsSQL = `SELECT 1 AS num, ROW (15, 'qux')::"user" AS "user";` type CompositeUserOneTwoColsRow struct { - Num int32 `json:"num"` - User User `json:"user"` + Num int32 `json:"num" db:"num"` + User User `json:"user" db:"user"` } // CompositeUserOneTwoCols implements Querier.CompositeUserOneTwoCols. func (q *DBQuerier) CompositeUserOneTwoCols(ctx context.Context) (CompositeUserOneTwoColsRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "CompositeUserOneTwoCols") - row := q.conn.QueryRow(ctx, compositeUserOneTwoColsSQL) - var item CompositeUserOneTwoColsRow - userRow := q.types.newUser() - if err := row.Scan(&item.Num, userRow); err != nil { - return item, fmt.Errorf("query CompositeUserOneTwoCols: %w", err) - } - if err := userRow.AssignTo(&item.User); err != nil { - return item, fmt.Errorf("assign CompositeUserOneTwoCols row: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "CompositeUserOneTwoCols") + rows, err := q.conn.Query(ctx, compositeUserOneTwoColsSQL) + if err != nil { + return CompositeUserOneTwoColsRow{}, fmt.Errorf("query CompositeUserOneTwoCols: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CompositeUserOneTwoColsRow]) } const compositeUserManySQL = `SELECT ROW (15, 'qux')::"user" AS "user";` // CompositeUserMany implements Querier.CompositeUserMany. func (q *DBQuerier) CompositeUserMany(ctx context.Context) ([]User, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "CompositeUserMany") + ctx = context.WithValue(ctx, QueryName{}, "CompositeUserMany") rows, err := q.conn.Query(ctx, compositeUserManySQL) if err != nil { return nil, fmt.Errorf("query CompositeUserMany: %w", err) } - defer rows.Close() - items := []User{} - userRow := q.types.newUser() - for rows.Next() { - var item User - if err := rows.Scan(userRow); err != nil { - return nil, fmt.Errorf("scan CompositeUserMany row: %w", err) - } - if err := userRow.AssignTo(&item); err != nil { - return nil, fmt.Errorf("assign CompositeUserMany row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close CompositeUserMany rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowTo[User]) } const insertUserSQL = `INSERT INTO "user" (id, name) @@ -302,10 +204,10 @@ VALUES ($1, $2);` // InsertUser implements Querier.InsertUser. func (q *DBQuerier) InsertUser(ctx context.Context, userID int, name string) (pgconn.CommandTag, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "InsertUser") + ctx = context.WithValue(ctx, QueryName{}, "InsertUser") cmdTag, err := q.conn.Exec(ctx, insertUserSQL, userID, name) if err != nil { - return cmdTag, fmt.Errorf("exec query InsertUser: %w", err) + return pgconn.CommandTag{}, fmt.Errorf("exec query InsertUser: %w", err) } return cmdTag, err } @@ -314,36 +216,11 @@ const insertDeviceSQL = `INSERT INTO device (mac, owner) VALUES ($1, $2);` // InsertDevice implements Querier.InsertDevice. -func (q *DBQuerier) InsertDevice(ctx context.Context, mac pgtype.Macaddr, owner int) (pgconn.CommandTag, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "InsertDevice") +func (q *DBQuerier) InsertDevice(ctx context.Context, mac net.HardwareAddr, owner int) (pgconn.CommandTag, error) { + ctx = context.WithValue(ctx, QueryName{}, "InsertDevice") cmdTag, err := q.conn.Exec(ctx, insertDeviceSQL, mac, owner) if err != nil { - return cmdTag, fmt.Errorf("exec query InsertDevice: %w", err) + return pgconn.CommandTag{}, fmt.Errorf("exec query InsertDevice: %w", err) } return cmdTag, err } - -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/device/query.sql_test.go b/example/device/query.sql_test.go index 9345a043..b0d87518 100644 --- a/example/device/query.sql_test.go +++ b/example/device/query.sql_test.go @@ -4,7 +4,6 @@ import ( "net" "testing" - "github.com/jackc/pgx/v5/pgtype" "github.com/jschaf/pggen/internal/pgtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -19,7 +18,7 @@ func TestQuerier_FindDevicesByUser(t *testing.T) { _, err := q.InsertUser(ctx, userID, "foo") require.NoError(t, err) mac1, _ := net.ParseMAC("11:22:33:44:55:66") - _, err = q.InsertDevice(ctx, pgtype.Macaddr{Status: pgtype.Present, Addr: mac1}, userID) + _, err = q.InsertDevice(ctx, mac1, userID) require.NoError(t, err) t.Run("FindDevicesByUser", func(t *testing.T) { @@ -27,16 +26,9 @@ func TestQuerier_FindDevicesByUser(t *testing.T) { require.NoError(t, err) want := []FindDevicesByUserRow{ { - ID: userID, - Name: "foo", - MacAddrs: pgtype.MacaddrArray{ - Elements: []pgtype.Macaddr{{Addr: mac1, Status: pgtype.Present}}, - Dimensions: []pgtype.ArrayDimension{{ - Length: 1, - LowerBound: 1, - }}, - Status: pgtype.Present, - }, + ID: userID, + Name: "foo", + MacAddrs: []net.HardwareAddr{mac1}, }, } assert.Equal(t, want, val) @@ -56,9 +48,9 @@ func TestQuerier_CompositeUser(t *testing.T) { mac1, _ := net.ParseMAC("11:22:33:44:55:66") mac2, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff") - _, err = q.InsertDevice(ctx, pgtype.Macaddr{Status: pgtype.Present, Addr: mac1}, userID) + _, err = q.InsertDevice(ctx, mac1, userID) require.NoError(t, err) - _, err = q.InsertDevice(ctx, pgtype.Macaddr{Status: pgtype.Present, Addr: mac2}, userID) + _, err = q.InsertDevice(ctx, mac2, userID) require.NoError(t, err) t.Run("CompositeUser", func(t *testing.T) { @@ -66,12 +58,12 @@ func TestQuerier_CompositeUser(t *testing.T) { require.NoError(t, err) want := []CompositeUserRow{ { - Mac: pgtype.Macaddr{Addr: mac1, Status: pgtype.Present}, + Mac: mac1, Type: DeviceTypeUndefined, User: User{ID: &userID, Name: &name}, }, { - Mac: pgtype.Macaddr{Addr: mac2, Status: pgtype.Present}, + Mac: mac2, Type: DeviceTypeUndefined, User: User{ID: &userID, Name: &name}, }, diff --git a/example/domain/query.sql.go b/example/domain/query.sql.go index 3d1de221..059565e4 100644 --- a/example/domain/query.sql.go +++ b/example/domain/query.sql.go @@ -7,9 +7,10 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { DomainOne(ctx context.Context) (string, error) @@ -18,8 +19,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -31,72 +31,52 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name + return &DBQuerier{conn: conn} } -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt +var typesToRegister = []string{} + +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } const domainOneSQL = `SELECT '90210'::us_postal_code;` // DomainOne implements Querier.DomainOne. func (q *DBQuerier) DomainOne(ctx context.Context) (string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "DomainOne") - row := q.conn.QueryRow(ctx, domainOneSQL) - var item string - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query DomainOne: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "DomainOne") + rows, err := q.conn.Query(ctx, domainOneSQL) + if err != nil { + return "", fmt.Errorf("query DomainOne: %w", err) } - return item, nil -} - -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) } - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/enums/query.sql.go b/example/enums/query.sql.go index c72c9d9c..f361dada 100644 --- a/example/enums/query.sql.go +++ b/example/enums/query.sql.go @@ -7,14 +7,16 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" + "net" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { FindAllDevices(ctx context.Context) ([]FindAllDevicesRow, error) - InsertDevice(ctx context.Context, mac pgtype.Macaddr, typePg DeviceType) (pgconn.CommandTag, error) + InsertDevice(ctx context.Context, mac net.HardwareAddr, typePg DeviceType) (pgconn.CommandTag, error) // Select an array of all device_type enum values. FindOneDeviceArray(ctx context.Context) ([]DeviceType, error) @@ -32,8 +34,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -45,29 +46,13 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} + return &DBQuerier{conn: conn} } // Device represents the Postgres composite type "device". type Device struct { - Mac pgtype.Macaddr `json:"mac"` - Type DeviceType `json:"type"` -} - -// newDeviceTypeEnum creates a new pgtype.ValueTranscoder for the -// Postgres enum type 'device_type'. -func newDeviceTypeEnum() pgtype.ValueTranscoder { - return pgtype.NewEnumType( - "device_type", - []string{ - string(DeviceTypeUndefined), - string(DeviceTypePhone), - string(DeviceTypeLaptop), - string(DeviceTypeIpad), - string(DeviceTypeDesktop), - string(DeviceTypeIot), - }, - ) + Mac net.HardwareAddr `json:"mac" db:"mac"` + Type DeviceType `json:"type" db:"type"` } // DeviceType represents the Postgres enum "device_type". @@ -84,141 +69,76 @@ const ( func (d DeviceType) String() string { return string(d) } -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} +var typesToRegister = []string{} -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -// newDevice creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'device'. -func (tr *typeResolver) newDevice() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "device", - compositeField{name: "mac", typeName: "macaddr", defaultVal: &pgtype.Macaddr{}}, - compositeField{name: "type", typeName: "device_type", defaultVal: newDeviceTypeEnum()}, - ) -} +var _ = addTypeToRegister("\"device_type\"") -// newDeviceTypeArray creates a new pgtype.ValueTranscoder for the Postgres -// '_device_type' array type. -func (tr *typeResolver) newDeviceTypeArray() pgtype.ValueTranscoder { - return tr.newArrayValue("_device_type", "device_type", newDeviceTypeEnum) -} +var _ = addTypeToRegister("\"device\"") + +var _ = addTypeToRegister("\"_device_type\"") const findAllDevicesSQL = `SELECT mac, type FROM device;` type FindAllDevicesRow struct { - Mac pgtype.Macaddr `json:"mac"` - Type DeviceType `json:"type"` + Mac net.HardwareAddr `json:"mac" db:"mac"` + Type DeviceType `json:"type" db:"type"` } // FindAllDevices implements Querier.FindAllDevices. func (q *DBQuerier) FindAllDevices(ctx context.Context) ([]FindAllDevicesRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindAllDevices") + ctx = context.WithValue(ctx, QueryName{}, "FindAllDevices") rows, err := q.conn.Query(ctx, findAllDevicesSQL) if err != nil { return nil, fmt.Errorf("query FindAllDevices: %w", err) } - defer rows.Close() - items := []FindAllDevicesRow{} - for rows.Next() { - var item FindAllDevicesRow - if err := rows.Scan(&item.Mac, &item.Type); err != nil { - return nil, fmt.Errorf("scan FindAllDevices row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindAllDevices rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowToStructByName[FindAllDevicesRow]) } const insertDeviceSQL = `INSERT INTO device (mac, type) VALUES ($1, $2);` // InsertDevice implements Querier.InsertDevice. -func (q *DBQuerier) InsertDevice(ctx context.Context, mac pgtype.Macaddr, typePg DeviceType) (pgconn.CommandTag, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "InsertDevice") +func (q *DBQuerier) InsertDevice(ctx context.Context, mac net.HardwareAddr, typePg DeviceType) (pgconn.CommandTag, error) { + ctx = context.WithValue(ctx, QueryName{}, "InsertDevice") cmdTag, err := q.conn.Exec(ctx, insertDeviceSQL, mac, typePg) if err != nil { - return cmdTag, fmt.Errorf("exec query InsertDevice: %w", err) + return pgconn.CommandTag{}, fmt.Errorf("exec query InsertDevice: %w", err) } return cmdTag, err } @@ -227,17 +147,13 @@ const findOneDeviceArraySQL = `SELECT enum_range(NULL::device_type) AS device_ty // FindOneDeviceArray implements Querier.FindOneDeviceArray. func (q *DBQuerier) FindOneDeviceArray(ctx context.Context) ([]DeviceType, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindOneDeviceArray") - row := q.conn.QueryRow(ctx, findOneDeviceArraySQL) - item := []DeviceType{} - deviceTypesArray := q.types.newDeviceTypeArray() - if err := row.Scan(deviceTypesArray); err != nil { - return item, fmt.Errorf("query FindOneDeviceArray: %w", err) - } - if err := deviceTypesArray.AssignTo(&item); err != nil { - return item, fmt.Errorf("assign FindOneDeviceArray row: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "FindOneDeviceArray") + rows, err := q.conn.Query(ctx, findOneDeviceArraySQL) + if err != nil { + return nil, fmt.Errorf("query FindOneDeviceArray: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]DeviceType]) } const findManyDeviceArraySQL = `SELECT enum_range('ipad'::device_type, 'iot'::device_type) AS device_types @@ -246,28 +162,13 @@ SELECT enum_range(NULL::device_type) AS device_types;` // FindManyDeviceArray implements Querier.FindManyDeviceArray. func (q *DBQuerier) FindManyDeviceArray(ctx context.Context) ([][]DeviceType, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindManyDeviceArray") + ctx = context.WithValue(ctx, QueryName{}, "FindManyDeviceArray") rows, err := q.conn.Query(ctx, findManyDeviceArraySQL) if err != nil { return nil, fmt.Errorf("query FindManyDeviceArray: %w", err) } - defer rows.Close() - items := [][]DeviceType{} - deviceTypesArray := q.types.newDeviceTypeArray() - for rows.Next() { - var item []DeviceType - if err := rows.Scan(deviceTypesArray); err != nil { - return nil, fmt.Errorf("scan FindManyDeviceArray row: %w", err) - } - if err := deviceTypesArray.AssignTo(&item); err != nil { - return nil, fmt.Errorf("assign FindManyDeviceArray row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindManyDeviceArray rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowTo[[]DeviceType]) } const findManyDeviceArrayWithNumSQL = `SELECT 1 AS num, enum_range('ipad'::device_type, 'iot'::device_type) AS device_types @@ -275,74 +176,30 @@ UNION ALL SELECT 2 as num, enum_range(NULL::device_type) AS device_types;` type FindManyDeviceArrayWithNumRow struct { - Num *int32 `json:"num"` - DeviceTypes []DeviceType `json:"device_types"` + Num *int32 `json:"num" db:"num"` + DeviceTypes []DeviceType `json:"device_types" db:"device_types"` } // FindManyDeviceArrayWithNum implements Querier.FindManyDeviceArrayWithNum. func (q *DBQuerier) FindManyDeviceArrayWithNum(ctx context.Context) ([]FindManyDeviceArrayWithNumRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindManyDeviceArrayWithNum") + ctx = context.WithValue(ctx, QueryName{}, "FindManyDeviceArrayWithNum") rows, err := q.conn.Query(ctx, findManyDeviceArrayWithNumSQL) if err != nil { return nil, fmt.Errorf("query FindManyDeviceArrayWithNum: %w", err) } - defer rows.Close() - items := []FindManyDeviceArrayWithNumRow{} - deviceTypesArray := q.types.newDeviceTypeArray() - for rows.Next() { - var item FindManyDeviceArrayWithNumRow - if err := rows.Scan(&item.Num, deviceTypesArray); err != nil { - return nil, fmt.Errorf("scan FindManyDeviceArrayWithNum row: %w", err) - } - if err := deviceTypesArray.AssignTo(&item.DeviceTypes); err != nil { - return nil, fmt.Errorf("assign FindManyDeviceArrayWithNum row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindManyDeviceArrayWithNum rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowToStructByName[FindManyDeviceArrayWithNumRow]) } const enumInsideCompositeSQL = `SELECT ROW('08:00:2b:01:02:03'::macaddr, 'phone'::device_type) ::device;` // EnumInsideComposite implements Querier.EnumInsideComposite. func (q *DBQuerier) EnumInsideComposite(ctx context.Context) (Device, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "EnumInsideComposite") - row := q.conn.QueryRow(ctx, enumInsideCompositeSQL) - var item Device - rowRow := q.types.newDevice() - if err := row.Scan(rowRow); err != nil { - return item, fmt.Errorf("query EnumInsideComposite: %w", err) - } - if err := rowRow.AssignTo(&item); err != nil { - return item, fmt.Errorf("assign EnumInsideComposite row: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "EnumInsideComposite") + rows, err := q.conn.Query(ctx, enumInsideCompositeSQL) + if err != nil { + return Device{}, fmt.Errorf("query EnumInsideComposite: %w", err) } - return item, nil -} -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[Device]) } - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/enums/query.sql_test.go b/example/enums/query.sql_test.go index c236a3d8..70d9d906 100644 --- a/example/enums/query.sql_test.go +++ b/example/enums/query.sql_test.go @@ -6,7 +6,6 @@ import ( "testing" "time" - "github.com/jackc/pgx/v5/pgtype" "github.com/jschaf/pggen/internal/pgtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -28,7 +27,7 @@ func TestNewQuerier_FindAllDevices(t *testing.T) { require.NoError(t, err) assert.Equal(t, []FindAllDevicesRow{ - {Mac: pgtype.Macaddr{Addr: mac, Status: pgtype.Present}, Type: DeviceTypeIot}, + {Mac: mac, Type: DeviceTypeIot}, }, devices, ) @@ -107,7 +106,7 @@ func TestNewQuerier_EnumInsideComposite(t *testing.T) { device, err := q.EnumInsideComposite(ctx) require.NoError(t, err) assert.Equal(t, - Device{Mac: pgtype.Macaddr{Addr: mac, Status: pgtype.Present}, Type: DeviceTypePhone}, + Device{Mac: mac, Type: DeviceTypePhone}, device, ) }) @@ -116,7 +115,7 @@ func TestNewQuerier_EnumInsideComposite(t *testing.T) { func insertDevice(t *testing.T, q *DBQuerier, mac net.HardwareAddr, device DeviceType) { t.Helper() _, err := q.InsertDevice(t.Context(), - pgtype.Macaddr{Addr: mac, Status: pgtype.Present}, + mac, device, ) require.NoError(t, err) diff --git a/example/erp/order/customer.sql.go b/example/erp/order/customer.sql.go index 45aca9c8..e91a598d 100644 --- a/example/erp/order/customer.sql.go +++ b/example/erp/order/customer.sql.go @@ -10,6 +10,8 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { CreateTenant(ctx context.Context, key string, name string) (CreateTenantRow, error) @@ -30,8 +32,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -43,36 +44,41 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + return &DBQuerier{conn: conn} +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt +var typesToRegister = []string{} + +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } const createTenantSQL = `INSERT INTO tenant (tenant_id, name) @@ -80,20 +86,20 @@ VALUES (base36_decode($1::text)::tenant_id, $2::text) RETURNING *;` type CreateTenantRow struct { - TenantID int `json:"tenant_id"` - Rname *string `json:"rname"` - Name string `json:"name"` + TenantID int `json:"tenant_id" db:"tenant_id"` + Rname *string `json:"rname" db:"rname"` + Name string `json:"name" db:"name"` } // CreateTenant implements Querier.CreateTenant. func (q *DBQuerier) CreateTenant(ctx context.Context, key string, name string) (CreateTenantRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "CreateTenant") - row := q.conn.QueryRow(ctx, createTenantSQL, key, name) - var item CreateTenantRow - if err := row.Scan(&item.TenantID, &item.Rname, &item.Name); err != nil { - return item, fmt.Errorf("query CreateTenant: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "CreateTenant") + rows, err := q.conn.Query(ctx, createTenantSQL, key, name) + if err != nil { + return CreateTenantRow{}, fmt.Errorf("query CreateTenant: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CreateTenantRow]) } const findOrdersByCustomerSQL = `SELECT * @@ -101,32 +107,21 @@ FROM orders WHERE customer_id = $1;` type FindOrdersByCustomerRow struct { - OrderID int32 `json:"order_id"` - OrderDate pgtype.Timestamptz `json:"order_date"` - OrderTotal pgtype.Numeric `json:"order_total"` - CustomerID *int32 `json:"customer_id"` + OrderID int32 `json:"order_id" db:"order_id"` + OrderDate pgtype.Timestamptz `json:"order_date" db:"order_date"` + OrderTotal pgtype.Numeric `json:"order_total" db:"order_total"` + CustomerID *int32 `json:"customer_id" db:"customer_id"` } // FindOrdersByCustomer implements Querier.FindOrdersByCustomer. func (q *DBQuerier) FindOrdersByCustomer(ctx context.Context, customerID int32) ([]FindOrdersByCustomerRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindOrdersByCustomer") + ctx = context.WithValue(ctx, QueryName{}, "FindOrdersByCustomer") rows, err := q.conn.Query(ctx, findOrdersByCustomerSQL, customerID) if err != nil { return nil, fmt.Errorf("query FindOrdersByCustomer: %w", err) } - defer rows.Close() - items := []FindOrdersByCustomerRow{} - for rows.Next() { - var item FindOrdersByCustomerRow - if err := rows.Scan(&item.OrderID, &item.OrderDate, &item.OrderTotal, &item.CustomerID); err != nil { - return nil, fmt.Errorf("scan FindOrdersByCustomer row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindOrdersByCustomer rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowToStructByName[FindOrdersByCustomerRow]) } const findProductsInOrderSQL = `SELECT o.order_id, p.product_id, p.name @@ -136,31 +131,20 @@ FROM orders o WHERE o.order_id = $1;` type FindProductsInOrderRow struct { - OrderID *int32 `json:"order_id"` - ProductID *int32 `json:"product_id"` - Name *string `json:"name"` + OrderID *int32 `json:"order_id" db:"order_id"` + ProductID *int32 `json:"product_id" db:"product_id"` + Name *string `json:"name" db:"name"` } // FindProductsInOrder implements Querier.FindProductsInOrder. func (q *DBQuerier) FindProductsInOrder(ctx context.Context, orderID int32) ([]FindProductsInOrderRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindProductsInOrder") + ctx = context.WithValue(ctx, QueryName{}, "FindProductsInOrder") rows, err := q.conn.Query(ctx, findProductsInOrderSQL, orderID) if err != nil { return nil, fmt.Errorf("query FindProductsInOrder: %w", err) } - defer rows.Close() - items := []FindProductsInOrderRow{} - for rows.Next() { - var item FindProductsInOrderRow - if err := rows.Scan(&item.OrderID, &item.ProductID, &item.Name); err != nil { - return nil, fmt.Errorf("scan FindProductsInOrder row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindProductsInOrder rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowToStructByName[FindProductsInOrderRow]) } const insertCustomerSQL = `INSERT INTO customer (first_name, last_name, email) @@ -174,21 +158,21 @@ type InsertCustomerParams struct { } type InsertCustomerRow struct { - CustomerID int32 `json:"customer_id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Email string `json:"email"` + CustomerID int32 `json:"customer_id" db:"customer_id"` + FirstName string `json:"first_name" db:"first_name"` + LastName string `json:"last_name" db:"last_name"` + Email string `json:"email" db:"email"` } // InsertCustomer implements Querier.InsertCustomer. func (q *DBQuerier) InsertCustomer(ctx context.Context, params InsertCustomerParams) (InsertCustomerRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "InsertCustomer") - row := q.conn.QueryRow(ctx, insertCustomerSQL, params.FirstName, params.LastName, params.Email) - var item InsertCustomerRow - if err := row.Scan(&item.CustomerID, &item.FirstName, &item.LastName, &item.Email); err != nil { - return item, fmt.Errorf("query InsertCustomer: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "InsertCustomer") + rows, err := q.conn.Query(ctx, insertCustomerSQL, params.FirstName, params.LastName, params.Email) + if err != nil { + return InsertCustomerRow{}, fmt.Errorf("query InsertCustomer: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[InsertCustomerRow]) } const insertOrderSQL = `INSERT INTO orders (order_date, order_total, customer_id) @@ -202,44 +186,19 @@ type InsertOrderParams struct { } type InsertOrderRow struct { - OrderID int32 `json:"order_id"` - OrderDate pgtype.Timestamptz `json:"order_date"` - OrderTotal pgtype.Numeric `json:"order_total"` - CustomerID *int32 `json:"customer_id"` + OrderID int32 `json:"order_id" db:"order_id"` + OrderDate pgtype.Timestamptz `json:"order_date" db:"order_date"` + OrderTotal pgtype.Numeric `json:"order_total" db:"order_total"` + CustomerID *int32 `json:"customer_id" db:"customer_id"` } // InsertOrder implements Querier.InsertOrder. func (q *DBQuerier) InsertOrder(ctx context.Context, params InsertOrderParams) (InsertOrderRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "InsertOrder") - row := q.conn.QueryRow(ctx, insertOrderSQL, params.OrderDate, params.OrderTotal, params.CustID) - var item InsertOrderRow - if err := row.Scan(&item.OrderID, &item.OrderDate, &item.OrderTotal, &item.CustomerID); err != nil { - return item, fmt.Errorf("query InsertOrder: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "InsertOrder") + rows, err := q.conn.Query(ctx, insertOrderSQL, params.OrderDate, params.OrderTotal, params.CustID) + if err != nil { + return InsertOrderRow{}, fmt.Errorf("query InsertOrder: %w", err) } - return item, nil -} - -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName + return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[InsertOrderRow]) } - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/erp/order/customer.sql_test.go b/example/erp/order/customer.sql_test.go index f08a79be..6f757c79 100644 --- a/example/erp/order/customer.sql_test.go +++ b/example/erp/order/customer.sql_test.go @@ -27,8 +27,8 @@ func TestNewQuerier_FindOrdersByCustomer(t *testing.T) { return } order1, err := q.InsertOrder(ctx, InsertOrderParams{ - OrderDate: pgtype.Timestamptz{Time: time.Now(), Status: pgtype.Present}, - OrderTotal: pgtype.Numeric{Int: big.NewInt(77), Status: pgtype.Present}, + OrderDate: pgtype.Timestamptz{Time: time.Now(), Valid: true}, + OrderTotal: pgtype.Numeric{Int: big.NewInt(77), Valid: true}, CustID: cust1.CustomerID, }) if err != nil { diff --git a/example/erp/order/price.sql.go b/example/erp/order/price.sql.go index c4c0b9ab..f610d29b 100644 --- a/example/erp/order/price.sql.go +++ b/example/erp/order/price.sql.go @@ -5,38 +5,28 @@ package order import ( "context" "fmt" + "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" ) const findOrdersByPriceSQL = `SELECT * FROM orders WHERE order_total > $1;` type FindOrdersByPriceRow struct { - OrderID int32 `json:"order_id"` - OrderDate pgtype.Timestamptz `json:"order_date"` - OrderTotal pgtype.Numeric `json:"order_total"` - CustomerID *int32 `json:"customer_id"` + OrderID int32 `json:"order_id" db:"order_id"` + OrderDate pgtype.Timestamptz `json:"order_date" db:"order_date"` + OrderTotal pgtype.Numeric `json:"order_total" db:"order_total"` + CustomerID *int32 `json:"customer_id" db:"customer_id"` } // FindOrdersByPrice implements Querier.FindOrdersByPrice. func (q *DBQuerier) FindOrdersByPrice(ctx context.Context, minTotal pgtype.Numeric) ([]FindOrdersByPriceRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindOrdersByPrice") + ctx = context.WithValue(ctx, QueryName{}, "FindOrdersByPrice") rows, err := q.conn.Query(ctx, findOrdersByPriceSQL, minTotal) if err != nil { return nil, fmt.Errorf("query FindOrdersByPrice: %w", err) } - defer rows.Close() - items := []FindOrdersByPriceRow{} - for rows.Next() { - var item FindOrdersByPriceRow - if err := rows.Scan(&item.OrderID, &item.OrderDate, &item.OrderTotal, &item.CustomerID); err != nil { - return nil, fmt.Errorf("scan FindOrdersByPrice row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindOrdersByPrice rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowToStructByName[FindOrdersByPriceRow]) } const findOrdersMRRSQL = `SELECT date_trunc('month', order_date) AS month, sum(order_total) AS order_mrr @@ -44,28 +34,17 @@ FROM orders GROUP BY date_trunc('month', order_date);` type FindOrdersMRRRow struct { - Month pgtype.Timestamptz `json:"month"` - OrderMRR pgtype.Numeric `json:"order_mrr"` + Month pgtype.Timestamptz `json:"month" db:"month"` + OrderMRR pgtype.Numeric `json:"order_mrr" db:"order_mrr"` } // FindOrdersMRR implements Querier.FindOrdersMRR. func (q *DBQuerier) FindOrdersMRR(ctx context.Context) ([]FindOrdersMRRRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindOrdersMRR") + ctx = context.WithValue(ctx, QueryName{}, "FindOrdersMRR") rows, err := q.conn.Query(ctx, findOrdersMRRSQL) if err != nil { return nil, fmt.Errorf("query FindOrdersMRR: %w", err) } - defer rows.Close() - items := []FindOrdersMRRRow{} - for rows.Next() { - var item FindOrdersMRRRow - if err := rows.Scan(&item.Month, &item.OrderMRR); err != nil { - return nil, fmt.Errorf("scan FindOrdersMRR row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindOrdersMRR rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowToStructByName[FindOrdersMRRRow]) } diff --git a/example/function/query.sql.go b/example/function/query.sql.go index c7ba5bd0..d571f1cb 100644 --- a/example/function/query.sql.go +++ b/example/function/query.sql.go @@ -7,9 +7,10 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { OutParams(ctx context.Context) ([]OutParamsRow, error) @@ -18,8 +19,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -31,185 +31,77 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} + return &DBQuerier{conn: conn} } // ListItem represents the Postgres composite type "list_item". type ListItem struct { - Name *string `json:"name"` - Color *string `json:"color"` + Name *string `json:"name" db:"name"` + Color *string `json:"color" db:"color"` } // ListStats represents the Postgres composite type "list_stats". type ListStats struct { - Val1 *string `json:"val1"` - Val2 []*int32 `json:"val2"` -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + Val1 *string `json:"val1" db:"val1"` + Val2 []*int32 `json:"val2" db:"val2"` +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} +var typesToRegister = []string{} -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -// newListItem creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'list_item'. -func (tr *typeResolver) newListItem() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "list_item", - compositeField{name: "name", typeName: "text", defaultVal: &pgtype.Text{}}, - compositeField{name: "color", typeName: "text", defaultVal: &pgtype.Text{}}, - ) -} +var _ = addTypeToRegister("\"list_item\"") -// newListStats creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'list_stats'. -func (tr *typeResolver) newListStats() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "list_stats", - compositeField{name: "val1", typeName: "text", defaultVal: &pgtype.Text{}}, - compositeField{name: "val2", typeName: "_int4", defaultVal: &pgtype.Int4Array{}}, - ) -} +var _ = addTypeToRegister("\"list_stats\"") -// newListItemArray creates a new pgtype.ValueTranscoder for the Postgres -// '_list_item' array type. -func (tr *typeResolver) newListItemArray() pgtype.ValueTranscoder { - return tr.newArrayValue("_list_item", "list_item", tr.newListItem) -} +var _ = addTypeToRegister("\"_list_item\"") const outParamsSQL = `SELECT * FROM out_params();` type OutParamsRow struct { - Items []ListItem `json:"_items"` - Stats ListStats `json:"_stats"` + Items []ListItem `json:"_items" db:"_items"` + Stats ListStats `json:"_stats" db:"_stats"` } // OutParams implements Querier.OutParams. func (q *DBQuerier) OutParams(ctx context.Context) ([]OutParamsRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "OutParams") + ctx = context.WithValue(ctx, QueryName{}, "OutParams") rows, err := q.conn.Query(ctx, outParamsSQL) if err != nil { return nil, fmt.Errorf("query OutParams: %w", err) } - defer rows.Close() - items := []OutParamsRow{} - itemsArray := q.types.newListItemArray() - statsRow := q.types.newListStats() - for rows.Next() { - var item OutParamsRow - if err := rows.Scan(itemsArray, statsRow); err != nil { - return nil, fmt.Errorf("scan OutParams row: %w", err) - } - if err := itemsArray.AssignTo(&item.Items); err != nil { - return nil, fmt.Errorf("assign OutParams row: %w", err) - } - if err := statsRow.AssignTo(&item.Stats); err != nil { - return nil, fmt.Errorf("assign OutParams row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close OutParams rows: %w", err) - } - return items, err -} -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string + return pgx.CollectRows(rows, pgx.RowToStructByName[OutParamsRow]) } - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/go_pointer_types/query.sql.go b/example/go_pointer_types/query.sql.go index 08276e52..49378119 100644 --- a/example/go_pointer_types/query.sql.go +++ b/example/go_pointer_types/query.sql.go @@ -7,9 +7,10 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { GenSeries1(ctx context.Context) (*int, error) @@ -28,8 +29,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -41,36 +41,41 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + return &DBQuerier{conn: conn} +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt +var typesToRegister = []string{} + +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } const genSeries1SQL = `SELECT n @@ -79,13 +84,13 @@ LIMIT 1;` // GenSeries1 implements Querier.GenSeries1. func (q *DBQuerier) GenSeries1(ctx context.Context) (*int, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "GenSeries1") - row := q.conn.QueryRow(ctx, genSeries1SQL) - var item *int - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query GenSeries1: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "GenSeries1") + rows, err := q.conn.Query(ctx, genSeries1SQL) + if err != nil { + return nil, fmt.Errorf("query GenSeries1: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*int]) } const genSeriesSQL = `SELECT n @@ -93,24 +98,13 @@ FROM generate_series(0, 2) n;` // GenSeries implements Querier.GenSeries. func (q *DBQuerier) GenSeries(ctx context.Context) ([]*int, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "GenSeries") + ctx = context.WithValue(ctx, QueryName{}, "GenSeries") rows, err := q.conn.Query(ctx, genSeriesSQL) if err != nil { return nil, fmt.Errorf("query GenSeries: %w", err) } - defer rows.Close() - items := []*int{} - for rows.Next() { - var item int - if err := rows.Scan(&item); err != nil { - return nil, fmt.Errorf("scan GenSeries row: %w", err) - } - items = append(items, &item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close GenSeries rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowTo[*int]) } const genSeriesArr1SQL = `SELECT array_agg(n) @@ -118,13 +112,13 @@ FROM generate_series(0, 2) n;` // GenSeriesArr1 implements Querier.GenSeriesArr1. func (q *DBQuerier) GenSeriesArr1(ctx context.Context) ([]int, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "GenSeriesArr1") - row := q.conn.QueryRow(ctx, genSeriesArr1SQL) - item := []int{} - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query GenSeriesArr1: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "GenSeriesArr1") + rows, err := q.conn.Query(ctx, genSeriesArr1SQL) + if err != nil { + return nil, fmt.Errorf("query GenSeriesArr1: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]int]) } const genSeriesArrSQL = `SELECT array_agg(n) @@ -132,24 +126,13 @@ FROM generate_series(0, 2) n;` // GenSeriesArr implements Querier.GenSeriesArr. func (q *DBQuerier) GenSeriesArr(ctx context.Context) ([][]int, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "GenSeriesArr") + ctx = context.WithValue(ctx, QueryName{}, "GenSeriesArr") rows, err := q.conn.Query(ctx, genSeriesArrSQL) if err != nil { return nil, fmt.Errorf("query GenSeriesArr: %w", err) } - defer rows.Close() - items := [][]int{} - for rows.Next() { - var item []int - if err := rows.Scan(&item); err != nil { - return nil, fmt.Errorf("scan GenSeriesArr row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close GenSeriesArr rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowTo[[]int]) } const genSeriesStr1SQL = `SELECT n::text @@ -158,13 +141,13 @@ LIMIT 1;` // GenSeriesStr1 implements Querier.GenSeriesStr1. func (q *DBQuerier) GenSeriesStr1(ctx context.Context) (*string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "GenSeriesStr1") - row := q.conn.QueryRow(ctx, genSeriesStr1SQL) - var item *string - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query GenSeriesStr1: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "GenSeriesStr1") + rows, err := q.conn.Query(ctx, genSeriesStr1SQL) + if err != nil { + return nil, fmt.Errorf("query GenSeriesStr1: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*string]) } const genSeriesStrSQL = `SELECT n::text @@ -172,47 +155,11 @@ FROM generate_series(0, 2) n;` // GenSeriesStr implements Querier.GenSeriesStr. func (q *DBQuerier) GenSeriesStr(ctx context.Context) ([]*string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "GenSeriesStr") + ctx = context.WithValue(ctx, QueryName{}, "GenSeriesStr") rows, err := q.conn.Query(ctx, genSeriesStrSQL) if err != nil { return nil, fmt.Errorf("query GenSeriesStr: %w", err) } - defer rows.Close() - items := []*string{} - for rows.Next() { - var item string - if err := rows.Scan(&item); err != nil { - return nil, fmt.Errorf("scan GenSeriesStr row: %w", err) - } - items = append(items, &item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close GenSeriesStr rows: %w", err) - } - return items, err -} - -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName + return pgx.CollectRows(rows, pgx.RowTo[*string]) } - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/inline_param_count/inline0/query.sql.go b/example/inline_param_count/inline0/query.sql.go index c63d4b90..0a7aa26f 100644 --- a/example/inline_param_count/inline0/query.sql.go +++ b/example/inline_param_count/inline0/query.sql.go @@ -7,9 +7,10 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { // CountAuthors returns the number of authors (zero params). @@ -28,8 +29,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -41,49 +41,54 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + return &DBQuerier{conn: conn} +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt +var typesToRegister = []string{} + +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } const countAuthorsSQL = `SELECT count(*) FROM author;` // CountAuthors implements Querier.CountAuthors. func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "CountAuthors") - row := q.conn.QueryRow(ctx, countAuthorsSQL) - var item *int - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query CountAuthors: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "CountAuthors") + rows, err := q.conn.Query(ctx, countAuthorsSQL) + if err != nil { + return nil, fmt.Errorf("query CountAuthors: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*int]) } const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` @@ -93,21 +98,21 @@ type FindAuthorByIDParams struct { } type FindAuthorByIDRow struct { - AuthorID int32 `json:"author_id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Suffix *string `json:"suffix"` + AuthorID int32 `json:"author_id" db:"author_id"` + FirstName string `json:"first_name" db:"first_name"` + LastName string `json:"last_name" db:"last_name"` + Suffix *string `json:"suffix" db:"suffix"` } // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, params FindAuthorByIDParams) (FindAuthorByIDRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthorByID") - row := q.conn.QueryRow(ctx, findAuthorByIDSQL, params.AuthorID) - var item FindAuthorByIDRow - if err := row.Scan(&item.AuthorID, &item.FirstName, &item.LastName, &item.Suffix); err != nil { - return item, fmt.Errorf("query FindAuthorByID: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") + rows, err := q.conn.Query(ctx, findAuthorByIDSQL, params.AuthorID) + if err != nil { + return FindAuthorByIDRow{}, fmt.Errorf("query FindAuthorByID: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) } const insertAuthorSQL = `INSERT INTO author (first_name, last_name) @@ -121,13 +126,13 @@ type InsertAuthorParams struct { // InsertAuthor implements Querier.InsertAuthor. func (q *DBQuerier) InsertAuthor(ctx context.Context, params InsertAuthorParams) (int32, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "InsertAuthor") - row := q.conn.QueryRow(ctx, insertAuthorSQL, params.FirstName, params.LastName) - var item int32 - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query InsertAuthor: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "InsertAuthor") + rows, err := q.conn.Query(ctx, insertAuthorSQL, params.FirstName, params.LastName) + if err != nil { + return 0, fmt.Errorf("query InsertAuthor: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int32]) } const deleteAuthorsByFullNameSQL = `DELETE @@ -144,35 +149,10 @@ type DeleteAuthorsByFullNameParams struct { // DeleteAuthorsByFullName implements Querier.DeleteAuthorsByFullName. func (q *DBQuerier) DeleteAuthorsByFullName(ctx context.Context, params DeleteAuthorsByFullNameParams) (pgconn.CommandTag, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "DeleteAuthorsByFullName") + ctx = context.WithValue(ctx, QueryName{}, "DeleteAuthorsByFullName") cmdTag, err := q.conn.Exec(ctx, deleteAuthorsByFullNameSQL, params.FirstName, params.LastName, params.Suffix) if err != nil { - return cmdTag, fmt.Errorf("exec query DeleteAuthorsByFullName: %w", err) + return pgconn.CommandTag{}, fmt.Errorf("exec query DeleteAuthorsByFullName: %w", err) } return cmdTag, err } - -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/inline_param_count/inline1/query.sql.go b/example/inline_param_count/inline1/query.sql.go index 2b55216d..4a09e23f 100644 --- a/example/inline_param_count/inline1/query.sql.go +++ b/example/inline_param_count/inline1/query.sql.go @@ -7,9 +7,10 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { // CountAuthors returns the number of authors (zero params). @@ -28,8 +29,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -41,69 +41,74 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + return &DBQuerier{conn: conn} +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt +var typesToRegister = []string{} + +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } const countAuthorsSQL = `SELECT count(*) FROM author;` // CountAuthors implements Querier.CountAuthors. func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "CountAuthors") - row := q.conn.QueryRow(ctx, countAuthorsSQL) - var item *int - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query CountAuthors: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "CountAuthors") + rows, err := q.conn.Query(ctx, countAuthorsSQL) + if err != nil { + return nil, fmt.Errorf("query CountAuthors: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*int]) } const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` type FindAuthorByIDRow struct { - AuthorID int32 `json:"author_id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Suffix *string `json:"suffix"` + AuthorID int32 `json:"author_id" db:"author_id"` + FirstName string `json:"first_name" db:"first_name"` + LastName string `json:"last_name" db:"last_name"` + Suffix *string `json:"suffix" db:"suffix"` } // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthorByID") - row := q.conn.QueryRow(ctx, findAuthorByIDSQL, authorID) - var item FindAuthorByIDRow - if err := row.Scan(&item.AuthorID, &item.FirstName, &item.LastName, &item.Suffix); err != nil { - return item, fmt.Errorf("query FindAuthorByID: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") + rows, err := q.conn.Query(ctx, findAuthorByIDSQL, authorID) + if err != nil { + return FindAuthorByIDRow{}, fmt.Errorf("query FindAuthorByID: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) } const insertAuthorSQL = `INSERT INTO author (first_name, last_name) @@ -117,13 +122,13 @@ type InsertAuthorParams struct { // InsertAuthor implements Querier.InsertAuthor. func (q *DBQuerier) InsertAuthor(ctx context.Context, params InsertAuthorParams) (int32, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "InsertAuthor") - row := q.conn.QueryRow(ctx, insertAuthorSQL, params.FirstName, params.LastName) - var item int32 - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query InsertAuthor: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "InsertAuthor") + rows, err := q.conn.Query(ctx, insertAuthorSQL, params.FirstName, params.LastName) + if err != nil { + return 0, fmt.Errorf("query InsertAuthor: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int32]) } const deleteAuthorsByFullNameSQL = `DELETE @@ -140,35 +145,10 @@ type DeleteAuthorsByFullNameParams struct { // DeleteAuthorsByFullName implements Querier.DeleteAuthorsByFullName. func (q *DBQuerier) DeleteAuthorsByFullName(ctx context.Context, params DeleteAuthorsByFullNameParams) (pgconn.CommandTag, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "DeleteAuthorsByFullName") + ctx = context.WithValue(ctx, QueryName{}, "DeleteAuthorsByFullName") cmdTag, err := q.conn.Exec(ctx, deleteAuthorsByFullNameSQL, params.FirstName, params.LastName, params.Suffix) if err != nil { - return cmdTag, fmt.Errorf("exec query DeleteAuthorsByFullName: %w", err) + return pgconn.CommandTag{}, fmt.Errorf("exec query DeleteAuthorsByFullName: %w", err) } return cmdTag, err } - -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/inline_param_count/inline2/query.sql.go b/example/inline_param_count/inline2/query.sql.go index 113ecb35..3408f117 100644 --- a/example/inline_param_count/inline2/query.sql.go +++ b/example/inline_param_count/inline2/query.sql.go @@ -7,9 +7,10 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { // CountAuthors returns the number of authors (zero params). @@ -28,8 +29,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -41,69 +41,74 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + return &DBQuerier{conn: conn} +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt +var typesToRegister = []string{} + +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } const countAuthorsSQL = `SELECT count(*) FROM author;` // CountAuthors implements Querier.CountAuthors. func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "CountAuthors") - row := q.conn.QueryRow(ctx, countAuthorsSQL) - var item *int - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query CountAuthors: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "CountAuthors") + rows, err := q.conn.Query(ctx, countAuthorsSQL) + if err != nil { + return nil, fmt.Errorf("query CountAuthors: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*int]) } const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` type FindAuthorByIDRow struct { - AuthorID int32 `json:"author_id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Suffix *string `json:"suffix"` + AuthorID int32 `json:"author_id" db:"author_id"` + FirstName string `json:"first_name" db:"first_name"` + LastName string `json:"last_name" db:"last_name"` + Suffix *string `json:"suffix" db:"suffix"` } // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthorByID") - row := q.conn.QueryRow(ctx, findAuthorByIDSQL, authorID) - var item FindAuthorByIDRow - if err := row.Scan(&item.AuthorID, &item.FirstName, &item.LastName, &item.Suffix); err != nil { - return item, fmt.Errorf("query FindAuthorByID: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") + rows, err := q.conn.Query(ctx, findAuthorByIDSQL, authorID) + if err != nil { + return FindAuthorByIDRow{}, fmt.Errorf("query FindAuthorByID: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) } const insertAuthorSQL = `INSERT INTO author (first_name, last_name) @@ -112,13 +117,13 @@ RETURNING author_id;` // InsertAuthor implements Querier.InsertAuthor. func (q *DBQuerier) InsertAuthor(ctx context.Context, firstName string, lastName string) (int32, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "InsertAuthor") - row := q.conn.QueryRow(ctx, insertAuthorSQL, firstName, lastName) - var item int32 - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query InsertAuthor: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "InsertAuthor") + rows, err := q.conn.Query(ctx, insertAuthorSQL, firstName, lastName) + if err != nil { + return 0, fmt.Errorf("query InsertAuthor: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int32]) } const deleteAuthorsByFullNameSQL = `DELETE @@ -135,35 +140,10 @@ type DeleteAuthorsByFullNameParams struct { // DeleteAuthorsByFullName implements Querier.DeleteAuthorsByFullName. func (q *DBQuerier) DeleteAuthorsByFullName(ctx context.Context, params DeleteAuthorsByFullNameParams) (pgconn.CommandTag, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "DeleteAuthorsByFullName") + ctx = context.WithValue(ctx, QueryName{}, "DeleteAuthorsByFullName") cmdTag, err := q.conn.Exec(ctx, deleteAuthorsByFullNameSQL, params.FirstName, params.LastName, params.Suffix) if err != nil { - return cmdTag, fmt.Errorf("exec query DeleteAuthorsByFullName: %w", err) + return pgconn.CommandTag{}, fmt.Errorf("exec query DeleteAuthorsByFullName: %w", err) } return cmdTag, err } - -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/inline_param_count/inline3/query.sql.go b/example/inline_param_count/inline3/query.sql.go index 88f0c874..c19d9f29 100644 --- a/example/inline_param_count/inline3/query.sql.go +++ b/example/inline_param_count/inline3/query.sql.go @@ -7,9 +7,10 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { // CountAuthors returns the number of authors (zero params). @@ -28,8 +29,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -41,69 +41,74 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + return &DBQuerier{conn: conn} +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt +var typesToRegister = []string{} + +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } const countAuthorsSQL = `SELECT count(*) FROM author;` // CountAuthors implements Querier.CountAuthors. func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "CountAuthors") - row := q.conn.QueryRow(ctx, countAuthorsSQL) - var item *int - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query CountAuthors: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "CountAuthors") + rows, err := q.conn.Query(ctx, countAuthorsSQL) + if err != nil { + return nil, fmt.Errorf("query CountAuthors: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*int]) } const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` type FindAuthorByIDRow struct { - AuthorID int32 `json:"author_id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Suffix *string `json:"suffix"` + AuthorID int32 `json:"author_id" db:"author_id"` + FirstName string `json:"first_name" db:"first_name"` + LastName string `json:"last_name" db:"last_name"` + Suffix *string `json:"suffix" db:"suffix"` } // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindAuthorByID") - row := q.conn.QueryRow(ctx, findAuthorByIDSQL, authorID) - var item FindAuthorByIDRow - if err := row.Scan(&item.AuthorID, &item.FirstName, &item.LastName, &item.Suffix); err != nil { - return item, fmt.Errorf("query FindAuthorByID: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") + rows, err := q.conn.Query(ctx, findAuthorByIDSQL, authorID) + if err != nil { + return FindAuthorByIDRow{}, fmt.Errorf("query FindAuthorByID: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) } const insertAuthorSQL = `INSERT INTO author (first_name, last_name) @@ -112,13 +117,13 @@ RETURNING author_id;` // InsertAuthor implements Querier.InsertAuthor. func (q *DBQuerier) InsertAuthor(ctx context.Context, firstName string, lastName string) (int32, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "InsertAuthor") - row := q.conn.QueryRow(ctx, insertAuthorSQL, firstName, lastName) - var item int32 - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query InsertAuthor: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "InsertAuthor") + rows, err := q.conn.Query(ctx, insertAuthorSQL, firstName, lastName) + if err != nil { + return 0, fmt.Errorf("query InsertAuthor: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int32]) } const deleteAuthorsByFullNameSQL = `DELETE @@ -129,35 +134,10 @@ WHERE first_name = $1 // DeleteAuthorsByFullName implements Querier.DeleteAuthorsByFullName. func (q *DBQuerier) DeleteAuthorsByFullName(ctx context.Context, firstName string, lastName string, suffix string) (pgconn.CommandTag, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "DeleteAuthorsByFullName") + ctx = context.WithValue(ctx, QueryName{}, "DeleteAuthorsByFullName") cmdTag, err := q.conn.Exec(ctx, deleteAuthorsByFullNameSQL, firstName, lastName, suffix) if err != nil { - return cmdTag, fmt.Errorf("exec query DeleteAuthorsByFullName: %w", err) + return pgconn.CommandTag{}, fmt.Errorf("exec query DeleteAuthorsByFullName: %w", err) } return cmdTag, err } - -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/ltree/codegen_test.go b/example/ltree/codegen_test.go index 1a49a6a0..689005a8 100644 --- a/example/ltree/codegen_test.go +++ b/example/ltree/codegen_test.go @@ -25,7 +25,7 @@ func TestGenerate_Go_Example_ltree(t *testing.T) { InlineParamCount: 2, TypeOverrides: map[string]string{ "ltree": "github.com/jackc/pgx/v5/pgtype.Text", - "_ltree": "github.com/jackc/pgx/v5/pgtype.TextArray", + "_ltree": "[]string", }, }) if err != nil { diff --git a/example/ltree/query.sql.go b/example/ltree/query.sql.go index 537e2343..ced1be19 100644 --- a/example/ltree/query.sql.go +++ b/example/ltree/query.sql.go @@ -10,11 +10,13 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { FindTopScienceChildren(ctx context.Context) ([]pgtype.Text, error) - FindTopScienceChildrenAgg(ctx context.Context) (pgtype.TextArray, error) + FindTopScienceChildrenAgg(ctx context.Context) ([]string, error) InsertSampleData(ctx context.Context) (pgconn.CommandTag, error) @@ -24,8 +26,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -37,36 +38,41 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + return &DBQuerier{conn: conn} +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt +var typesToRegister = []string{} + +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } const findTopScienceChildrenSQL = `SELECT path @@ -75,24 +81,13 @@ WHERE path <@ 'Top.Science';` // FindTopScienceChildren implements Querier.FindTopScienceChildren. func (q *DBQuerier) FindTopScienceChildren(ctx context.Context) ([]pgtype.Text, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindTopScienceChildren") + ctx = context.WithValue(ctx, QueryName{}, "FindTopScienceChildren") rows, err := q.conn.Query(ctx, findTopScienceChildrenSQL) if err != nil { return nil, fmt.Errorf("query FindTopScienceChildren: %w", err) } - defer rows.Close() - items := []pgtype.Text{} - for rows.Next() { - var item pgtype.Text - if err := rows.Scan(&item); err != nil { - return nil, fmt.Errorf("scan FindTopScienceChildren row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindTopScienceChildren rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowTo[pgtype.Text]) } const findTopScienceChildrenAggSQL = `SELECT array_agg(path) @@ -100,14 +95,14 @@ FROM test WHERE path <@ 'Top.Science';` // FindTopScienceChildrenAgg implements Querier.FindTopScienceChildrenAgg. -func (q *DBQuerier) FindTopScienceChildrenAgg(ctx context.Context) (pgtype.TextArray, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindTopScienceChildrenAgg") - row := q.conn.QueryRow(ctx, findTopScienceChildrenAggSQL) - var item pgtype.TextArray - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query FindTopScienceChildrenAgg: %w", err) +func (q *DBQuerier) FindTopScienceChildrenAgg(ctx context.Context) ([]string, error) { + ctx = context.WithValue(ctx, QueryName{}, "FindTopScienceChildrenAgg") + rows, err := q.conn.Query(ctx, findTopScienceChildrenAggSQL) + if err != nil { + return nil, fmt.Errorf("query FindTopScienceChildrenAgg: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]string]) } const insertSampleDataSQL = `INSERT INTO test @@ -127,10 +122,10 @@ VALUES ('Top'), // InsertSampleData implements Querier.InsertSampleData. func (q *DBQuerier) InsertSampleData(ctx context.Context) (pgconn.CommandTag, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "InsertSampleData") + ctx = context.WithValue(ctx, QueryName{}, "InsertSampleData") cmdTag, err := q.conn.Exec(ctx, insertSampleDataSQL) if err != nil { - return cmdTag, fmt.Errorf("exec query InsertSampleData: %w", err) + return pgconn.CommandTag{}, fmt.Errorf("exec query InsertSampleData: %w", err) } return cmdTag, err } @@ -147,42 +142,17 @@ const findLtreeInputSQL = `SELECT ($2::text[])::ltree[] AS text_arr;` type FindLtreeInputRow struct { - Ltree pgtype.Text `json:"ltree"` - TextArr pgtype.TextArray `json:"text_arr"` + Ltree pgtype.Text `json:"ltree" db:"ltree"` + TextArr []string `json:"text_arr" db:"text_arr"` } // FindLtreeInput implements Querier.FindLtreeInput. func (q *DBQuerier) FindLtreeInput(ctx context.Context, inLtree pgtype.Text, inLtreeArray []string) (FindLtreeInputRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindLtreeInput") - row := q.conn.QueryRow(ctx, findLtreeInputSQL, inLtree, inLtreeArray) - var item FindLtreeInputRow - if err := row.Scan(&item.Ltree, &item.TextArr); err != nil { - return item, fmt.Errorf("query FindLtreeInput: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "FindLtreeInput") + rows, err := q.conn.Query(ctx, findLtreeInputSQL, inLtree, inLtreeArray) + if err != nil { + return FindLtreeInputRow{}, fmt.Errorf("query FindLtreeInput: %w", err) } - return item, nil -} -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string + return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindLtreeInputRow]) } - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/ltree/query.sql_test.go b/example/ltree/query.sql_test.go index 7c8c28b9..7e2369fe 100644 --- a/example/ltree/query.sql_test.go +++ b/example/ltree/query.sql_test.go @@ -24,10 +24,10 @@ func TestQuerier(t *testing.T) { rows, err := q.FindTopScienceChildren(ctx) require.NoError(t, err) want := []pgtype.Text{ - {String: "Top.Science", Status: pgtype.Present}, - {String: "Top.Science.Astronomy", Status: pgtype.Present}, - {String: "Top.Science.Astronomy.Astrophysics", Status: pgtype.Present}, - {String: "Top.Science.Astronomy.Cosmology", Status: pgtype.Present}, + {String: "Top.Science", Valid: true}, + {String: "Top.Science.Astronomy", Valid: true}, + {String: "Top.Science.Astronomy.Astrophysics", Valid: true}, + {String: "Top.Science.Astronomy.Cosmology", Valid: true}, } assert.Equal(t, want, rows) } @@ -35,42 +35,23 @@ func TestQuerier(t *testing.T) { { rows, err := q.FindTopScienceChildrenAgg(ctx) require.NoError(t, err) - want := pgtype.TextArray{ - Elements: []pgtype.Text{ - {String: "Top.Science", Status: pgtype.Present}, - {String: "Top.Science.Astronomy", Status: pgtype.Present}, - {String: "Top.Science.Astronomy.Astrophysics", Status: pgtype.Present}, - {String: "Top.Science.Astronomy.Cosmology", Status: pgtype.Present}, - }, - Status: pgtype.Present, - Dimensions: []pgtype.ArrayDimension{{Length: 4, LowerBound: 1}}, + want := []string{ + "Top.Science", + "Top.Science.Astronomy", + "Top.Science.Astronomy.Astrophysics", + "Top.Science.Astronomy.Cosmology", } assert.Equal(t, want, rows) } { - in1 := pgtype.Text{String: "foo", Status: pgtype.Present} + in1 := pgtype.Text{String: "foo", Valid: true} in2 := []string{"qux", "qux"} - in2Txt := newTextArray(in2) rows, err := q.FindLtreeInput(ctx, in1, in2) require.NoError(t, err) assert.Equal(t, FindLtreeInputRow{ Ltree: in1, - TextArr: in2Txt, + TextArr: in2, }, rows) } } - -// newTextArray creates a one dimensional text array from the string slice with -// no null elements. -func newTextArray(ss []string) pgtype.TextArray { - elems := make([]pgtype.Text, len(ss)) - for i, s := range ss { - elems[i] = pgtype.Text{String: s, Status: pgtype.Present} - } - return pgtype.TextArray{ - Elements: elems, - Dimensions: []pgtype.ArrayDimension{{Length: int32(len(ss)), LowerBound: 1}}, - Status: pgtype.Present, - } -} diff --git a/example/nested/query.sql.go b/example/nested/query.sql.go index f1bc80bd..5dbc94ad 100644 --- a/example/nested/query.sql.go +++ b/example/nested/query.sql.go @@ -7,9 +7,10 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { ArrayNested2(ctx context.Context) ([]ProductImageType, error) @@ -20,8 +21,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -33,146 +33,71 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} + return &DBQuerier{conn: conn} } // Dimensions represents the Postgres composite type "dimensions". type Dimensions struct { - Width int `json:"width"` - Height int `json:"height"` + Width int `json:"width" db:"width"` + Height int `json:"height" db:"height"` } // ProductImageSetType represents the Postgres composite type "product_image_set_type". type ProductImageSetType struct { - Name string `json:"name"` - OrigImage ProductImageType `json:"orig_image"` - Images []ProductImageType `json:"images"` + Name string `json:"name" db:"name"` + OrigImage ProductImageType `json:"orig_image" db:"orig_image"` + Images []ProductImageType `json:"images" db:"images"` } // ProductImageType represents the Postgres composite type "product_image_type". type ProductImageType struct { - Source string `json:"source"` - Dimensions Dimensions `json:"dimensions"` -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.Codec // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + Source string `json:"source" db:"source"` + Dimensions Dimensions `json:"dimensions" db:"dimensions"` +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} +var typesToRegister = []string{} -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -// newDimensions creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'dimensions'. -func (tr *typeResolver) newDimensions() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "dimensions", - compositeField{name: "width", typeName: "int4", defaultVal: &pgtype.Int4{}}, - compositeField{name: "height", typeName: "int4", defaultVal: &pgtype.Int4{}}, - ) -} +var _ = addTypeToRegister("\"dimensions\"") -// newProductImageSetType creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'product_image_set_type'. -func (tr *typeResolver) newProductImageSetType() pgtype.ValueTranscoder { - pgtype.CompositeCodec{} - return tr.newCompositeValue( - "product_image_set_type", - compositeField{name: "name", typeName: "text", defaultVal: &pgtype.Text{}}, - compositeField{name: "orig_image", typeName: "product_image_type", defaultVal: tr.newProductImageType()}, - compositeField{name: "images", typeName: "_product_image_type", defaultVal: tr.newProductImageTypeArray()}, - ) -} +var _ = addTypeToRegister("\"product_image_set_type\"") -// newProductImageType creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'product_image_type'. -func (tr *typeResolver) newProductImageType() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "product_image_type", - compositeField{name: "source", typeName: "text", defaultVal: &pgtype.Text{}}, - compositeField{name: "dimensions", typeName: "dimensions", defaultVal: tr.newDimensions()}, - ) -} +var _ = addTypeToRegister("\"product_image_type\"") -// newProductImageTypeArray creates a new pgtype.ValueTranscoder for the Postgres -// '_product_image_type' array type. -func (tr *typeResolver) newProductImageTypeArray() pgtype.ValueTranscoder { - pgtype.CompositeCodec{} - return tr.newArrayValue("_product_image_type", "product_image_type", tr.newProductImageType) -} +var _ = addTypeToRegister("\"_product_image_type\"") const arrayNested2SQL = `SELECT ARRAY [ @@ -182,17 +107,13 @@ const arrayNested2SQL = `SELECT // ArrayNested2 implements Querier.ArrayNested2. func (q *DBQuerier) ArrayNested2(ctx context.Context) ([]ProductImageType, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "ArrayNested2") - row := q.conn.QueryRow(ctx, arrayNested2SQL) - item := []ProductImageType{} - imagesArray := q.types.newProductImageTypeArray() - if err := row.Scan(imagesArray); err != nil { - return item, fmt.Errorf("query ArrayNested2: %w", err) - } - if err := imagesArray.AssignTo(&item); err != nil { - return item, fmt.Errorf("assign ArrayNested2 row: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "ArrayNested2") + rows, err := q.conn.Query(ctx, arrayNested2SQL) + if err != nil { + return nil, fmt.Errorf("query ArrayNested2: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]ProductImageType]) } const nested3SQL = `SELECT @@ -207,51 +128,11 @@ const nested3SQL = `SELECT // Nested3 implements Querier.Nested3. func (q *DBQuerier) Nested3(ctx context.Context) ([]ProductImageSetType, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "Nested3") + ctx = context.WithValue(ctx, QueryName{}, "Nested3") rows, err := q.conn.Query(ctx, nested3SQL) if err != nil { return nil, fmt.Errorf("query Nested3: %w", err) } - defer rows.Close() - items := []ProductImageSetType{} - rowRow := q.types.newProductImageSetType() - for rows.Next() { - var item ProductImageSetType - if err := rows.Scan(rowRow); err != nil { - return nil, fmt.Errorf("scan Nested3 row: %w", err) - } - if err := rowRow.AssignTo(&item); err != nil { - return nil, fmt.Errorf("assign Nested3 row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close Nested3 rows: %w", err) - } - return items, err -} -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string + return pgx.CollectRows(rows, pgx.RowTo[ProductImageSetType]) } - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/pgcrypto/query.sql.go b/example/pgcrypto/query.sql.go index ff478512..3eb98c33 100644 --- a/example/pgcrypto/query.sql.go +++ b/example/pgcrypto/query.sql.go @@ -7,9 +7,10 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { CreateUser(ctx context.Context, email string, password string) (pgconn.CommandTag, error) @@ -20,8 +21,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -33,36 +33,41 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} + return &DBQuerier{conn: conn} } -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt +var typesToRegister = []string{} + +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } const createUserSQL = `INSERT INTO "user" (email, pass) @@ -70,10 +75,10 @@ VALUES ($1, crypt($2, gen_salt('bf')));` // CreateUser implements Querier.CreateUser. func (q *DBQuerier) CreateUser(ctx context.Context, email string, password string) (pgconn.CommandTag, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "CreateUser") + ctx = context.WithValue(ctx, QueryName{}, "CreateUser") cmdTag, err := q.conn.Exec(ctx, createUserSQL, email, password) if err != nil { - return cmdTag, fmt.Errorf("exec query CreateUser: %w", err) + return pgconn.CommandTag{}, fmt.Errorf("exec query CreateUser: %w", err) } return cmdTag, err } @@ -82,42 +87,17 @@ const findUserSQL = `SELECT email, pass from "user" where email = $1;` type FindUserRow struct { - Email string `json:"email"` - Pass string `json:"pass"` + Email string `json:"email" db:"email"` + Pass string `json:"pass" db:"pass"` } // FindUser implements Querier.FindUser. func (q *DBQuerier) FindUser(ctx context.Context, email string) (FindUserRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindUser") - row := q.conn.QueryRow(ctx, findUserSQL, email) - var item FindUserRow - if err := row.Scan(&item.Email, &item.Pass); err != nil { - return item, fmt.Errorf("query FindUser: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "FindUser") + rows, err := q.conn.Query(ctx, findUserSQL, email) + if err != nil { + return FindUserRow{}, fmt.Errorf("query FindUser: %w", err) } - return item, nil -} - -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} + return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindUserRow]) } - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/separate_out_dir/out/alpha_query.sql.0.go b/example/separate_out_dir/out/alpha_query.sql.0.go index f99f15dc..2b724fc5 100644 --- a/example/separate_out_dir/out/alpha_query.sql.0.go +++ b/example/separate_out_dir/out/alpha_query.sql.0.go @@ -7,9 +7,10 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { AlphaNested(ctx context.Context) (string, error) @@ -24,8 +25,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -37,160 +37,76 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} + return &DBQuerier{conn: conn} } // Alpha represents the Postgres composite type "alpha". type Alpha struct { - Key *string `json:"key"` -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + Key *string `json:"key" db:"key"` +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} +var typesToRegister = []string{} -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -// newAlpha creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'alpha'. -func (tr *typeResolver) newAlpha() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "alpha", - compositeField{name: "key", typeName: "text", defaultVal: &pgtype.Text{}}, - ) -} +var _ = addTypeToRegister("\"alpha\"") -// newAlphaArray creates a new pgtype.ValueTranscoder for the Postgres -// '_alpha' array type. -func (tr *typeResolver) newAlphaArray() pgtype.ValueTranscoder { - return tr.newArrayValue("_alpha", "alpha", tr.newAlpha) -} +var _ = addTypeToRegister("\"_alpha\"") const alphaNestedSQL = `SELECT 'alpha_nested' as output;` // AlphaNested implements Querier.AlphaNested. func (q *DBQuerier) AlphaNested(ctx context.Context) (string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "AlphaNested") - row := q.conn.QueryRow(ctx, alphaNestedSQL) - var item string - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query AlphaNested: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "AlphaNested") + rows, err := q.conn.Query(ctx, alphaNestedSQL) + if err != nil { + return "", fmt.Errorf("query AlphaNested: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) } const alphaCompositeArraySQL = `SELECT ARRAY[ROW('key')]::alpha[];` // AlphaCompositeArray implements Querier.AlphaCompositeArray. func (q *DBQuerier) AlphaCompositeArray(ctx context.Context) ([]Alpha, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "AlphaCompositeArray") - row := q.conn.QueryRow(ctx, alphaCompositeArraySQL) - item := []Alpha{} - arrayArray := q.types.newAlphaArray() - if err := row.Scan(arrayArray); err != nil { - return item, fmt.Errorf("query AlphaCompositeArray: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "AlphaCompositeArray") + rows, err := q.conn.Query(ctx, alphaCompositeArraySQL) + if err != nil { + return nil, fmt.Errorf("query AlphaCompositeArray: %w", err) } - if err := arrayArray.AssignTo(&item); err != nil { - return item, fmt.Errorf("assign AlphaCompositeArray row: %w", err) - } - return item, nil -} - -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]Alpha]) } - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/separate_out_dir/out/alpha_query.sql.1.go b/example/separate_out_dir/out/alpha_query.sql.1.go index c5b10b7c..ae6dcfed 100644 --- a/example/separate_out_dir/out/alpha_query.sql.1.go +++ b/example/separate_out_dir/out/alpha_query.sql.1.go @@ -5,17 +5,18 @@ package out import ( "context" "fmt" + "github.com/jackc/pgx/v5" ) const alphaSQL = `SELECT 'alpha' as output;` // Alpha implements Querier.Alpha. func (q *DBQuerier) Alpha(ctx context.Context) (string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "Alpha") - row := q.conn.QueryRow(ctx, alphaSQL) - var item string - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query Alpha: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "Alpha") + rows, err := q.conn.Query(ctx, alphaSQL) + if err != nil { + return "", fmt.Errorf("query Alpha: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) } diff --git a/example/separate_out_dir/out/bravo_query.sql.go b/example/separate_out_dir/out/bravo_query.sql.go index d3869f04..876f6e9e 100644 --- a/example/separate_out_dir/out/bravo_query.sql.go +++ b/example/separate_out_dir/out/bravo_query.sql.go @@ -5,17 +5,18 @@ package out import ( "context" "fmt" + "github.com/jackc/pgx/v5" ) const bravoSQL = `SELECT 'bravo' as output;` // Bravo implements Querier.Bravo. func (q *DBQuerier) Bravo(ctx context.Context) (string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "Bravo") - row := q.conn.QueryRow(ctx, bravoSQL) - var item string - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query Bravo: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "Bravo") + rows, err := q.conn.Query(ctx, bravoSQL) + if err != nil { + return "", fmt.Errorf("query Bravo: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) } diff --git a/example/slices/query.sql.go b/example/slices/query.sql.go index ed0fad12..629503bb 100644 --- a/example/slices/query.sql.go +++ b/example/slices/query.sql.go @@ -7,10 +7,11 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" "time" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { GetBools(ctx context.Context, data []bool) ([]bool, error) @@ -25,8 +26,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -38,123 +38,71 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + return &DBQuerier{conn: conn} +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} +var typesToRegister = []string{} -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -// newboolArrayRaw returns all elements for the Postgres array type '_bool' -// as a slice of interface{} for use with the pgtype.Value Set method. -func (tr *typeResolver) newboolArrayRaw(vs []bool) []interface{} { - elems := make([]interface{}, len(vs)) - for i, v := range vs { - elems[i] = v - } - return elems -} + const getBoolsSQL = `SELECT $1::boolean[];` // GetBools implements Querier.GetBools. func (q *DBQuerier) GetBools(ctx context.Context, data []bool) ([]bool, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "GetBools") - row := q.conn.QueryRow(ctx, getBoolsSQL, data) - item := []bool{} - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query GetBools: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "GetBools") + rows, err := q.conn.Query(ctx, getBoolsSQL, data) + if err != nil { + return nil, fmt.Errorf("query GetBools: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]bool]) } const getOneTimestampSQL = `SELECT $1::timestamp;` // GetOneTimestamp implements Querier.GetOneTimestamp. func (q *DBQuerier) GetOneTimestamp(ctx context.Context, data *time.Time) (*time.Time, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "GetOneTimestamp") - row := q.conn.QueryRow(ctx, getOneTimestampSQL, data) - var item *time.Time - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query GetOneTimestamp: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "GetOneTimestamp") + rows, err := q.conn.Query(ctx, getOneTimestampSQL, data) + if err != nil { + return nil, fmt.Errorf("query GetOneTimestamp: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*time.Time]) } const getManyTimestamptzsSQL = `SELECT * @@ -162,24 +110,13 @@ FROM unnest($1::timestamptz[]);` // GetManyTimestamptzs implements Querier.GetManyTimestamptzs. func (q *DBQuerier) GetManyTimestamptzs(ctx context.Context, data []time.Time) ([]*time.Time, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "GetManyTimestamptzs") + ctx = context.WithValue(ctx, QueryName{}, "GetManyTimestamptzs") rows, err := q.conn.Query(ctx, getManyTimestamptzsSQL, data) if err != nil { return nil, fmt.Errorf("query GetManyTimestamptzs: %w", err) } - defer rows.Close() - items := []*time.Time{} - for rows.Next() { - var item time.Time - if err := rows.Scan(&item); err != nil { - return nil, fmt.Errorf("scan GetManyTimestamptzs row: %w", err) - } - items = append(items, &item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close GetManyTimestamptzs rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowTo[*time.Time]) } const getManyTimestampsSQL = `SELECT * @@ -187,47 +124,11 @@ FROM unnest($1::timestamp[]);` // GetManyTimestamps implements Querier.GetManyTimestamps. func (q *DBQuerier) GetManyTimestamps(ctx context.Context, data []*time.Time) ([]*time.Time, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "GetManyTimestamps") + ctx = context.WithValue(ctx, QueryName{}, "GetManyTimestamps") rows, err := q.conn.Query(ctx, getManyTimestampsSQL, data) if err != nil { return nil, fmt.Errorf("query GetManyTimestamps: %w", err) } - defer rows.Close() - items := []*time.Time{} - for rows.Next() { - var item time.Time - if err := rows.Scan(&item); err != nil { - return nil, fmt.Errorf("scan GetManyTimestamps row: %w", err) - } - items = append(items, &item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close GetManyTimestamps rows: %w", err) - } - return items, err -} - -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} + return pgx.CollectRows(rows, pgx.RowTo[*time.Time]) } - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/syntax/query.sql.go b/example/syntax/query.sql.go index 85bf0661..f085f89f 100644 --- a/example/syntax/query.sql.go +++ b/example/syntax/query.sql.go @@ -7,9 +7,10 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { // Query to test escaping in generated Go. @@ -42,8 +43,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -55,7 +55,7 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} + return &DBQuerier{conn: conn} } // UnnamedEnum123 represents the Postgres enum "123". @@ -70,178 +70,160 @@ const ( func (u UnnamedEnum123) String() string { return string(u) } -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining + } + return nil } -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} +var typesToRegister = []string{} -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false - } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} +var _ = addTypeToRegister("\"123\"") const backtickSQL = "SELECT '`';" // Backtick implements Querier.Backtick. func (q *DBQuerier) Backtick(ctx context.Context) (string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "Backtick") - row := q.conn.QueryRow(ctx, backtickSQL) - var item string - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query Backtick: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "Backtick") + rows, err := q.conn.Query(ctx, backtickSQL) + if err != nil { + return "", fmt.Errorf("query Backtick: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) } const backtickQuoteBacktickSQL = "SELECT '`\"`';" // BacktickQuoteBacktick implements Querier.BacktickQuoteBacktick. func (q *DBQuerier) BacktickQuoteBacktick(ctx context.Context) (string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "BacktickQuoteBacktick") - row := q.conn.QueryRow(ctx, backtickQuoteBacktickSQL) - var item string - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query BacktickQuoteBacktick: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "BacktickQuoteBacktick") + rows, err := q.conn.Query(ctx, backtickQuoteBacktickSQL) + if err != nil { + return "", fmt.Errorf("query BacktickQuoteBacktick: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) } const backtickNewlineSQL = "SELECT '`\n';" // BacktickNewline implements Querier.BacktickNewline. func (q *DBQuerier) BacktickNewline(ctx context.Context) (string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "BacktickNewline") - row := q.conn.QueryRow(ctx, backtickNewlineSQL) - var item string - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query BacktickNewline: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "BacktickNewline") + rows, err := q.conn.Query(ctx, backtickNewlineSQL) + if err != nil { + return "", fmt.Errorf("query BacktickNewline: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) } const backtickDoubleQuoteSQL = "SELECT '`\"';" // BacktickDoubleQuote implements Querier.BacktickDoubleQuote. func (q *DBQuerier) BacktickDoubleQuote(ctx context.Context) (string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "BacktickDoubleQuote") - row := q.conn.QueryRow(ctx, backtickDoubleQuoteSQL) - var item string - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query BacktickDoubleQuote: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "BacktickDoubleQuote") + rows, err := q.conn.Query(ctx, backtickDoubleQuoteSQL) + if err != nil { + return "", fmt.Errorf("query BacktickDoubleQuote: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) } const backtickBackslashNSQL = "SELECT '`\\n';" // BacktickBackslashN implements Querier.BacktickBackslashN. func (q *DBQuerier) BacktickBackslashN(ctx context.Context) (string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "BacktickBackslashN") - row := q.conn.QueryRow(ctx, backtickBackslashNSQL) - var item string - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query BacktickBackslashN: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "BacktickBackslashN") + rows, err := q.conn.Query(ctx, backtickBackslashNSQL) + if err != nil { + return "", fmt.Errorf("query BacktickBackslashN: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) } const illegalNameSymbolsSQL = "SELECT '`\\n' as \"$\", $1 as \"foo.bar!@#$%&*()\"\"--+\";" type IllegalNameSymbolsRow struct { - UnnamedColumn0 string `json:"$"` - FooBar string `json:"foo.bar!@#$%&*()\"--+"` + UnnamedColumn0 string `json:"$" db:"$"` + FooBar string `json:"foo.bar!@#$%&*()\"--+" db:"foo.bar!@#$%&*()\"--+"` } // IllegalNameSymbols implements Querier.IllegalNameSymbols. func (q *DBQuerier) IllegalNameSymbols(ctx context.Context, helloWorld string) (IllegalNameSymbolsRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "IllegalNameSymbols") - row := q.conn.QueryRow(ctx, illegalNameSymbolsSQL, helloWorld) - var item IllegalNameSymbolsRow - if err := row.Scan(&item.UnnamedColumn0, &item.FooBar); err != nil { - return item, fmt.Errorf("query IllegalNameSymbols: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "IllegalNameSymbols") + rows, err := q.conn.Query(ctx, illegalNameSymbolsSQL, helloWorld) + if err != nil { + return IllegalNameSymbolsRow{}, fmt.Errorf("query IllegalNameSymbols: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[IllegalNameSymbolsRow]) } const spaceAfterSQL = `SELECT $1;` // SpaceAfter implements Querier.SpaceAfter. func (q *DBQuerier) SpaceAfter(ctx context.Context, space string) (string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "SpaceAfter") - row := q.conn.QueryRow(ctx, spaceAfterSQL, space) - var item string - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query SpaceAfter: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "SpaceAfter") + rows, err := q.conn.Query(ctx, spaceAfterSQL, space) + if err != nil { + return "", fmt.Errorf("query SpaceAfter: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) } const badEnumNameSQL = `SELECT 'inconvertible_enum_name'::"123";` // BadEnumName implements Querier.BadEnumName. func (q *DBQuerier) BadEnumName(ctx context.Context) (UnnamedEnum123, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "BadEnumName") - row := q.conn.QueryRow(ctx, badEnumNameSQL) - var item UnnamedEnum123 - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query BadEnumName: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "BadEnumName") + rows, err := q.conn.Query(ctx, badEnumNameSQL) + if err != nil { + return "", fmt.Errorf("query BadEnumName: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[UnnamedEnum123]) } const goKeywordSQL = `SELECT $1::text;` // GoKeyword implements Querier.GoKeyword. func (q *DBQuerier) GoKeyword(ctx context.Context, go_ string) (string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "GoKeyword") - row := q.conn.QueryRow(ctx, goKeywordSQL, go_) - var item string - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query GoKeyword: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "GoKeyword") + rows, err := q.conn.Query(ctx, goKeywordSQL, go_) + if err != nil { + return "", fmt.Errorf("query GoKeyword: %w", err) } - return item, nil -} - -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} - -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) } - -func (t textPreferrer) TypeName() string { - return t.typeName -} - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/example/void/query.sql.go b/example/void/query.sql.go index 146b3cf1..94790852 100644 --- a/example/void/query.sql.go +++ b/example/void/query.sql.go @@ -7,9 +7,10 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgtype" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { VoidOnly(ctx context.Context) (pgconn.CommandTag, error) @@ -26,8 +27,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use - types *typeResolver // resolve types by name + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -39,46 +39,51 @@ type genericConn interface { // NewQuerier creates a DBQuerier that implements Querier. func NewQuerier(conn genericConn) *DBQuerier { - return &DBQuerier{conn: conn, types: newTypeResolver()} -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + return &DBQuerier{conn: conn} +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt +var typesToRegister = []string{} + +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } const voidOnlySQL = `SELECT void_fn();` // VoidOnly implements Querier.VoidOnly. func (q *DBQuerier) VoidOnly(ctx context.Context) (pgconn.CommandTag, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "VoidOnly") + ctx = context.WithValue(ctx, QueryName{}, "VoidOnly") cmdTag, err := q.conn.Exec(ctx, voidOnlySQL) if err != nil { - return cmdTag, fmt.Errorf("exec query VoidOnly: %w", err) + return pgconn.CommandTag{}, fmt.Errorf("exec query VoidOnly: %w", err) } return cmdTag, err } @@ -87,10 +92,10 @@ const voidOnlyTwoParamsSQL = `SELECT void_fn_two_params($1, 'text');` // VoidOnlyTwoParams implements Querier.VoidOnlyTwoParams. func (q *DBQuerier) VoidOnlyTwoParams(ctx context.Context, id int32) (pgconn.CommandTag, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "VoidOnlyTwoParams") + ctx = context.WithValue(ctx, QueryName{}, "VoidOnlyTwoParams") cmdTag, err := q.conn.Exec(ctx, voidOnlyTwoParamsSQL, id) if err != nil { - return cmdTag, fmt.Errorf("exec query VoidOnlyTwoParams: %w", err) + return pgconn.CommandTag{}, fmt.Errorf("exec query VoidOnlyTwoParams: %w", err) } return cmdTag, err } @@ -99,78 +104,60 @@ const voidTwoSQL = `SELECT void_fn(), 'foo' as name;` // VoidTwo implements Querier.VoidTwo. func (q *DBQuerier) VoidTwo(ctx context.Context) (string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "VoidTwo") - row := q.conn.QueryRow(ctx, voidTwoSQL) + ctx = context.WithValue(ctx, QueryName{}, "VoidTwo") + rows, err := q.conn.Query(ctx, voidTwoSQL) + if err != nil { + return "", fmt.Errorf("query VoidTwo: %w", err) + } + + return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (string, error) { var item string if err := row.Scan(nil, &item); err != nil { - return item, fmt.Errorf("query VoidTwo: %w", err) + return item, err } return item, nil +}) } const voidThreeSQL = `SELECT void_fn(), 'foo' as foo, 'bar' as bar;` type VoidThreeRow struct { - Foo string `json:"foo"` - Bar string `json:"bar"` + Foo string `json:"foo" db:"foo"` + Bar string `json:"bar" db:"bar"` } // VoidThree implements Querier.VoidThree. func (q *DBQuerier) VoidThree(ctx context.Context) (VoidThreeRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "VoidThree") - row := q.conn.QueryRow(ctx, voidThreeSQL) + ctx = context.WithValue(ctx, QueryName{}, "VoidThree") + rows, err := q.conn.Query(ctx, voidThreeSQL) + if err != nil { + return VoidThreeRow{}, fmt.Errorf("query VoidThree: %w", err) + } + + return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (VoidThreeRow, error) { var item VoidThreeRow if err := row.Scan(nil, &item.Foo, &item.Bar); err != nil { - return item, fmt.Errorf("query VoidThree: %w", err) + return item, err } return item, nil +}) } const voidThree2SQL = `SELECT 'foo' as foo, void_fn(), void_fn();` // VoidThree2 implements Querier.VoidThree2. func (q *DBQuerier) VoidThree2(ctx context.Context) ([]string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "VoidThree2") + ctx = context.WithValue(ctx, QueryName{}, "VoidThree2") rows, err := q.conn.Query(ctx, voidThree2SQL) if err != nil { return nil, fmt.Errorf("query VoidThree2: %w", err) } - defer rows.Close() - items := []string{} - for rows.Next() { - var item string - if err := rows.Scan(&item, nil, nil); err != nil { - return nil, fmt.Errorf("scan VoidThree2 row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close VoidThree2 rows: %w", err) - } - return items, err -} - -// textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding -// format to text instead binary (the default). pggen uses the text format -// when the OID is unknownOID because the binary format requires the OID. -// Typically occurs for unregistered types. -type textPreferrer struct { - pgtype.ValueTranscoder - typeName string -} -// PreferredParamFormat implements pgtype.ParamFormatPreferrer. -func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } - -func (t textPreferrer) NewTypeValue() pgtype.Value { - return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} -} - -func (t textPreferrer) TypeName() string { - return t.typeName + return pgx.CollectRows(rows, func(row pgx.CollectableRow) (string, error) { + var item string + if err := row.Scan(&item, nil, nil); err != nil { + return item, err + } + return item, nil +}) } - -// unknownOID means we don't know the OID for a type. This is okay for decoding -// because pgx call DecodeText or DecodeBinary without requiring the OID. For -// encoding parameters, pggen uses textPreferrer if the OID is unknown. -const unknownOID = 0 diff --git a/internal/codegen/golang/declarer.go b/internal/codegen/golang/declarer.go index aa1347a1..e35ed33e 100644 --- a/internal/codegen/golang/declarer.go +++ b/internal/codegen/golang/declarer.go @@ -2,6 +2,7 @@ package golang import ( "sort" + "strings" "github.com/jschaf/pggen/internal/codegen/golang/gotype" ) @@ -43,6 +44,10 @@ func (d DeclarerSet) ListAll() []Declarer { return decls } +func pgTypeNameForLoadType(name string) string { + return `"` + strings.ReplaceAll(name, `"`, `""`) + `"` +} + // FindInputDeclarers finds all necessary Declarers for types that appear in // the input parameters. Returns nil if no declarers are needed. func FindInputDeclarers(typ gotype.Type) DeclarerSet { @@ -70,7 +75,7 @@ func FindInputDeclarers(typ gotype.Type) DeclarerSet { decls.AddAll(NewTypeResolverInitDeclarer()) // always add findInputDeclsHelper(typ, decls) // Inputs depend on output transcoders. - findOutputDeclsHelper(typ, decls /*hadCompositeParent*/, false) + findOutputDeclsHelper(typ, decls) return decls } @@ -103,21 +108,17 @@ func findInputDeclsHelper(typ gotype.Type, decls DeclarerSet) { func FindOutputDeclarers(typ gotype.Type) DeclarerSet { decls := NewDeclarerSet() decls.AddAll(NewTypeResolverInitDeclarer()) // always add - findOutputDeclsHelper(typ, decls, false) + findOutputDeclsHelper(typ, decls) return decls } -func findOutputDeclsHelper(typ gotype.Type, decls DeclarerSet, hadCompositeParent bool) { +func findOutputDeclsHelper(typ gotype.Type, decls DeclarerSet) { switch typ := gotype.UnwrapNestedType(typ).(type) { case *gotype.EnumType: decls.AddAll( NewEnumTypeDeclarer(typ), + NewEnumTranscoderDeclarer(typ), ) - if hadCompositeParent { - // We can use a string as the decoder except if the enum is part of a - // composite type. - decls.AddAll(NewEnumTranscoderDeclarer(typ)) - } case *gotype.CompositeType: decls.AddAll( @@ -126,7 +127,7 @@ func findOutputDeclsHelper(typ gotype.Type, decls DeclarerSet, hadCompositeParen NewTypeResolverDeclarer(), ) for _, childType := range typ.FieldTypes { - findOutputDeclsHelper(childType, decls, true) + findOutputDeclsHelper(childType, decls) } case *gotype.ArrayType: @@ -138,7 +139,7 @@ func findOutputDeclsHelper(typ gotype.Type, decls DeclarerSet, hadCompositeParen case *gotype.CompositeType, *gotype.EnumType: decls.AddAll(NewArrayDecoderDeclarer(typ)) } - findOutputDeclsHelper(typ.Elem, decls, hadCompositeParent) + findOutputDeclsHelper(typ.Elem, decls) default: return @@ -158,33 +159,38 @@ func NewConstantDeclarer(key, str string) ConstantDeclarer { func (c ConstantDeclarer) DedupeKey() string { return c.key } func (c ConstantDeclarer) Declare(string) (string, error) { return c.str, nil } -const typeResolverInitDecl = `// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false +const typeResolverInitDecl = `// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt +var typesToRegister = []string{} + +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} }` // NewTypeResolverInitDeclarer declare type resolver init code always needed. @@ -192,56 +198,7 @@ func NewTypeResolverInitDeclarer() ConstantDeclarer { return NewConstantDeclarer("type_resolver::00_common", typeResolverInitDecl) } -const typeResolverBodyDecl = `type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use -} - -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} - -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -}` +const typeResolverBodyDecl = `` // NewTypeResolverDeclarer declares type resolver body code sometimes needed. func NewTypeResolverDeclarer() ConstantDeclarer { diff --git a/internal/codegen/golang/declarer_array.go b/internal/codegen/golang/declarer_array.go index bb9a58a3..1aac1ab0 100644 --- a/internal/codegen/golang/declarer_array.go +++ b/internal/codegen/golang/declarer_array.go @@ -1,24 +1,18 @@ package golang import ( - "fmt" "strconv" "strings" "github.com/jschaf/pggen/internal/codegen/golang/gotype" ) -// NameArrayTranscoderFunc returns the function name that creates a -// pgtype.ValueTranscoder for the array type that's used to decode rows returned -// by Postgres. +// NameArrayTranscoderFunc returns the legacy array helper name. func NameArrayTranscoderFunc(typ *gotype.ArrayType) string { return "new" + typ.Elem.BaseName() + "Array" } -// NameArrayInitFunc returns the name for the function that creates an -// initialized pgtype.ValueTranscoder for the array type that's used to encode -// query parameters. This function is only necessary for top-level types. -// Descendant types use the raw functions, named by NameArrayRawFunc. +// NameArrayInitFunc returns the legacy array input helper name. func NameArrayInitFunc(typ *gotype.ArrayType) string { elem := typ.Elem if t, ok := elem.(*gotype.ImportType); ok { @@ -36,9 +30,7 @@ func NameArrayInitFunc(typ *gotype.ArrayType) string { } } -// NameArrayRawFunc returns the function name that create the []interface{} -// array for the array type so that we can use it with a parent encoder -// function, like NameCompositeInitFunc, in the pgtype.Value Set call. +// NameArrayRawFunc returns the legacy raw array helper name. func NameArrayRawFunc(typ *gotype.ArrayType) string { elem := typ.Elem if t, ok := elem.(*gotype.ImportType); ok { @@ -56,8 +48,7 @@ func NameArrayRawFunc(typ *gotype.ArrayType) string { } } -// ArrayTranscoderDeclarer declares a new Go function that creates a -// pgtype.ValueTranscoder decoder for an array Postgres type. +// ArrayTranscoderDeclarer declares a PostgreSQL array type registration. type ArrayTranscoderDeclarer struct { typ *gotype.ArrayType } @@ -72,57 +63,14 @@ func (a ArrayTranscoderDeclarer) DedupeKey() string { func (a ArrayTranscoderDeclarer) Declare(string) (string, error) { sb := &strings.Builder{} - funcName := NameArrayTranscoderFunc(a.typ) - - // Doc comment - sb.WriteString("// ") - sb.WriteString(funcName) - sb.WriteString(" creates a new pgtype.ValueTranscoder for the Postgres\n") - sb.WriteString("// '") - sb.WriteString(a.typ.PgArray.Name) - sb.WriteString("' array type.\n") - - // Function signature - sb.WriteString("func (tr *typeResolver) ") - sb.WriteString(funcName) - sb.WriteString("() pgtype.ValueTranscoder {\n\t") - - // newArrayValue call - sb.WriteString("return tr.newArrayValue(") - sb.WriteString(strconv.Quote(a.typ.PgArray.Name)) - sb.WriteString(", ") - sb.WriteString(strconv.Quote(a.typ.PgArray.Elem.String())) - sb.WriteString(", ") - - // Default element transcoder - switch elem := gotype.UnwrapNestedType(a.typ.Elem).(type) { - case *gotype.CompositeType: - sb.WriteString("tr.") - sb.WriteString(NameCompositeTranscoderFunc(elem)) - case *gotype.EnumType: - sb.WriteString(NameEnumTranscoderFunc(elem)) - default: - return "", fmt.Errorf("array composite decoder only supports composite and enum elems; got %T", a.typ.Elem) - } + sb.WriteString("var _ = addTypeToRegister(") + sb.WriteString(strconv.Quote(pgTypeNameForLoadType(a.typ.PgArray.Name))) sb.WriteString(")") - sb.WriteString("\n") - sb.WriteString("}") - return sb.String(), nil } -// ArrayInitDeclarer declares a new Go function that creates an *initialized* -// pgtype.ValueTranscoder for the Postgres type represented by the -// gotype.ArrayType. -// -// We need a separate declarer from ArrayTranscoderDeclarer because setting a -// pgtype.ValueTranscoder is much less flexible on the values allowed compared -// to AssignTo. We can assign a pgtype.ArrayType to any struct but we can only -// set it with an [][]interface{} if the array elements are composite types. -// -// Additionally, we need to use the Postgres text format exclusively because the -// Postgres binary format requires the type OID but pggen doesn't necessarily -// know the OIDs of the types. The text format, however, doesn't require OIDs. +// ArrayInitDeclarer is retained for the old declarer flow. pgx v5 encodes +// registered array types directly. type ArrayInitDeclarer struct { typ *gotype.ArrayType } @@ -136,45 +84,10 @@ func (a ArrayInitDeclarer) DedupeKey() string { } func (a ArrayInitDeclarer) Declare(string) (string, error) { - funcName := NameArrayInitFunc(a.typ) - sb := &strings.Builder{} - sb.Grow(256) - - // Doc comment - sb.WriteString("// ") - sb.WriteString(funcName) - sb.WriteString(" creates an initialized pgtype.ValueTranscoder for the\n") - sb.WriteString("// Postgres array type '") - sb.WriteString(a.typ.PgArray.Name) - sb.WriteString("' to encode query parameters.\n") - - // Function signature - sb.WriteString("func (tr *typeResolver) ") - sb.WriteString(funcName) - sb.WriteString("(ps ") - sb.WriteString(a.typ.BaseName()) - sb.WriteString(") pgtype.ValueTranscoder {\n\t") - - // Function body - sb.WriteString("dec := tr.") - sb.WriteString(NameArrayTranscoderFunc(a.typ)) - sb.WriteString("()\n\t") - sb.WriteString("if err := dec.Set(tr.") - sb.WriteString(NameArrayRawFunc(a.typ)) - sb.WriteString("(ps)); err != nil {\n\t\t") - sb.WriteString(fmt.Sprintf(`panic("encode %s: " + err.Error())`, a.typ.BaseName())) - sb.WriteString(" // should always succeed\n\t") - sb.WriteString("}\n\t") - sb.WriteString("return textPreferrer{ValueTranscoder: dec, typeName: ") - sb.WriteString(strconv.Quote(a.typ.PgArray.Name)) - sb.WriteString("}\n") - sb.WriteString("}") - return sb.String(), nil + return "", nil } -// ArrayRawDeclarer declares a new Go function that returns all fields -// as a generic array: []interface{}. Necessary because we can only set -// pgtype.ArrayType from a []interface{}. +// ArrayRawDeclarer is retained for the old declarer flow. type ArrayRawDeclarer struct { typ *gotype.ArrayType } @@ -188,39 +101,5 @@ func (a ArrayRawDeclarer) DedupeKey() string { } func (a ArrayRawDeclarer) Declare(pkgPath string) (string, error) { - funcName := NameArrayRawFunc(a.typ) - sb := &strings.Builder{} - sb.Grow(256) - - // Doc comment - sb.WriteString("// ") - sb.WriteString(funcName) - sb.WriteString(" returns all elements for the Postgres array type '") - sb.WriteString(a.typ.PgArray.Name) - sb.WriteString("'\n// as a slice of interface{} for use with the pgtype.Value Set method.\n") - - // Function signature - sb.WriteString("func (tr *typeResolver) ") - sb.WriteString(funcName) - sb.WriteString("(vs ") - sb.WriteString(gotype.QualifyType(a.typ, pkgPath)) - sb.WriteString(") []interface{} {\n\t") - - // Function body - sb.WriteString("elems := make([]interface{}, len(vs))\n\t") - sb.WriteString("for i, v := range vs {\n\t\t") - sb.WriteString("elems[i] = ") - switch elem := gotype.UnwrapNestedType(a.typ.Elem).(type) { - case *gotype.CompositeType: - sb.WriteString("tr.") - sb.WriteString(NameCompositeRawFunc(elem)) - sb.WriteString("(v)") - default: - sb.WriteString("v") - } - sb.WriteString("\n\t") - sb.WriteString("}\n\t") - sb.WriteString("return elems\n") - sb.WriteString("}") - return sb.String(), nil + return "", nil } diff --git a/internal/codegen/golang/declarer_composite.go b/internal/codegen/golang/declarer_composite.go index a2c2a34c..409308a2 100644 --- a/internal/codegen/golang/declarer_composite.go +++ b/internal/codegen/golang/declarer_composite.go @@ -5,28 +5,19 @@ import ( "strings" "github.com/jschaf/pggen/internal/codegen/golang/gotype" - "github.com/jschaf/pggen/internal/pg" ) -// NameCompositeTranscoderFunc returns the function name that creates a -// pgtype.ValueTranscoder for the composite type that's used to decode rows -// returned by Postgres. +// NameCompositeTranscoderFunc returns the legacy composite helper name. func NameCompositeTranscoderFunc(typ *gotype.CompositeType) string { return "new" + typ.Name } -// NameCompositeInitFunc returns the name of the function that creates an -// initialized pgtype.ValueTranscoder for the composite type used as a query -// parameters. This function is only necessary for top-level types. Descendant -// types use the raw functions, named by NameCompositeRawFunc. +// NameCompositeInitFunc returns the legacy composite input helper name. func NameCompositeInitFunc(typ *gotype.CompositeType) string { return "new" + typ.Name + "Init" } -// NameCompositeRawFunc returns the function name that creates the -// []interface{} array for the composite type so that we can use it with a -// parent encoder function, like NameCompositeInitFunc, in the pgtype.Value -// Set call. +// NameCompositeRawFunc returns the legacy raw composite helper name. func NameCompositeRawFunc(typ *gotype.CompositeType) string { return "new" + typ.Name + "Raw" } @@ -78,6 +69,8 @@ func (c CompositeTypeDeclarer) Declare(pkgPath string) (string, error) { sb.WriteString(strings.Repeat(" ", typeLen-len(qualType))) sb.WriteString("`json:") sb.WriteString(strconv.Quote(c.comp.PgComposite.ColumnNames[i])) + sb.WriteString(" db:") + sb.WriteString(strconv.Quote(c.comp.PgComposite.ColumnNames[i])) sb.WriteString("`") sb.WriteRune('\n') } @@ -122,100 +115,16 @@ func (c CompositeTranscoderDeclarer) DedupeKey() string { } func (c CompositeTranscoderDeclarer) Declare(pkgPath string) (string, error) { - funcName := NameCompositeTranscoderFunc(c.typ) sb := &strings.Builder{} sb.Grow(256) - - // Doc comment - sb.WriteString("// ") - sb.WriteString(funcName) - sb.WriteString(" creates a new pgtype.ValueTranscoder for the Postgres\n") - sb.WriteString("// composite type '") - sb.WriteString(c.typ.PgComposite.Name) - sb.WriteString("'.\n") - - // Function signature - sb.WriteString("func (tr *typeResolver) ") - sb.WriteString(funcName) - sb.WriteString("() pgtype.ValueTranscoder {\n\t") - - // newCompositeValue call - sb.WriteString("return tr.newCompositeValue(\n\t\t") - sb.WriteString(strconv.Quote(c.typ.PgComposite.Name)) - sb.WriteString(",") - - // newCompositeValue - field names of the composite type - for i := range c.typ.FieldNames { - sb.WriteString("\n\t\t") - sb.WriteString(`compositeField{name: `) - sb.WriteString(strconv.Quote(c.typ.PgComposite.ColumnNames[i])) // field name - sb.WriteString(", typeName: ") - sb.WriteString(strconv.Quote(c.typ.PgComposite.ColumnTypes[i].String())) // field type name - sb.WriteString(", defaultVal: ") - - // field default pgtype.ValueTranscoder - switch fieldType := gotype.UnwrapNestedType(c.typ.FieldTypes[i]).(type) { - case *gotype.CompositeType: - childFuncName := NameCompositeTranscoderFunc(fieldType) - sb.WriteString("tr.") - sb.WriteString(childFuncName) - sb.WriteString("()") - case *gotype.EnumType: - sb.WriteString(NameEnumTranscoderFunc(fieldType)) - sb.WriteString("()") - case *gotype.ArrayType: - if typ, ok := gotype.FindKnownTypePgx(fieldType.PgArray.OID()); ok { - sb.WriteString("&") // pgx needs pointers to types - sb.WriteString(gotype.QualifyType(typ, pkgPath)) - sb.WriteString("{}") - break - } - sb.WriteString("tr.") - sb.WriteString(NameArrayTranscoderFunc(fieldType)) - sb.WriteString("()") - case *gotype.VoidType: - // skip - default: - sb.WriteString("&") // pgx needs pointers to types - pgType := c.typ.PgComposite.ColumnTypes[i] - if pgType == nil || pgType == (pg.VoidType{}) { - sb.WriteString("nil,") - } else { - var qualType string - if decoderType, ok := gotype.FindKnownTypePgx(pgType.OID()); ok { - // Look for the pgx variant it matches the interface expected by - // newCompositeValue, pgtype.ValueTranscoder. - qualType = gotype.QualifyType(decoderType, pkgPath) - } else { - // Attempt to use the original, provided type. - qualType = gotype.QualifyType(c.typ.FieldTypes[i], pkgPath) - } - sb.WriteString(qualType) - sb.WriteString("{}") - } - } - sb.WriteString(`},`) - } - - sb.WriteString("\n\t") + sb.WriteString("var _ = addTypeToRegister(") + sb.WriteString(strconv.Quote(pgTypeNameForLoadType(c.typ.PgComposite.Name))) sb.WriteString(")") - sb.WriteString("\n") - sb.WriteString("}") return sb.String(), nil } -// CompositeInitDeclarer declares a new Go function that creates an initialized -// pgtype.ValueTranscoder for the Postgres type represented by the -// gotype.CompositeType. -// -// We need a separate encoder because setting a pgtype.ValueTranscoder is much -// less flexible on the values allowed compared to AssignTo. We can assign a -// pgtype.CompositeType to any struct but we can only set it with an -// []interface{}. -// -// Additionally, we need to use the Postgres text format exclusively because the -// Postgres binary format requires the type OID but pggen doesn't necessarily -// know the OIDs of the types. The text format, however, doesn't require OIDs. +// CompositeInitDeclarer is retained for the old declarer flow. pgx v5 encodes +// registered composite types directly. type CompositeInitDeclarer struct { typ *gotype.CompositeType } @@ -229,41 +138,10 @@ func (c CompositeInitDeclarer) DedupeKey() string { } func (c CompositeInitDeclarer) Declare(string) (string, error) { - funcName := NameCompositeInitFunc(c.typ) - sb := &strings.Builder{} - sb.Grow(256) - - // Doc comment - sb.WriteString("// ") - sb.WriteString(funcName) - sb.WriteString(" creates an initialized pgtype.ValueTranscoder for the\n") - sb.WriteString("// Postgres composite type '") - sb.WriteString(c.typ.PgComposite.Name) - sb.WriteString("' to encode query parameters.\n") - - // Function signature - sb.WriteString("func (tr *typeResolver) ") - sb.WriteString(funcName) - sb.WriteString("(v ") - sb.WriteString(c.typ.Name) - sb.WriteString(") pgtype.ValueTranscoder {\n\t") - - // Function body - sb.WriteString("return tr.setValue(tr.") - sb.WriteString(NameCompositeTranscoderFunc(c.typ)) - sb.WriteString("(), tr.") - sb.WriteString(NameCompositeRawFunc(c.typ)) - sb.WriteString("(v))\n") - sb.WriteString("}") - return sb.String(), nil + return "", nil } -// CompositeRawDeclarer declares a new Go function that returns all fields -// of a composite type as a generic array: []interface{}. Necessary because we -// can only set pgtype.CompositeType from a []interface{}. -// -// Revisit after https://github.com/jackc/pgx/v5/pgtype/pull/100 to see if we can -// simplify. +// CompositeRawDeclarer is retained for the old declarer flow. type CompositeRawDeclarer struct { typ *gotype.CompositeType } @@ -277,62 +155,5 @@ func (c CompositeRawDeclarer) DedupeKey() string { } func (c CompositeRawDeclarer) Declare(string) (string, error) { - funcName := NameCompositeRawFunc(c.typ) - sb := &strings.Builder{} - sb.Grow(256) - - // Doc comment - sb.WriteString("// ") - sb.WriteString(funcName) - sb.WriteString(" returns all composite fields for the Postgres composite\n") - sb.WriteString("// type '") - sb.WriteString(c.typ.PgComposite.Name) - sb.WriteString("' as a slice of interface{} to encode query parameters.\n") - - // Function signature - sb.WriteString("func (tr *typeResolver) ") - sb.WriteString(funcName) - sb.WriteString("(v ") - sb.WriteString(c.typ.Name) - sb.WriteString(") []interface{} {\n\t") - - // Function body - sb.WriteString("return []interface{}{") - - // Field Assigners of the composite type - for i, fieldType := range c.typ.FieldTypes { - fieldName := c.typ.FieldNames[i] - sb.WriteString("\n\t\t") - switch fieldType := gotype.UnwrapNestedType(fieldType).(type) { - case *gotype.CompositeType: - childFuncName := NameCompositeRawFunc(fieldType) - sb.WriteString("tr.") - sb.WriteString(childFuncName) - sb.WriteString("(v.") - sb.WriteString(fieldName) - sb.WriteString(")") - case *gotype.ArrayType: - if _, ok := gotype.FindKnownTypePgx(fieldType.PgArray.OID()); ok { - sb.WriteString("v.") - sb.WriteString(fieldName) - break - } - sb.WriteString("tr.") - sb.WriteString(NameArrayRawFunc(fieldType)) - sb.WriteString("(v.") - sb.WriteString(fieldName) - sb.WriteString(")") - case *gotype.VoidType: - sb.WriteString("nil") - default: - sb.WriteString("v.") - sb.WriteString(fieldName) - } - sb.WriteString(",") - } - sb.WriteString("\n\t") - sb.WriteString("}") - sb.WriteString("\n") - sb.WriteString("}") - return sb.String(), nil + return "", nil } diff --git a/internal/codegen/golang/declarer_enum.go b/internal/codegen/golang/declarer_enum.go index 268b13e1..f1437340 100644 --- a/internal/codegen/golang/declarer_enum.go +++ b/internal/codegen/golang/declarer_enum.go @@ -80,43 +80,13 @@ func NewEnumTranscoderDeclarer(enum *gotype.EnumType) EnumTranscoderDeclarer { } func (e EnumTranscoderDeclarer) DedupeKey() string { - return "enum_decoder::" + e.typ.Name + return "type_resolver::" + e.typ.Name + "_01_transcoder" } func (e EnumTranscoderDeclarer) Declare(string) (string, error) { sb := &strings.Builder{} - funcName := NameEnumTranscoderFunc(e.typ) - - // Doc comment - sb.WriteString("// ") - sb.WriteString(funcName) - sb.WriteString(" creates a new pgtype.ValueTranscoder for the\n") - sb.WriteString("// Postgres enum type '") - sb.WriteString(e.typ.PgEnum.Name) - sb.WriteString("'.\n") - - // Function signature - sb.WriteString("func ") - sb.WriteString(funcName) - sb.WriteString("() pgtype.ValueTranscoder {\n\t") - - // NewEnumType call - sb.WriteString("return pgtype.NewEnumType(\n\t\t") - sb.WriteString(strconv.Quote(e.typ.PgEnum.Name)) - sb.WriteString(",\n\t\t") - sb.WriteString(`[]string{`) - for _, label := range e.typ.Labels { - sb.WriteString("\n\t\t\t") - sb.WriteString("string(") - sb.WriteString(label) - sb.WriteString("),") - } - sb.WriteString("\n\t\t") - sb.WriteString("},") - sb.WriteString("\n\t") + sb.WriteString("var _ = addTypeToRegister(") + sb.WriteString(strconv.Quote(pgTypeNameForLoadType(e.typ.PgEnum.Name))) sb.WriteString(")") - sb.WriteString("\n") - sb.WriteString("}") - return sb.String(), nil } diff --git a/internal/codegen/golang/gotype/known_types.go b/internal/codegen/golang/gotype/known_types.go index 8a813647..3322a6ba 100644 --- a/internal/codegen/golang/gotype/known_types.go +++ b/internal/codegen/golang/gotype/known_types.go @@ -94,7 +94,7 @@ var ( //nolint:gochecknoglobals var ( PgBool = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Bool", pg.Bool) - PgQChar = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.QChar", pg.QChar) + PgQChar = MustParseKnownType("byte", pg.QChar) PgName = MustParseKnownType("string", pg.Name) PgInt8 = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Int8", pg.Int8) PgInt2 = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Int2", pg.Int2) @@ -105,61 +105,61 @@ var ( PgTID = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.TID", pg.TID) PgXID = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.XID", pg.XID) PgCID = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.CID", pg.CID) - PgJSON = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.JSON", pg.JSON) + PgJSON = MustParseKnownType("*string", pg.JSON) PgPoint = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Point", pg.Point) PgLseg = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Lseg", pg.Lseg) PgPath = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Path", pg.Path) PgBox = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Box", pg.Box) PgPolygon = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Polygon", pg.Polygon) PgLine = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Line", pg.Line) - PgCIDR = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.CIDR", pg.CIDR) - PgCIDRArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.CIDRArray", pg.CIDRArray) + PgCIDR = MustParseKnownType("net/netip.Prefix", pg.CIDR) + PgCIDRArray = MustParseKnownType("[]net/netip.Prefix", pg.CIDRArray) PgFloat4 = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Float4", pg.Float4) PgFloat8 = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Float8", pg.Float8) - PgUnknown = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Unknown", pg.Unknown) + PgUnknown = MustParseKnownType("string", pg.Unknown) PgCircle = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Circle", pg.Circle) - PgMacaddr = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Macaddr", pg.Macaddr) - PgInet = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Inet", pg.Inet) - PgBoolArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.BoolArray", pg.BoolArray) - PgByteaArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.ByteaArray", pg.ByteaArray) - PgInt2Array = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Int2Array", pg.Int2Array) - PgInt4Array = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Int4Array", pg.Int4Array) - PgTextArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.TextArray", pg.TextArray) - PgBPCharArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.BPCharArray", pg.BPCharArray) - PgVarcharArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.VarcharArray", pg.VarcharArray) - PgInt8Array = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Int8Array", pg.Int8Array) - PgFloat4Array = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Float4Array", pg.Float4Array) - PgFloat8Array = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Float8Array", pg.Float8Array) - PgACLItem = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.ACLItem", pg.ACLItem) - PgACLItemArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.ACLItemArray", pg.ACLItemArray) - PgInetArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.InetArray", pg.InetArray) - PgMacaddrArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.MacaddrArray", pg.MacaddrArray) + PgMacaddr = MustParseKnownType("net.HardwareAddr", pg.Macaddr) + PgInet = MustParseKnownType("net/netip.Addr", pg.Inet) + PgBoolArray = MustParseKnownType("[]bool", pg.BoolArray) + PgByteaArray = MustParseKnownType("[][]byte", pg.ByteaArray) + PgInt2Array = MustParseKnownType("[]int16", pg.Int2Array) + PgInt4Array = MustParseKnownType("[]int32", pg.Int4Array) + PgTextArray = MustParseKnownType("[]string", pg.TextArray) + PgBPCharArray = MustParseKnownType("[]string", pg.BPCharArray) + PgVarcharArray = MustParseKnownType("[]string", pg.VarcharArray) + PgInt8Array = MustParseKnownType("[]int64", pg.Int8Array) + PgFloat4Array = MustParseKnownType("[]float32", pg.Float4Array) + PgFloat8Array = MustParseKnownType("[]float64", pg.Float8Array) + PgACLItem = MustParseKnownType("string", pg.ACLItem) + PgACLItemArray = MustParseKnownType("[]string", pg.ACLItemArray) + PgInetArray = MustParseKnownType("[]net/netip.Addr", pg.InetArray) + PgMacaddrArray = MustParseKnownType("[]net.HardwareAddr", pg.MacaddrArray) PgBPChar = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.BPChar", pg.BPChar) PgVarchar = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Varchar", pg.Varchar) PgDate = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Date", pg.Date) PgTime = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Time", pg.Time) PgTimestamp = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Timestamp", pg.Timestamp) - PgTimestampArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.TimestampArray", pg.TimestampArray) - PgDateArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.DateArray", pg.DateArray) + PgTimestampArray = MustParseKnownType("[]github.com/jackc/pgx/v5/pgtype.Timestamp", pg.TimestampArray) + PgDateArray = MustParseKnownType("[]github.com/jackc/pgx/v5/pgtype.Date", pg.DateArray) PgTimestamptz = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Timestamptz", pg.Timestamptz) - PgTimestamptzArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.TimestamptzArray", pg.TimestamptzArray) + PgTimestamptzArray = MustParseKnownType("[]github.com/jackc/pgx/v5/pgtype.Timestamptz", pg.TimestamptzArray) PgInterval = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Interval", pg.Interval) - PgNumericArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.NumericArray", pg.NumericArray) + PgNumericArray = MustParseKnownType("[]github.com/jackc/pgx/v5/pgtype.Numeric", pg.NumericArray) PgBit = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Bit", pg.Bit) PgVarbit = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Varbit", pg.Varbit) PgVoid = &VoidType{} PgNumeric = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Numeric", pg.Numeric) - PgRecord = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Record", pg.Record) + PgRecord = MustParseKnownType("any", pg.Record) PgUUID = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.UUID", pg.UUID) - PgUUIDArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.UUIDArray", pg.UUIDArray) - PgJSONB = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.JSONB", pg.JSONB) - PgJSONBArray = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.JSONBArray", pg.JSONBArray) - PgInt4range = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Int4range", pg.Int4range) - PgNumrange = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Numrange", pg.Numrange) - PgTsrange = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Tsrange", pg.Tsrange) - PgTstzrange = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Tstzrange", pg.Tstzrange) - PgDaterange = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Daterange", pg.Daterange) - PgInt8range = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Int8range", pg.Int8range) + PgUUIDArray = MustParseKnownType("[]github.com/jackc/pgx/v5/pgtype.UUID", pg.UUIDArray) + PgJSONB = MustParseKnownType("*string", pg.JSONB) + PgJSONBArray = MustParseKnownType("[]*string", pg.JSONBArray) + PgInt4range = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Range[int32]", pg.Int4range) + PgNumrange = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Range[float64]", pg.Numrange) + PgTsrange = MustParseKnownType("string", pg.Tsrange) + PgTstzrange = MustParseKnownType("string", pg.Tstzrange) + PgDaterange = MustParseKnownType("string", pg.Daterange) + PgInt8range = MustParseKnownType("github.com/jackc/pgx/v5/pgtype.Range[int64]", pg.Int8range) ) // knownGoType is the native pgtype type, the nullable and non-nullable types @@ -167,8 +167,7 @@ var ( // // pgNative means a type that implements the pgx decoder methods directly. // Such types are typically provided by the pgtype package. Used as the fallback -// type and for cases like composite types where we need a -// pgtype.ValueTranscoder. +// type and for cases where no native Go type is configured. // // A nullable type is one that can represent a nullable column, like *string for // a Postgres text type that can be null. A nullable type is nicer to work with diff --git a/internal/codegen/golang/import_set.go b/internal/codegen/golang/import_set.go index 274f225d..14cb2a8e 100644 --- a/internal/codegen/golang/import_set.go +++ b/internal/codegen/golang/import_set.go @@ -24,13 +24,20 @@ func (s *ImportSet) AddPackage(p string) { // AddType adds all fully qualified package paths needed for type and any child // types. func (s *ImportSet) AddType(typ gotype.Type) { - s.AddPackage(typ.Import()) - comp, ok := typ.(*gotype.CompositeType) - if !ok { - return - } - for _, childType := range comp.FieldTypes { - s.AddType(childType) + switch typ := typ.(type) { + case *gotype.ArrayType: + s.AddType(typ.Elem) + case *gotype.CompositeType: + for _, childType := range typ.FieldTypes { + s.AddType(childType) + } + case *gotype.ImportType: + s.AddPackage(typ.PkgPath) + s.AddType(typ.Type) + case *gotype.PointerType: + s.AddType(typ.Elem) + default: + s.AddPackage(typ.Import()) } } diff --git a/internal/codegen/golang/query.gotemplate b/internal/codegen/golang/query.gotemplate index d6c823f0..b1dafd3f 100644 --- a/internal/codegen/golang/query.gotemplate +++ b/internal/codegen/golang/query.gotemplate @@ -13,6 +13,8 @@ import ( {{- if .IsLeader -}} {{- "\n\n" -}} +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { {{- range $pkgFile := .Pkg.Files -}} @@ -27,7 +29,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -42,6 +44,7 @@ func NewQuerier(conn genericConn) *DBQuerier { return &DBQuerier{conn: conn} } +{{- range .Declarers}}{{- "\n\n" -}}{{ .Declare $.PkgPath }}{{ end -}} {{- end -}} {{- range $i, $q := .Queries -}} @@ -52,7 +55,7 @@ const {{ $q.SQLVarName }} = {{ $q.EmitPreparedSQL }} {{- "\n\n" -}} // {{ $q.Name }} implements Querier.{{ $q.Name }}. func (q *DBQuerier) {{ $q.Name }}(ctx context.Context {{- $q.EmitParams }}) ({{ $q.EmitResultType }}, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "{{ $q.Name }}") + ctx = context.WithValue(ctx, QueryName{}, "{{ $q.Name }}") {{- if eq $q.ResultKind ":exec" }} cmdTag, err := q.conn.Exec(ctx, {{ $q.SQLVarName }} {{- $q.EmitParamNames }}) if err != nil { @@ -64,77 +67,10 @@ func (q *DBQuerier) {{ $q.Name }}(ctx context.Context {{- $q.EmitParams }}) ({{ if err != nil { return {{ $q.EmitZeroResult }}, fmt.Errorf("query {{ $q.Name }}: %w", err) } - fds := rows.FieldDescriptions() - {{- range $i, $col := $q.Outputs }} - {{ $q.EmitPlanScan $i $col}} - {{- end }} - return {{ $q.EmitCollectionFunc }}(rows, func(row pgx.CollectableRow) ({{ $q.EmitSingularResultType }}, error) { - vals := row.RawValues() - var item {{ $q.EmitSingularResultType }} - {{- range $i, $col := $q.Outputs }} - {{ $q.EmitScanColumn $i $col }} - {{- end }} - return item, nil - }) + return {{ $q.EmitCollectionFunc }}(rows, {{ $q.EmitRowMapper }}) {{- end }} } {{- end -}} {{- "\n" -}} -{{ if .IsLeader }} -type scanCacheKey struct { - oid uint32 - format int16 - typeName string -} - -var ( - plans = make(map[scanCacheKey]pgtype.ScanPlan, 16) - plansMu sync.RWMutex -) - -func planScan(codec pgtype.Codec, fd pgconn.FieldDescription, target any) pgtype.ScanPlan { - key := scanCacheKey{fd.DataTypeOID, fd.Format, fmt.Sprintf("%T", target)} - plansMu.RLock() - plan := plans[key] - plansMu.RUnlock() - if plan != nil { - return plan - } - plan = codec.PlanScan(nil, fd.DataTypeOID, fd.Format, target) - plansMu.Lock() - plans[key] = plan - plansMu.Unlock() - return plan -} - -type ptrScanner[T any] struct { - basePlan pgtype.ScanPlan -} - -func (s ptrScanner[T]) Scan(src []byte, dst any) error { - if src == nil { - return nil - } - d := dst.(**T) - *d = new(T) - return s.basePlan.Scan(src, *d) -} - -func planPtrScan[T any](codec pgtype.Codec, fd pgconn.FieldDescription, target *T) pgtype.ScanPlan { - key := scanCacheKey{fd.DataTypeOID, fd.Format, fmt.Sprintf("*%T", target)} - plansMu.RLock() - plan := plans[key] - plansMu.RUnlock() - if plan != nil { - return plan - } - basePlan := planScan(codec, fd, target) - ptrPlan := ptrScanner[T]{basePlan} - plansMu.Lock() - plans[key] = plan - plansMu.Unlock() - return ptrPlan -} -{{- end -}} {{- end -}} diff --git a/internal/codegen/golang/templated_file.go b/internal/codegen/golang/templated_file.go index 5c40a856..674eadeb 100644 --- a/internal/codegen/golang/templated_file.go +++ b/internal/codegen/golang/templated_file.go @@ -153,46 +153,20 @@ func (tq TemplatedQuery) EmitParamStruct() string { // EmitParamNames emits the TemplatedQuery.Inputs into comma separated names // for use in a method invocation. func (tq TemplatedQuery) EmitParamNames() string { - appendParam := func(sb *strings.Builder, typ gotype.Type, name string) { - switch typ := gotype.UnwrapNestedType(typ).(type) { - case *gotype.CompositeType: - sb.WriteString("q.types.") - sb.WriteString(NameCompositeInitFunc(typ)) - sb.WriteString("(") - sb.WriteString(name) - sb.WriteString(")") - case *gotype.ArrayType: - if gotype.IsPgxSupportedArray(typ) { - sb.WriteString(name) - break - } - switch gotype.UnwrapNestedType(typ.Elem).(type) { - case *gotype.CompositeType, *gotype.EnumType: - sb.WriteString("q.types.") - sb.WriteString(NameArrayInitFunc(typ)) - sb.WriteString("(") - sb.WriteString(name) - sb.WriteString(")") - default: - sb.WriteString(name) - } - default: - sb.WriteString(name) - } - } switch { case tq.isInlineParams(): sb := &strings.Builder{} for _, input := range tq.Inputs { sb.WriteString(", ") - appendParam(sb, input.Type, input.LowerName) + sb.WriteString(input.LowerName) } return sb.String() default: sb := &strings.Builder{} for _, input := range tq.Inputs { sb.WriteString(", ") - appendParam(sb, input.Type, "params."+input.UpperName) + sb.WriteString("params.") + sb.WriteString(input.UpperName) } return sb.String() } @@ -296,6 +270,58 @@ func (tq TemplatedQuery) EmitCollectionFunc() (string, error) { } } +func (tq TemplatedQuery) EmitRowToFunc() (string, error) { + switch tq.ResultKind { + case ast.ResultKindExec: + return "", fmt.Errorf("cannot EmitRowToFunc for :exec query %s", tq.Name) + case ast.ResultKindMany, ast.ResultKindOne: + if len(tq.Outputs) == 1 { + return "pgx.RowTo", nil + } + return "pgx.RowToStructByName", nil + default: + return "", fmt.Errorf("unhandled EmitRowToFunc type: %s", tq.ResultKind) + } +} + +func (tq TemplatedQuery) EmitRowMapper() (string, error) { + rowToFunc, err := tq.EmitRowToFunc() + if err != nil { + return "", err + } + resultType := tq.EmitSingularResultType() + if len(tq.ScanCols) == len(tq.Outputs) { + return fmt.Sprintf("%s[%s]", rowToFunc, resultType), nil + } + scanTargets, err := tq.EmitScanTargets() + if err != nil { + return "", err + } + return fmt.Sprintf(`func(row pgx.CollectableRow) (%s, error) { + var item %s + if err := row.Scan(%s); err != nil { + return item, err + } + return item, nil +}`, resultType, resultType, scanTargets), nil +} + +func (tq TemplatedQuery) EmitScanTargets() (string, error) { + targets := make([]string, 0, len(tq.ScanCols)) + for _, col := range tq.ScanCols { + if _, ok := gotype.UnwrapNestedType(col.Type).(*gotype.VoidType); ok { + targets = append(targets, "nil") + continue + } + if len(tq.Outputs) == 1 { + targets = append(targets, "&item") + continue + } + targets = append(targets, "&item."+col.UpperName) + } + return strings.Join(targets, ", "), nil +} + // EmitSingularResultType returns the string representing a single element // of the overall query result type, like FindAuthorsRow when the overall return // type is []FindAuthorsRow. @@ -384,15 +410,18 @@ func (tq TemplatedQuery) EmitZeroResult() (string, error) { case strings.HasPrefix(typ, "*"): return "nil", nil // nil pointer } + if _, ok := gotype.UnwrapNestedType(tq.Outputs[0].Type).(*gotype.EnumType); ok { + return `""`, nil + } switch typ { - case "int", "int32", "int64", "float32", "float64": + case "int", "int32", "int64", "float32", "float64", "uint", "uint32", "uint64": return "0", nil case "string": return `""`, nil case "bool": return "false", nil default: - return typ + "{}", nil // won't work for type Foo int + return tq.Outputs[0].QualType + "{}", nil // won't work for type Foo int } default: return "", fmt.Errorf("unhandled EmitZeroResult kind: %s", tq.ResultKind) @@ -477,6 +506,8 @@ func (tq TemplatedQuery) EmitRowStruct() string { sb.WriteString(strings.Repeat(" ", maxTypeLen-len(out.QualType))) sb.WriteString("`json:") sb.WriteString(strconv.Quote(out.PgName)) + sb.WriteString(" db:") + sb.WriteString(strconv.Quote(out.PgName)) sb.WriteString("`") sb.WriteRune('\n') } diff --git a/internal/codegen/golang/templater.go b/internal/codegen/golang/templater.go index 5c050936..7cef961e 100644 --- a/internal/codegen/golang/templater.go +++ b/internal/codegen/golang/templater.go @@ -117,7 +117,6 @@ func (tm Templater) templateFile(file codegen.QueryFile, isLeader bool) (Templat imports.AddPackage("fmt") imports.AddPackage("github.com/jackc/pgx/v5/pgconn") if isLeader { - imports.AddPackage("github.com/jackc/pgx/v5/pgtype") imports.AddPackage("github.com/jackc/pgx/v5") } @@ -190,6 +189,9 @@ func (tm Templater) templateFile(file codegen.QueryFile, isLeader bool) (Templat if len(nonVoidCols) == 0 { resultKind = ast.ResultKindExec } + if resultKind != ast.ResultKindExec { + imports.AddPackage("github.com/jackc/pgx/v5") + } queries = append(queries, TemplatedQuery{ Name: tm.caser.ToUpperGoIdent(query.Name), SQLVarName: tm.caser.ToLowerGoIdent(query.Name) + "SQL", diff --git a/internal/codegen/golang/testdata/declarer_composite.input.golden b/internal/codegen/golang/testdata/declarer_composite.input.golden index 8cfddeb6..690e99a7 100644 --- a/internal/codegen/golang/testdata/declarer_composite.input.golden +++ b/internal/codegen/golang/testdata/declarer_composite.input.golden @@ -1,110 +1,46 @@ // SomeTable represents the Postgres composite type "some_table". type SomeTable struct { - Foo int16 `json:"foo"` - BarBaz pgtype.Text `json:"bar_baz"` -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + Foo int16 `json:"foo" db:"foo"` + BarBaz pgtype.Text `json:"bar_baz" db:"bar_baz"` +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} +var typesToRegister = []string{} -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -// newSomeTable creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'some_table'. -func (tr *typeResolver) newSomeTable() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "some_table", - compositeField{name: "foo", typeName: "int2", defaultVal: &pgtype.Int2{}}, - compositeField{name: "bar_baz", typeName: "text", defaultVal: &pgtype.Text{}}, - ) -} +var _ = addTypeToRegister("\"some_table\"") + -// newSomeTableInit creates an initialized pgtype.ValueTranscoder for the -// Postgres composite type 'some_table' to encode query parameters. -func (tr *typeResolver) newSomeTableInit(v SomeTable) pgtype.ValueTranscoder { - return tr.setValue(tr.newSomeTable(), tr.newSomeTableRaw(v)) -} -// newSomeTableRaw returns all composite fields for the Postgres composite -// type 'some_table' as a slice of interface{} to encode query parameters. -func (tr *typeResolver) newSomeTableRaw(v SomeTable) []interface{} { - return []interface{}{ - v.Foo, - v.BarBaz, - } -} \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_composite.output.golden b/internal/codegen/golang/testdata/declarer_composite.output.golden index 8c697bf3..49f809b6 100644 --- a/internal/codegen/golang/testdata/declarer_composite.output.golden +++ b/internal/codegen/golang/testdata/declarer_composite.output.golden @@ -1,95 +1,43 @@ // SomeTable represents the Postgres composite type "some_table". type SomeTable struct { - Foo int16 `json:"foo"` - BarBaz pgtype.Text `json:"bar_baz"` -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + Foo int16 `json:"foo" db:"foo"` + BarBaz pgtype.Text `json:"bar_baz" db:"bar_baz"` +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} +var typesToRegister = []string{} -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -// newSomeTable creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'some_table'. -func (tr *typeResolver) newSomeTable() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "some_table", - compositeField{name: "foo", typeName: "int2", defaultVal: &pgtype.Int2{}}, - compositeField{name: "bar_baz", typeName: "text", defaultVal: &pgtype.Text{}}, - ) -} \ No newline at end of file +var _ = addTypeToRegister("\"some_table\"") \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_composite_array.input.golden b/internal/codegen/golang/testdata/declarer_composite_array.input.golden index 887fbca1..6c4e9a20 100644 --- a/internal/codegen/golang/testdata/declarer_composite_array.input.golden +++ b/internal/codegen/golang/testdata/declarer_composite_array.input.golden @@ -1,130 +1,50 @@ // SomeTable represents the Postgres composite type "some_table". type SomeTable struct { - Foo int16 `json:"foo"` - BarBaz pgtype.Text `json:"bar_baz"` + Foo int16 `json:"foo" db:"foo"` + BarBaz pgtype.Text `json:"bar_baz" db:"bar_baz"` +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining + } + return nil } -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} +var typesToRegister = []string{} -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false - } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true -} -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use -} +var _ = addTypeToRegister("\"some_table\"") -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -// newSomeTable creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'some_table'. -func (tr *typeResolver) newSomeTable() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "some_table", - compositeField{name: "foo", typeName: "int2", defaultVal: &pgtype.Int2{}}, - compositeField{name: "bar_baz", typeName: "text", defaultVal: &pgtype.Text{}}, - ) -} +var _ = addTypeToRegister("\"_some_array\"") -// newSomeTableRaw returns all composite fields for the Postgres composite -// type 'some_table' as a slice of interface{} to encode query parameters. -func (tr *typeResolver) newSomeTableRaw(v SomeTable) []interface{} { - return []interface{}{ - v.Foo, - v.BarBaz, - } -} - -// newSomeTableArray creates a new pgtype.ValueTranscoder for the Postgres -// '_some_array' array type. -func (tr *typeResolver) newSomeTableArray() pgtype.ValueTranscoder { - return tr.newArrayValue("_some_array", "some_table", tr.newSomeTable) -} -// newSomeTableArrayInit creates an initialized pgtype.ValueTranscoder for the -// Postgres array type '_some_array' to encode query parameters. -func (tr *typeResolver) newSomeTableArrayInit(ps []SomeTable) pgtype.ValueTranscoder { - dec := tr.newSomeTableArray() - if err := dec.Set(tr.newSomeTableArrayRaw(ps)); err != nil { - panic("encode []SomeTable: " + err.Error()) // should always succeed - } - return textPreferrer{ValueTranscoder: dec, typeName: "_some_array"} -} -// newSomeTableArrayRaw returns all elements for the Postgres array type '_some_array' -// as a slice of interface{} for use with the pgtype.Value Set method. -func (tr *typeResolver) newSomeTableArrayRaw(vs []SomeTable) []interface{} { - elems := make([]interface{}, len(vs)) - for i, v := range vs { - elems[i] = tr.newSomeTableRaw(v) - } - return elems -} \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_composite_array.output.golden b/internal/codegen/golang/testdata/declarer_composite_array.output.golden index 9947597c..31714f83 100644 --- a/internal/codegen/golang/testdata/declarer_composite_array.output.golden +++ b/internal/codegen/golang/testdata/declarer_composite_array.output.golden @@ -1,101 +1,45 @@ // SomeTable represents the Postgres composite type "some_table". type SomeTable struct { - Foo int16 `json:"foo"` - BarBaz pgtype.Text `json:"bar_baz"` -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + Foo int16 `json:"foo" db:"foo"` + BarBaz pgtype.Text `json:"bar_baz" db:"bar_baz"` +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} +var typesToRegister = []string{} -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -// newSomeTable creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'some_table'. -func (tr *typeResolver) newSomeTable() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "some_table", - compositeField{name: "foo", typeName: "int2", defaultVal: &pgtype.Int2{}}, - compositeField{name: "bar_baz", typeName: "text", defaultVal: &pgtype.Text{}}, - ) -} +var _ = addTypeToRegister("\"some_table\"") -// newSomeTableArray creates a new pgtype.ValueTranscoder for the Postgres -// '_some_array' array type. -func (tr *typeResolver) newSomeTableArray() pgtype.ValueTranscoder { - return tr.newArrayValue("_some_array", "some_table", tr.newSomeTable) -} \ No newline at end of file +var _ = addTypeToRegister("\"_some_array\"") \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_composite_enum.input.golden b/internal/codegen/golang/testdata/declarer_composite_enum.input.golden index 567b2626..12afb781 100644 --- a/internal/codegen/golang/testdata/declarer_composite_enum.input.golden +++ b/internal/codegen/golang/testdata/declarer_composite_enum.input.golden @@ -1,18 +1,6 @@ // SomeTableEnum represents the Postgres composite type "some_table_enum". type SomeTableEnum struct { - Foo DeviceType `json:"foo"` -} - -// newDeviceTypeEnum creates a new pgtype.ValueTranscoder for the -// Postgres enum type 'device_type'. -func newDeviceTypeEnum() pgtype.ValueTranscoder { - return pgtype.NewEnumType( - "device_type", - []string{ - string(DeviceTypeIOS), - string(DeviceTypeMobile), - }, - ) + Foo DeviceType `json:"foo" db:"foo"` } // DeviceType represents the Postgres enum "device_type". @@ -25,105 +13,45 @@ const ( func (d DeviceType) String() string { return string(d) } -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining + } + return nil } -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} +var typesToRegister = []string{} -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false - } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use -} -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} +var _ = addTypeToRegister("\"device_type\"") -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} +var _ = addTypeToRegister("\"some_table_enum\"") -// newSomeTableEnum creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'some_table_enum'. -func (tr *typeResolver) newSomeTableEnum() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "some_table_enum", - compositeField{name: "foo", typeName: "some_table_enum", defaultVal: newDeviceTypeEnum()}, - ) -} -// newSomeTableEnumInit creates an initialized pgtype.ValueTranscoder for the -// Postgres composite type 'some_table_enum' to encode query parameters. -func (tr *typeResolver) newSomeTableEnumInit(v SomeTableEnum) pgtype.ValueTranscoder { - return tr.setValue(tr.newSomeTableEnum(), tr.newSomeTableEnumRaw(v)) -} -// newSomeTableEnumRaw returns all composite fields for the Postgres composite -// type 'some_table_enum' as a slice of interface{} to encode query parameters. -func (tr *typeResolver) newSomeTableEnumRaw(v SomeTableEnum) []interface{} { - return []interface{}{ - v.Foo, - } -} \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_composite_enum.output.golden b/internal/codegen/golang/testdata/declarer_composite_enum.output.golden index a0daad58..5dcdb847 100644 --- a/internal/codegen/golang/testdata/declarer_composite_enum.output.golden +++ b/internal/codegen/golang/testdata/declarer_composite_enum.output.golden @@ -1,18 +1,6 @@ // SomeTableEnum represents the Postgres composite type "some_table_enum". type SomeTableEnum struct { - Foo DeviceType `json:"foo"` -} - -// newDeviceTypeEnum creates a new pgtype.ValueTranscoder for the -// Postgres enum type 'device_type'. -func newDeviceTypeEnum() pgtype.ValueTranscoder { - return pgtype.NewEnumType( - "device_type", - []string{ - string(DeviceTypeIOS), - string(DeviceTypeMobile), - }, - ) + Foo DeviceType `json:"foo" db:"foo"` } // DeviceType represents the Postgres enum "device_type". @@ -25,91 +13,42 @@ const ( func (d DeviceType) String() string { return string(d) } -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} +var typesToRegister = []string{} -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -// newSomeTableEnum creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'some_table_enum'. -func (tr *typeResolver) newSomeTableEnum() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "some_table_enum", - compositeField{name: "foo", typeName: "some_table_enum", defaultVal: newDeviceTypeEnum()}, - ) -} \ No newline at end of file +var _ = addTypeToRegister("\"device_type\"") + +var _ = addTypeToRegister("\"some_table_enum\"") \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_composite_nested.input.golden b/internal/codegen/golang/testdata/declarer_composite_nested.input.golden index 058642fd..034a967e 100644 --- a/internal/codegen/golang/testdata/declarer_composite_nested.input.golden +++ b/internal/codegen/golang/testdata/declarer_composite_nested.input.golden @@ -1,132 +1,55 @@ // FooType represents the Postgres composite type "foo_type". type FooType struct { - Alpha pgtype.Text `json:"alpha"` + Alpha pgtype.Text `json:"alpha" db:"alpha"` } // SomeTableNested represents the Postgres composite type "some_table_nested". type SomeTableNested struct { - Foo FooType `json:"foo"` - BarBaz pgtype.Text `json:"bar_baz"` + Foo FooType `json:"foo" db:"foo"` + BarBaz pgtype.Text `json:"bar_baz" db:"bar_baz"` +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining + } + return nil } -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} +var typesToRegister = []string{} -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false - } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true -} -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} - -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use -} -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} +var _ = addTypeToRegister("\"foo_type\"") -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -// newFooType creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'foo_type'. -func (tr *typeResolver) newFooType() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "foo_type", - compositeField{name: "alpha", typeName: "text", defaultVal: &pgtype.Text{}}, - ) -} -// newFooTypeRaw returns all composite fields for the Postgres composite -// type 'foo_type' as a slice of interface{} to encode query parameters. -func (tr *typeResolver) newFooTypeRaw(v FooType) []interface{} { - return []interface{}{ - v.Alpha, - } -} +var _ = addTypeToRegister("\"some_table_nested\"") -// newSomeTableNested creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'some_table_nested'. -func (tr *typeResolver) newSomeTableNested() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "some_table_nested", - compositeField{name: "foo", typeName: "foo_type", defaultVal: tr.newFooType()}, - compositeField{name: "bar_baz", typeName: "text", defaultVal: &pgtype.Text{}}, - ) -} -// newSomeTableNestedInit creates an initialized pgtype.ValueTranscoder for the -// Postgres composite type 'some_table_nested' to encode query parameters. -func (tr *typeResolver) newSomeTableNestedInit(v SomeTableNested) pgtype.ValueTranscoder { - return tr.setValue(tr.newSomeTableNested(), tr.newSomeTableNestedRaw(v)) -} -// newSomeTableNestedRaw returns all composite fields for the Postgres composite -// type 'some_table_nested' as a slice of interface{} to encode query parameters. -func (tr *typeResolver) newSomeTableNestedRaw(v SomeTableNested) []interface{} { - return []interface{}{ - tr.newFooTypeRaw(v.Foo), - v.BarBaz, - } -} \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_composite_nested.output.golden b/internal/codegen/golang/testdata/declarer_composite_nested.output.golden index 12719532..c7658db8 100644 --- a/internal/codegen/golang/testdata/declarer_composite_nested.output.golden +++ b/internal/codegen/golang/testdata/declarer_composite_nested.output.golden @@ -1,109 +1,50 @@ // FooType represents the Postgres composite type "foo_type". type FooType struct { - Alpha pgtype.Text `json:"alpha"` + Alpha pgtype.Text `json:"alpha" db:"alpha"` } // SomeTableNested represents the Postgres composite type "some_table_nested". type SomeTableNested struct { - Foo FooType `json:"foo"` - BarBaz pgtype.Text `json:"bar_baz"` -} - -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name -} - -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} - -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false + Foo FooType `json:"foo" db:"foo"` + BarBaz pgtype.Text `json:"bar_baz" db:"bar_baz"` +} + +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true + return nil } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} +var typesToRegister = []string{} -type compositeField struct { - name string // name of the field - typeName string // Postgres type name - defaultVal pgtype.ValueTranscoder // default value to use +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - fs := make([]pgtype.CompositeTypeField, len(fields)) - vals := make([]pgtype.ValueTranscoder, len(fields)) - isBinaryOk := true - for i, field := range fields { - oid, val, ok := tr.findValue(field.typeName) - if !ok { - oid = unknownOID - val = field.defaultVal - } - isBinaryOk = isBinaryOk && oid != unknownOID - fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} - vals[i] = val - } - // Okay to ignore error because it's only thrown when the number of field - // names does not equal the number of ValueTranscoders. - typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) - if !isBinaryOk { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { - if _, val, ok := tr.findValue(name); ok { - return val - } - elemOID, elemVal, ok := tr.findValue(elemName) - elemValFunc := func() pgtype.ValueTranscoder { - return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) - } - if !ok { - elemOID = unknownOID - elemValFunc = defaultVal - } - typ := pgtype.NewArrayType(name, elemOID, elemValFunc) - if elemOID == unknownOID { - return textPreferrer{ValueTranscoder: typ, typeName: name} - } - return typ -} -// newFooType creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'foo_type'. -func (tr *typeResolver) newFooType() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "foo_type", - compositeField{name: "alpha", typeName: "text", defaultVal: &pgtype.Text{}}, - ) -} +var _ = addTypeToRegister("\"foo_type\"") -// newSomeTableNested creates a new pgtype.ValueTranscoder for the Postgres -// composite type 'some_table_nested'. -func (tr *typeResolver) newSomeTableNested() pgtype.ValueTranscoder { - return tr.newCompositeValue( - "some_table_nested", - compositeField{name: "foo", typeName: "foo_type", defaultVal: tr.newFooType()}, - compositeField{name: "bar_baz", typeName: "text", defaultVal: &pgtype.Text{}}, - ) -} \ No newline at end of file +var _ = addTypeToRegister("\"some_table_nested\"") \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_enum_escaping.input.golden b/internal/codegen/golang/testdata/declarer_enum_escaping.input.golden index 8b4b9c34..4e05f106 100644 --- a/internal/codegen/golang/testdata/declarer_enum_escaping.input.golden +++ b/internal/codegen/golang/testdata/declarer_enum_escaping.input.golden @@ -8,31 +8,38 @@ const ( func (q Quoting) String() string { return string(q) } -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining + } + return nil } -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} +var typesToRegister = []string{} -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false - } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} \ No newline at end of file +var _ = addTypeToRegister("\"quoting\"") \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_enum_escaping.output.golden b/internal/codegen/golang/testdata/declarer_enum_escaping.output.golden index 8b4b9c34..4e05f106 100644 --- a/internal/codegen/golang/testdata/declarer_enum_escaping.output.golden +++ b/internal/codegen/golang/testdata/declarer_enum_escaping.output.golden @@ -8,31 +8,38 @@ const ( func (q Quoting) String() string { return string(q) } -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining + } + return nil } -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} +var typesToRegister = []string{} -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false - } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} \ No newline at end of file +var _ = addTypeToRegister("\"quoting\"") \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_enum_simple.input.golden b/internal/codegen/golang/testdata/declarer_enum_simple.input.golden index a6bfe1ab..1ffeb7b8 100644 --- a/internal/codegen/golang/testdata/declarer_enum_simple.input.golden +++ b/internal/codegen/golang/testdata/declarer_enum_simple.input.golden @@ -8,31 +8,38 @@ const ( func (d DeviceType) String() string { return string(d) } -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining + } + return nil } -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} +var typesToRegister = []string{} -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false - } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} \ No newline at end of file +var _ = addTypeToRegister("\"device_type\"") \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_enum_simple.output.golden b/internal/codegen/golang/testdata/declarer_enum_simple.output.golden index a6bfe1ab..1ffeb7b8 100644 --- a/internal/codegen/golang/testdata/declarer_enum_simple.output.golden +++ b/internal/codegen/golang/testdata/declarer_enum_simple.output.golden @@ -8,31 +8,38 @@ const ( func (d DeviceType) String() string { return string(d) } -// typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. -type typeResolver struct { - connInfo *pgtype.ConnInfo // types by Postgres type name +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining + } + return nil } -func newTypeResolver() *typeResolver { - ci := pgtype.NewConnInfo() - return &typeResolver{connInfo: ci} -} +var typesToRegister = []string{} -// findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. -func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { - typ, ok := tr.connInfo.DataTypeForName(name) - if !ok { - return 0, nil, false - } - v := pgtype.NewValue(typ.Value) - return typ.OID, v.(pgtype.ValueTranscoder), true +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} } -// setValue sets the value of a ValueTranscoder to a value that should always -// work and panics if it fails. -func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { - if err := vt.Set(val); err != nil { - panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) - } - return vt -} \ No newline at end of file +var _ = addTypeToRegister("\"device_type\"") \ No newline at end of file diff --git a/internal/pg/query.sql.go b/internal/pg/query.sql.go index af7b16a3..815a8c72 100644 --- a/internal/pg/query.sql.go +++ b/internal/pg/query.sql.go @@ -9,6 +9,8 @@ import ( "github.com/jackc/pgx/v5/pgconn" ) +type QueryName struct{} + // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { FindEnumTypes(ctx context.Context, oids []uint32) ([]FindEnumTypesRow, error) @@ -34,7 +36,7 @@ type Querier interface { var _ Querier = &DBQuerier{} type DBQuerier struct { - conn genericConn // underlying Postgres transport to use + conn genericConn } // genericConn is a connection like *pgx.Conn, pgx.Tx, or *pgxpool.Pool. @@ -49,6 +51,42 @@ func NewQuerier(conn genericConn) *DBQuerier { return &DBQuerier{conn: conn} } +// RegisterTypes loads custom PostgreSQL types into conn's pgx type map. +func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { + pending := append([]string(nil), typesToRegister...) + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + var lastErr error + var lastType string + for _, typ := range pending { + dt, err := conn.LoadType(ctx, typ) + if err != nil { + lastErr = err + lastType = typ + remaining = append(remaining, typ) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return fmt.Errorf("load PostgreSQL type %q: %w", lastType, lastErr) + } + pending = remaining + } + return nil +} + +var typesToRegister = []string{} + +func addTypeToRegister(typ string) struct{} { + typesToRegister = append(typesToRegister, typ) + return struct{}{} +} + + + const findEnumTypesSQL = `WITH enums AS ( SELECT enumtypid::int8 AS enum_type, @@ -94,35 +132,24 @@ WHERE typ.typisdefined AND typ.oid = ANY ($1::oid[]);` type FindEnumTypesRow struct { - OID uint32 `json:"oid"` - TypeName string `json:"type_name"` - ChildOIDs []int `json:"child_oids"` - Orders []float32 `json:"orders"` - Labels []string `json:"labels"` - TypeKind byte `json:"type_kind"` - DefaultExpr string `json:"default_expr"` + OID uint32 `json:"oid" db:"oid"` + TypeName string `json:"type_name" db:"type_name"` + ChildOIDs []int `json:"child_oids" db:"child_oids"` + Orders []float32 `json:"orders" db:"orders"` + Labels []string `json:"labels" db:"labels"` + TypeKind byte `json:"type_kind" db:"type_kind"` + DefaultExpr string `json:"default_expr" db:"default_expr"` } // FindEnumTypes implements Querier.FindEnumTypes. func (q *DBQuerier) FindEnumTypes(ctx context.Context, oids []uint32) ([]FindEnumTypesRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindEnumTypes") + ctx = context.WithValue(ctx, QueryName{}, "FindEnumTypes") rows, err := q.conn.Query(ctx, findEnumTypesSQL, oids) if err != nil { return nil, fmt.Errorf("query FindEnumTypes: %w", err) } - defer rows.Close() - items := []FindEnumTypesRow{} - for rows.Next() { - var item FindEnumTypesRow - if err := rows.Scan(&item.OID, &item.TypeName, &item.ChildOIDs, &item.Orders, &item.Labels, &item.TypeKind, &item.DefaultExpr); err != nil { - return nil, fmt.Errorf("scan FindEnumTypes row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindEnumTypes rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowToStructByName[FindEnumTypesRow]) } const findArrayTypesSQL = `SELECT @@ -155,32 +182,21 @@ WHERE arr_typ.typisdefined AND arr_typ.oid = ANY ($1::oid[]);` type FindArrayTypesRow struct { - OID uint32 `json:"oid"` - TypeName string `json:"type_name"` - ElemOID uint32 `json:"elem_oid"` - TypeKind byte `json:"type_kind"` + OID uint32 `json:"oid" db:"oid"` + TypeName string `json:"type_name" db:"type_name"` + ElemOID uint32 `json:"elem_oid" db:"elem_oid"` + TypeKind byte `json:"type_kind" db:"type_kind"` } // FindArrayTypes implements Querier.FindArrayTypes. func (q *DBQuerier) FindArrayTypes(ctx context.Context, oids []uint32) ([]FindArrayTypesRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindArrayTypes") + ctx = context.WithValue(ctx, QueryName{}, "FindArrayTypes") rows, err := q.conn.Query(ctx, findArrayTypesSQL, oids) if err != nil { return nil, fmt.Errorf("query FindArrayTypes: %w", err) } - defer rows.Close() - items := []FindArrayTypesRow{} - for rows.Next() { - var item FindArrayTypesRow - if err := rows.Scan(&item.OID, &item.TypeName, &item.ElemOID, &item.TypeKind); err != nil { - return nil, fmt.Errorf("scan FindArrayTypes row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindArrayTypes rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowToStructByName[FindArrayTypesRow]) } const findCompositeTypesSQL = `WITH table_cols AS ( @@ -214,36 +230,25 @@ WHERE typ.oid = ANY ($1::oid[]) AND typ.typtype = 'c';` type FindCompositeTypesRow struct { - TableTypeName string `json:"table_type_name"` - TableTypeOID uint32 `json:"table_type_oid"` - TableName string `json:"table_name"` - ColNames []string `json:"col_names"` - ColOIDs []int `json:"col_oids"` - ColOrders []int `json:"col_orders"` - ColNotNulls []bool `json:"col_not_nulls"` - ColTypeNames []string `json:"col_type_names"` + TableTypeName string `json:"table_type_name" db:"table_type_name"` + TableTypeOID uint32 `json:"table_type_oid" db:"table_type_oid"` + TableName string `json:"table_name" db:"table_name"` + ColNames []string `json:"col_names" db:"col_names"` + ColOIDs []int `json:"col_oids" db:"col_oids"` + ColOrders []int `json:"col_orders" db:"col_orders"` + ColNotNulls []bool `json:"col_not_nulls" db:"col_not_nulls"` + ColTypeNames []string `json:"col_type_names" db:"col_type_names"` } // FindCompositeTypes implements Querier.FindCompositeTypes. func (q *DBQuerier) FindCompositeTypes(ctx context.Context, oids []uint32) ([]FindCompositeTypesRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindCompositeTypes") + ctx = context.WithValue(ctx, QueryName{}, "FindCompositeTypes") rows, err := q.conn.Query(ctx, findCompositeTypesSQL, oids) if err != nil { return nil, fmt.Errorf("query FindCompositeTypes: %w", err) } - defer rows.Close() - items := []FindCompositeTypesRow{} - for rows.Next() { - var item FindCompositeTypesRow - if err := rows.Scan(&item.TableTypeName, &item.TableTypeOID, &item.TableName, &item.ColNames, &item.ColOIDs, &item.ColOrders, &item.ColNotNulls, &item.ColTypeNames); err != nil { - return nil, fmt.Errorf("scan FindCompositeTypes row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindCompositeTypes rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowToStructByName[FindCompositeTypesRow]) } const findDescendantOIDsSQL = `WITH RECURSIVE oid_descs(oid) AS ( @@ -276,24 +281,13 @@ FROM oid_descs;` // FindDescendantOIDs implements Querier.FindDescendantOIDs. func (q *DBQuerier) FindDescendantOIDs(ctx context.Context, oids []uint32) ([]uint32, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindDescendantOIDs") + ctx = context.WithValue(ctx, QueryName{}, "FindDescendantOIDs") rows, err := q.conn.Query(ctx, findDescendantOIDsSQL, oids) if err != nil { return nil, fmt.Errorf("query FindDescendantOIDs: %w", err) } - defer rows.Close() - items := []uint32{} - for rows.Next() { - var item uint32 - if err := rows.Scan(&item); err != nil { - return nil, fmt.Errorf("scan FindDescendantOIDs row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindDescendantOIDs rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowTo[uint32]) } const findOIDByNameSQL = `SELECT oid @@ -304,13 +298,13 @@ LIMIT 1;` // FindOIDByName implements Querier.FindOIDByName. func (q *DBQuerier) FindOIDByName(ctx context.Context, name string) (uint32, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindOIDByName") - row := q.conn.QueryRow(ctx, findOIDByNameSQL, name) - var item uint32 - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query FindOIDByName: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "FindOIDByName") + rows, err := q.conn.Query(ctx, findOIDByNameSQL, name) + if err != nil { + return 0, fmt.Errorf("query FindOIDByName: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[uint32]) } const findOIDNameSQL = `SELECT typname AS name @@ -319,13 +313,13 @@ WHERE oid = $1;` // FindOIDName implements Querier.FindOIDName. func (q *DBQuerier) FindOIDName(ctx context.Context, oid uint32) (string, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindOIDName") - row := q.conn.QueryRow(ctx, findOIDNameSQL, oid) - var item string - if err := row.Scan(&item); err != nil { - return item, fmt.Errorf("query FindOIDName: %w", err) + ctx = context.WithValue(ctx, QueryName{}, "FindOIDName") + rows, err := q.conn.Query(ctx, findOIDNameSQL, oid) + if err != nil { + return "", fmt.Errorf("query FindOIDName: %w", err) } - return item, nil + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) } const findOIDNamesSQL = `SELECT oid, typname AS name, typtype AS kind @@ -333,29 +327,18 @@ FROM pg_type WHERE oid = ANY ($1::oid[]);` type FindOIDNamesRow struct { - OID uint32 `json:"oid"` - Name string `json:"name"` - Kind byte `json:"kind"` + OID uint32 `json:"oid" db:"oid"` + Name string `json:"name" db:"name"` + Kind byte `json:"kind" db:"kind"` } // FindOIDNames implements Querier.FindOIDNames. func (q *DBQuerier) FindOIDNames(ctx context.Context, oid []uint32) ([]FindOIDNamesRow, error) { - ctx = context.WithValue(ctx, "pggen_query_name", "FindOIDNames") + ctx = context.WithValue(ctx, QueryName{}, "FindOIDNames") rows, err := q.conn.Query(ctx, findOIDNamesSQL, oid) if err != nil { return nil, fmt.Errorf("query FindOIDNames: %w", err) } - defer rows.Close() - items := []FindOIDNamesRow{} - for rows.Next() { - var item FindOIDNamesRow - if err := rows.Scan(&item.OID, &item.Name, &item.Kind); err != nil { - return nil, fmt.Errorf("scan FindOIDNames row: %w", err) - } - items = append(items, item) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("close FindOIDNames rows: %w", err) - } - return items, err + + return pgx.CollectRows(rows, pgx.RowToStructByName[FindOIDNamesRow]) } diff --git a/internal/pg/type_fetcher.go b/internal/pg/type_fetcher.go index e081f34f..00cb0ab5 100644 --- a/internal/pg/type_fetcher.go +++ b/internal/pg/type_fetcher.go @@ -40,9 +40,7 @@ func (tf *TypeFetcher) FindTypesByOIDs(oids ...uint32) (map[uint32]Type, error) return nil, fmt.Errorf("find descendant oids: %w", err) } allOIDs := make([]uint32, len(descOIDs)) - for i, d := range descOIDs { - allOIDs[i] = uint32(d) - } + copy(allOIDs, descOIDs) types, uncached := tf.cache.getOIDs(allOIDs...) enums, err := tf.findEnumTypes(ctx, uncached) @@ -189,7 +187,7 @@ func (tf *TypeFetcher) findArrayTypes(ctx context.Context, uncached map[uint32]s } types := make([]ArrayType, len(rows)) for i, row := range rows { - elemType, ok := tf.cache.getOID(uint32(row.ElemOID)) + elemType, ok := tf.cache.getOID(row.ElemOID) if !ok { return nil, fmt.Errorf("find type for array elem %s oid=%d", row.TypeName, row.OID) } @@ -249,7 +247,7 @@ func (tf *TypeFetcher) resolvePlaceholderTypes(knownTypes map[uint32]Type) error func oidKeys(os map[uint32]struct{}) []uint32 { oids := make([]uint32, 0, len(os)) for oid := range os { - oids = append(oids, uint32(oid)) + oids = append(oids, oid) } return oids } diff --git a/internal/pg/type_fetcher_test.go b/internal/pg/type_fetcher_test.go index fef06a27..5f1900b4 100644 --- a/internal/pg/type_fetcher_test.go +++ b/internal/pg/type_fetcher_test.go @@ -195,7 +195,7 @@ func TestNewTypeFetcher(t *testing.T) { // Act. fetcher := NewTypeFetcher(conn) oid := findOIDVal(t, tt.fetchOID, querier) - gotTypeMap, err := fetcher.FindTypesByOIDs(uint32(oid)) + gotTypeMap, err := fetcher.FindTypesByOIDs(oid) if err != nil { t.Fatal(err) } diff --git a/internal/pginfer/pginfer.go b/internal/pginfer/pginfer.go index 4d65d93e..35f08758 100644 --- a/internal/pginfer/pginfer.go +++ b/internal/pginfer/pginfer.go @@ -157,7 +157,7 @@ func (inf *Inferrer) prepareTypes(query *ast.SourceQuery) (_a []InputParam, _ [] return nil, nil, fmt.Errorf("fetch oid types: %w", err) } for i, oid := range stmtDesc.ParamOIDs { - inputType, ok := types[uint32(oid)] + inputType, ok := types[oid] if !ok { return nil, nil, fmt.Errorf("no postgres type name found for parameter %s with oid %d", query.ParamNames[i], oid) } @@ -187,12 +187,12 @@ func (inf *Inferrer) prepareTypes(query *ast.SourceQuery) (_a []InputParam, _ [] // Create output columns var outputColumns []OutputColumn for i, desc := range stmtDesc.Fields { - pgType, ok := outputTypes[uint32(desc.DataTypeOID)] + pgType, ok := outputTypes[desc.DataTypeOID] if !ok { - return nil, nil, fmt.Errorf("no postgrestype name found for column %s with oid %d", string(desc.Name), desc.DataTypeOID) + return nil, nil, fmt.Errorf("no postgrestype name found for column %s with oid %d", desc.Name, desc.DataTypeOID) } outputColumns = append(outputColumns, OutputColumn{ - PgName: string(desc.Name), + PgName: desc.Name, PgType: pgType, Nullable: nullables[i], }) @@ -215,7 +215,7 @@ func (inf *Inferrer) inferOutputNullability(query *ast.SourceQuery, descs []pgco for i, desc := range descs { if desc.TableOID > 0 { columnKeys[i] = pg.ColumnKey{ - TableOID: uint32(desc.TableOID), + TableOID: desc.TableOID, Number: desc.TableAttributeNumber, } } diff --git a/internal/pgtest/pg_test_db.go b/internal/pgtest/pg_test_db.go index 3c236362..c850d69f 100644 --- a/internal/pgtest/pg_test_db.go +++ b/internal/pgtest/pg_test_db.go @@ -11,6 +11,7 @@ import ( "time" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" ) // CleanupFunc deletes the schema and all database objects. @@ -53,6 +54,12 @@ func NewPostgresSchemaString(t *testing.T, sql string, opts ...Option) (*pgx.Con if _, err := schemaConn.Exec(ctx, sql); err != nil { t.Fatalf("run sql: %s", err) } + if err := registerExtensionTypes(ctx, schemaConn); err != nil { + t.Fatalf("register extension types: %s", err) + } + if err := registerSchemaTypes(ctx, schemaConn); err != nil { + t.Fatalf("register schema types: %s", err) + } cleanup := func() { ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) @@ -70,6 +77,79 @@ func NewPostgresSchemaString(t *testing.T, sql string, opts ...Option) (*pgx.Con return schemaConn, cleanup } +func registerExtensionTypes(ctx context.Context, conn *pgx.Conn) error { + rows, err := conn.Query(ctx, ` +SELECT typname, oid +FROM pg_type +WHERE typname IN ('citext') + AND pg_type_is_visible(oid)`) + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var name string + var oid uint32 + if err := rows.Scan(&name, &oid); err != nil { + return err + } + conn.TypeMap().RegisterType(&pgtype.Type{ + Name: name, + OID: oid, + Codec: pgtype.TextCodec{}, + }) + } + return rows.Err() +} + +func registerSchemaTypes(ctx context.Context, conn *pgx.Conn) error { + rows, err := conn.Query(ctx, ` +SELECT format('%I.%I', nsp.nspname, typ.typname) +FROM pg_type typ +JOIN pg_namespace nsp ON nsp.oid = typ.typnamespace +WHERE typ.typnamespace = current_schema()::regnamespace + AND (typ.typtype IN ('c', 'd', 'e') OR (typ.typtype = 'b' AND typ.typelem <> 0 AND typ.typname LIKE '\_%')) +ORDER BY typ.typname`) + if err != nil { + return err + } + var typeNames []string + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + rows.Close() + return err + } + typeNames = append(typeNames, name) + } + if err := rows.Err(); err != nil { + rows.Close() + return err + } + rows.Close() + + pending := typeNames + for len(pending) > 0 { + remaining := pending[:0] + loaded := 0 + for _, name := range pending { + dt, err := conn.LoadType(ctx, name) + if err != nil { + remaining = append(remaining, name) + continue + } + conn.TypeMap().RegisterType(dt) + loaded++ + } + if loaded == 0 { + return nil + } + pending = remaining + } + return nil +} + func postgresConnString() string { if connStr := os.Getenv("PGURL"); connStr != "" { return connStr From ebeee45a92af65b87a3bb2cc307454e4428216c6 Mon Sep 17 00:00:00 2001 From: meoyawn Date: Wed, 6 May 2026 19:01:13 +0000 Subject: [PATCH 10/27] Restore generated type registration test coverage --- example/complex_params/query.sql_test.go | 6 ++ example/composite/query.sql_test.go | 23 ++++++ example/device/query.sql_test.go | 3 + example/enums/query.sql_test.go | 5 ++ example/function/query.sql_test.go | 1 + example/nested/query.sql_test.go | 3 + .../separate_out_dir/out/query.sql_test.go | 8 ++ example/syntax/query.sql_test.go | 8 +- internal/pgtest/pg_test_db.go | 81 ------------------- 9 files changed, 56 insertions(+), 82 deletions(-) diff --git a/example/complex_params/query.sql_test.go b/example/complex_params/query.sql_test.go index 64616694..42327d16 100644 --- a/example/complex_params/query.sql_test.go +++ b/example/complex_params/query.sql_test.go @@ -11,6 +11,7 @@ import ( func TestNewQuerier_ParamArrayInt(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) ctx := t.Context() @@ -27,6 +28,7 @@ func TestNewQuerier_ParamArrayInt(t *testing.T) { func TestNewQuerier_ParamNested1(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) ctx := t.Context() @@ -43,6 +45,7 @@ func TestNewQuerier_ParamNested1(t *testing.T) { func TestNewQuerier_ParamNested2(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) ctx := t.Context() @@ -62,6 +65,7 @@ func TestNewQuerier_ParamNested2(t *testing.T) { func TestNewQuerier_ParamNested2Array(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) ctx := t.Context() @@ -81,6 +85,7 @@ func TestNewQuerier_ParamNested2Array(t *testing.T) { func TestNewQuerier_ParamNested3(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) ctx := t.Context() @@ -105,6 +110,7 @@ func TestNewQuerier_ParamNested3_QueryAllDataTypes(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() ctx := t.Context() + require.NoError(t, RegisterTypes(ctx, conn)) // dataTypes, err := QueryAllDataTypes(ctx, conn) // require.NoError(t, err) q := NewQuerier(conn) diff --git a/example/composite/query.sql_test.go b/example/composite/query.sql_test.go index 1724785a..1da4ea88 100644 --- a/example/composite/query.sql_test.go +++ b/example/composite/query.sql_test.go @@ -3,6 +3,7 @@ package composite import ( "testing" + "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" "github.com/jschaf/pggen/internal/difftest" "github.com/jschaf/pggen/internal/pgtest" @@ -14,6 +15,8 @@ import ( func TestNewQuerier_SearchScreenshots(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + registerCitext(t, conn) + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) screenshotID := 99 @@ -61,6 +64,8 @@ func TestNewQuerier_SearchScreenshots(t *testing.T) { func TestNewQuerier_ArraysInput(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + registerCitext(t, conn) + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) @@ -80,6 +85,8 @@ func TestNewQuerier_ArraysInput(t *testing.T) { func TestNewQuerier_UserEmails(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + registerCitext(t, conn) + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) @@ -98,3 +105,19 @@ func insertScreenshotBlock(t *testing.T, q *DBQuerier, screenID int, body string require.NoError(t, err, "insert screenshot blocks") return row } + +func registerCitext(t *testing.T, conn *pgx.Conn) { + t.Helper() + row := conn.QueryRow(t.Context(), ` +SELECT oid +FROM pg_type +WHERE typname = 'citext' + AND pg_type_is_visible(oid)`) + var oid uint32 + require.NoError(t, row.Scan(&oid)) + conn.TypeMap().RegisterType(&pgtype.Type{ + Name: "citext", + OID: oid, + Codec: pgtype.TextCodec{}, + }) +} diff --git a/example/device/query.sql_test.go b/example/device/query.sql_test.go index b0d87518..571ac386 100644 --- a/example/device/query.sql_test.go +++ b/example/device/query.sql_test.go @@ -12,6 +12,7 @@ import ( func TestQuerier_FindDevicesByUser(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) ctx := t.Context() userID := 18 @@ -38,6 +39,7 @@ func TestQuerier_FindDevicesByUser(t *testing.T) { func TestQuerier_CompositeUser(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) ctx := t.Context() @@ -75,6 +77,7 @@ func TestQuerier_CompositeUser(t *testing.T) { func TestQuerier_CompositeUserOne(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) ctx := t.Context() id := 15 diff --git a/example/enums/query.sql_test.go b/example/enums/query.sql_test.go index 70d9d906..ae39ba95 100644 --- a/example/enums/query.sql_test.go +++ b/example/enums/query.sql_test.go @@ -16,6 +16,7 @@ func TestNewQuerier_FindAllDevices(t *testing.T) { defer cancel() conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(ctx, conn)) q := NewQuerier(conn) mac, _ := net.ParseMAC("00:00:5e:00:53:01") @@ -49,6 +50,7 @@ func TestNewQuerier_FindOneDeviceArray(t *testing.T) { defer cancel() conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(ctx, conn)) q := NewQuerier(conn) @@ -64,6 +66,7 @@ func TestNewQuerier_FindManyDeviceArray(t *testing.T) { defer cancel() conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(ctx, conn)) q := NewQuerier(conn) @@ -79,6 +82,7 @@ func TestNewQuerier_FindManyDeviceArrayWithNum(t *testing.T) { defer cancel() conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(ctx, conn)) q := NewQuerier(conn) one, two := int32(1), int32(2) @@ -98,6 +102,7 @@ func TestNewQuerier_EnumInsideComposite(t *testing.T) { defer cancel() conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(ctx, conn)) q := NewQuerier(conn) mac, _ := net.ParseMAC("08:00:2b:01:02:03") diff --git a/example/function/query.sql_test.go b/example/function/query.sql_test.go index 4e290263..07ef3b58 100644 --- a/example/function/query.sql_test.go +++ b/example/function/query.sql_test.go @@ -13,6 +13,7 @@ import ( func TestNewQuerier_OutParams(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) diff --git a/example/nested/query.sql_test.go b/example/nested/query.sql_test.go index 4eeb4b7c..8d3612d0 100644 --- a/example/nested/query.sql_test.go +++ b/example/nested/query.sql_test.go @@ -5,11 +5,13 @@ import ( "github.com/jschaf/pggen/internal/pgtest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewQuerier_ArrayNested2(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) ctx := t.Context() @@ -30,6 +32,7 @@ func TestNewQuerier_ArrayNested2(t *testing.T) { func TestNewQuerier_Nested3(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) ctx := t.Context() diff --git a/example/separate_out_dir/out/query.sql_test.go b/example/separate_out_dir/out/query.sql_test.go index c90e72fc..2e3db92d 100644 --- a/example/separate_out_dir/out/query.sql_test.go +++ b/example/separate_out_dir/out/query.sql_test.go @@ -12,6 +12,7 @@ import ( func TestNewQuerier_FindAuthorByID(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"../schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) @@ -27,6 +28,13 @@ func TestNewQuerier_FindAuthorByID(t *testing.T) { assert.Equal(t, "alpha", got) }) + t.Run("AlphaCompositeArray", func(t *testing.T) { + key := "key" + got, err := q.AlphaCompositeArray(t.Context()) + require.NoError(t, err) + assert.Equal(t, []Alpha{{Key: &key}}, got) + }) + t.Run("Bravo", func(t *testing.T) { got, err := q.Bravo(t.Context()) require.NoError(t, err) diff --git a/example/syntax/query.sql_test.go b/example/syntax/query.sql_test.go index 79041464..46676caa 100644 --- a/example/syntax/query.sql_test.go +++ b/example/syntax/query.sql_test.go @@ -5,11 +5,13 @@ import ( "github.com/jschaf/pggen/internal/pgtest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestQuerier(t *testing.T) { - conn, cleanup := pgtest.NewPostgresSchema(t, nil) + conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() + require.NoError(t, RegisterTypes(t.Context(), conn)) q := NewQuerier(conn) ctx := t.Context() @@ -32,4 +34,8 @@ func TestQuerier(t *testing.T) { val, err = q.BacktickBackslashN(ctx) assert.NoError(t, err, "BacktickBackslashN") assert.Equal(t, "`\\n", val, "BacktickBackslashN") + + enumVal, err := q.BadEnumName(ctx) + assert.NoError(t, err, "BadEnumName") + assert.Equal(t, UnnamedEnum123InconvertibleEnumName, enumVal, "BadEnumName") } diff --git a/internal/pgtest/pg_test_db.go b/internal/pgtest/pg_test_db.go index c850d69f..08703035 100644 --- a/internal/pgtest/pg_test_db.go +++ b/internal/pgtest/pg_test_db.go @@ -11,7 +11,6 @@ import ( "time" "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgtype" ) // CleanupFunc deletes the schema and all database objects. @@ -54,13 +53,6 @@ func NewPostgresSchemaString(t *testing.T, sql string, opts ...Option) (*pgx.Con if _, err := schemaConn.Exec(ctx, sql); err != nil { t.Fatalf("run sql: %s", err) } - if err := registerExtensionTypes(ctx, schemaConn); err != nil { - t.Fatalf("register extension types: %s", err) - } - if err := registerSchemaTypes(ctx, schemaConn); err != nil { - t.Fatalf("register schema types: %s", err) - } - cleanup := func() { ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() @@ -77,79 +69,6 @@ func NewPostgresSchemaString(t *testing.T, sql string, opts ...Option) (*pgx.Con return schemaConn, cleanup } -func registerExtensionTypes(ctx context.Context, conn *pgx.Conn) error { - rows, err := conn.Query(ctx, ` -SELECT typname, oid -FROM pg_type -WHERE typname IN ('citext') - AND pg_type_is_visible(oid)`) - if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var name string - var oid uint32 - if err := rows.Scan(&name, &oid); err != nil { - return err - } - conn.TypeMap().RegisterType(&pgtype.Type{ - Name: name, - OID: oid, - Codec: pgtype.TextCodec{}, - }) - } - return rows.Err() -} - -func registerSchemaTypes(ctx context.Context, conn *pgx.Conn) error { - rows, err := conn.Query(ctx, ` -SELECT format('%I.%I', nsp.nspname, typ.typname) -FROM pg_type typ -JOIN pg_namespace nsp ON nsp.oid = typ.typnamespace -WHERE typ.typnamespace = current_schema()::regnamespace - AND (typ.typtype IN ('c', 'd', 'e') OR (typ.typtype = 'b' AND typ.typelem <> 0 AND typ.typname LIKE '\_%')) -ORDER BY typ.typname`) - if err != nil { - return err - } - var typeNames []string - for rows.Next() { - var name string - if err := rows.Scan(&name); err != nil { - rows.Close() - return err - } - typeNames = append(typeNames, name) - } - if err := rows.Err(); err != nil { - rows.Close() - return err - } - rows.Close() - - pending := typeNames - for len(pending) > 0 { - remaining := pending[:0] - loaded := 0 - for _, name := range pending { - dt, err := conn.LoadType(ctx, name) - if err != nil { - remaining = append(remaining, name) - continue - } - conn.TypeMap().RegisterType(dt) - loaded++ - } - if loaded == 0 { - return nil - } - pending = remaining - } - return nil -} - func postgresConnString() string { if connStr := os.Getenv("PGURL"); connStr != "" { return connStr From 8b76a4d96d02afbdcd60e77f193e498bdc0965ec Mon Sep 17 00:00:00 2001 From: meoyawn Date: Wed, 6 May 2026 19:16:03 +0000 Subject: [PATCH 11/27] Add pgx ltree array override coverage --- example/acceptance_test.go | 2 +- example/ltree/codegen_test.go | 2 +- example/ltree/query.sql.go | 12 +++++----- example/ltree/query.sql_test.go | 25 ++++++++++++++------ internal/codegen/golang/gotype/types.go | 21 +++++++++++++++- internal/codegen/golang/gotype/types_test.go | 13 ++++++++++ 6 files changed, 59 insertions(+), 16 deletions(-) diff --git a/example/acceptance_test.go b/example/acceptance_test.go index 8fac5cf5..cfe30a7e 100644 --- a/example/acceptance_test.go +++ b/example/acceptance_test.go @@ -180,7 +180,7 @@ func TestExamples(t *testing.T) { "--schema-glob", "example/ltree/schema.sql", "--query-glob", "example/ltree/query.sql", "--go-type", "ltree=github.com/jackc/pgx/v5/pgtype.Text", - "--go-type", "_ltree=[]string", + "--go-type", "_ltree=github.com/jackc/pgx/v5/pgtype.Array[pgtype.Text]", }, }, { diff --git a/example/ltree/codegen_test.go b/example/ltree/codegen_test.go index 689005a8..bc62aadf 100644 --- a/example/ltree/codegen_test.go +++ b/example/ltree/codegen_test.go @@ -25,7 +25,7 @@ func TestGenerate_Go_Example_ltree(t *testing.T) { InlineParamCount: 2, TypeOverrides: map[string]string{ "ltree": "github.com/jackc/pgx/v5/pgtype.Text", - "_ltree": "[]string", + "_ltree": "github.com/jackc/pgx/v5/pgtype.Array[pgtype.Text]", }, }) if err != nil { diff --git a/example/ltree/query.sql.go b/example/ltree/query.sql.go index ced1be19..d771c53d 100644 --- a/example/ltree/query.sql.go +++ b/example/ltree/query.sql.go @@ -16,7 +16,7 @@ type QueryName struct{} type Querier interface { FindTopScienceChildren(ctx context.Context) ([]pgtype.Text, error) - FindTopScienceChildrenAgg(ctx context.Context) ([]string, error) + FindTopScienceChildrenAgg(ctx context.Context) (pgtype.Array[pgtype.Text], error) InsertSampleData(ctx context.Context) (pgconn.CommandTag, error) @@ -95,14 +95,14 @@ FROM test WHERE path <@ 'Top.Science';` // FindTopScienceChildrenAgg implements Querier.FindTopScienceChildrenAgg. -func (q *DBQuerier) FindTopScienceChildrenAgg(ctx context.Context) ([]string, error) { +func (q *DBQuerier) FindTopScienceChildrenAgg(ctx context.Context) (pgtype.Array[pgtype.Text], error) { ctx = context.WithValue(ctx, QueryName{}, "FindTopScienceChildrenAgg") rows, err := q.conn.Query(ctx, findTopScienceChildrenAggSQL) if err != nil { - return nil, fmt.Errorf("query FindTopScienceChildrenAgg: %w", err) + return pgtype.Array[pgtype.Text]{}, fmt.Errorf("query FindTopScienceChildrenAgg: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]string]) + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[pgtype.Array[pgtype.Text]]) } const insertSampleDataSQL = `INSERT INTO test @@ -142,8 +142,8 @@ const findLtreeInputSQL = `SELECT ($2::text[])::ltree[] AS text_arr;` type FindLtreeInputRow struct { - Ltree pgtype.Text `json:"ltree" db:"ltree"` - TextArr []string `json:"text_arr" db:"text_arr"` + Ltree pgtype.Text `json:"ltree" db:"ltree"` + TextArr pgtype.Array[pgtype.Text] `json:"text_arr" db:"text_arr"` } // FindLtreeInput implements Querier.FindLtreeInput. diff --git a/example/ltree/query.sql_test.go b/example/ltree/query.sql_test.go index 7e2369fe..14041b45 100644 --- a/example/ltree/query.sql_test.go +++ b/example/ltree/query.sql_test.go @@ -35,11 +35,15 @@ func TestQuerier(t *testing.T) { { rows, err := q.FindTopScienceChildrenAgg(ctx) require.NoError(t, err) - want := []string{ - "Top.Science", - "Top.Science.Astronomy", - "Top.Science.Astronomy.Astrophysics", - "Top.Science.Astronomy.Cosmology", + want := pgtype.Array[pgtype.Text]{ + Elements: []pgtype.Text{ + {String: "Top.Science", Valid: true}, + {String: "Top.Science.Astronomy", Valid: true}, + {String: "Top.Science.Astronomy.Astrophysics", Valid: true}, + {String: "Top.Science.Astronomy.Cosmology", Valid: true}, + }, + Dims: []pgtype.ArrayDimension{{Length: 4, LowerBound: 1}}, + Valid: true, } assert.Equal(t, want, rows) } @@ -50,8 +54,15 @@ func TestQuerier(t *testing.T) { rows, err := q.FindLtreeInput(ctx, in1, in2) require.NoError(t, err) assert.Equal(t, FindLtreeInputRow{ - Ltree: in1, - TextArr: in2, + Ltree: in1, + TextArr: pgtype.Array[pgtype.Text]{ + Elements: []pgtype.Text{ + {String: "qux", Valid: true}, + {String: "qux", Valid: true}, + }, + Dims: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}}, + Valid: true, + }, }, rows) } } diff --git a/internal/codegen/golang/gotype/types.go b/internal/codegen/golang/gotype/types.go index b74a6a98..e02b0808 100644 --- a/internal/codegen/golang/gotype/types.go +++ b/internal/codegen/golang/gotype/types.go @@ -206,7 +206,7 @@ func ParseOpaqueType(qualType string, pgType pg.Type) (Type, error) { if isPtr { bs = bs[1:] } - idx := bytes.LastIndexByte(bs, '.') + idx := lastIndexByteOutsideBrackets(bs, '.') name := string(bs[idx+1:]) var typ Type = &OpaqueType{Name: name} // On array types, the PgType goes on the Array. In all other cases, it @@ -240,6 +240,25 @@ func ParseOpaqueType(qualType string, pgType pg.Type) (Type, error) { return typ, nil } +func lastIndexByteOutsideBrackets(bs []byte, ch byte) int { + depth := 0 + for i := len(bs) - 1; i >= 0; i-- { + switch bs[i] { + case ']': + depth++ + case '[': + if depth > 0 { + depth-- + } + case ch: + if depth == 0 { + return i + } + } + } + return -1 +} + // MustParseKnownType creates a gotype.Type by parsing a fully qualified Go type // that pgx supports natively like "github.com/jackc/pgx/v5/pgtype.Int4Array", or most // builtin types like "string" and []*int16. diff --git a/internal/codegen/golang/gotype/types_test.go b/internal/codegen/golang/gotype/types_test.go index 485cb205..207b9d77 100644 --- a/internal/codegen/golang/gotype/types_test.go +++ b/internal/codegen/golang/gotype/types_test.go @@ -55,6 +55,13 @@ func TestMustParseKnownType(t *testing.T) { Elem: &ImportType{PkgPath: "util/custom/times", Type: &OpaqueType{Name: "Interval"}}, }, }, + { + qualType: "github.com/jackc/pgx/v5/pgtype.Array[pgtype.Text]", + want: &ImportType{ + PkgPath: "github.com/jackc/pgx/v5/pgtype", + Type: &OpaqueType{Name: "Array[pgtype.Text]"}, + }, + }, } for _, tt := range tests { t.Run(tt.qualType, func(t *testing.T) { @@ -115,6 +122,12 @@ func TestQualifyType(t *testing.T) { otherPkg: "example.com/foo", want: "[]Bar", }, + { + name: "github.com/jackc/pgx/v5/pgtype.Array[pgtype.Text] - example.com/foo", + typ: &ImportType{PkgPath: "github.com/jackc/pgx/v5/pgtype", Type: &OpaqueType{Name: "Array[pgtype.Text]"}}, + otherPkg: "example.com/foo", + want: "pgtype.Array[pgtype.Text]", + }, } for _, tt := range tests { From 47c914855b558509371e9a1a94a4325f89ee1a37 Mon Sep 17 00:00:00 2001 From: meoyawn Date: Wed, 6 May 2026 19:33:43 +0000 Subject: [PATCH 12/27] Trim blank declarer output --- example/complex_params/query.sql.go | 18 ------------ example/composite/query.sql.go | 8 ----- example/device/query.sql.go | 2 -- example/enums/query.sql.go | 2 -- example/function/query.sql.go | 2 -- example/nested/query.sql.go | 2 -- .../separate_out_dir/out/alpha_query.sql.0.go | 2 -- example/slices/query.sql.go | 4 --- internal/codegen/golang/declarer.go | 15 ++++++++++ internal/codegen/golang/declarer_test.go | 29 ++++--------------- internal/codegen/golang/generate.go | 4 ++- internal/codegen/golang/query.gotemplate | 2 +- .../testdata/declarer_composite.input.golden | 7 +---- .../testdata/declarer_composite.output.golden | 2 -- .../declarer_composite_array.input.golden | 9 +----- .../declarer_composite_array.output.golden | 2 -- .../declarer_composite_enum.input.golden | 7 +---- .../declarer_composite_enum.output.golden | 2 -- .../declarer_composite_nested.input.golden | 9 +----- .../declarer_composite_nested.output.golden | 2 -- internal/pg/query.sql.go | 2 -- 21 files changed, 29 insertions(+), 103 deletions(-) diff --git a/example/complex_params/query.sql.go b/example/complex_params/query.sql.go index 6f4c9e84..e881273a 100644 --- a/example/complex_params/query.sql.go +++ b/example/complex_params/query.sql.go @@ -95,32 +95,14 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - var _ = addTypeToRegister("\"dimensions\"") - - - - var _ = addTypeToRegister("\"product_image_set_type\"") - - - - var _ = addTypeToRegister("\"product_image_type\"") - - - - var _ = addTypeToRegister("\"_product_image_type\"") - - - - const paramArrayIntSQL = `SELECT $1::bigint[];` // ParamArrayInt implements Querier.ParamArrayInt. diff --git a/example/composite/query.sql.go b/example/composite/query.sql.go index 87632eb1..94dc3ec1 100644 --- a/example/composite/query.sql.go +++ b/example/composite/query.sql.go @@ -98,22 +98,14 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - var _ = addTypeToRegister("\"arrays\"") - - - - var _ = addTypeToRegister("\"blocks\"") var _ = addTypeToRegister("\"user_email\"") var _ = addTypeToRegister("\"_blocks\"") - - const searchScreenshotsSQL = `SELECT ss.id, array_agg(bl) AS blocks diff --git a/example/device/query.sql.go b/example/device/query.sql.go index 85b9ee91..2cffae10 100644 --- a/example/device/query.sql.go +++ b/example/device/query.sql.go @@ -101,8 +101,6 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - var _ = addTypeToRegister("\"device_type\"") var _ = addTypeToRegister("\"user\"") diff --git a/example/enums/query.sql.go b/example/enums/query.sql.go index f361dada..fe1f8848 100644 --- a/example/enums/query.sql.go +++ b/example/enums/query.sql.go @@ -103,8 +103,6 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - var _ = addTypeToRegister("\"device_type\"") var _ = addTypeToRegister("\"device\"") diff --git a/example/function/query.sql.go b/example/function/query.sql.go index d571f1cb..eb07b9c1 100644 --- a/example/function/query.sql.go +++ b/example/function/query.sql.go @@ -80,8 +80,6 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - var _ = addTypeToRegister("\"list_item\"") var _ = addTypeToRegister("\"list_stats\"") diff --git a/example/nested/query.sql.go b/example/nested/query.sql.go index 5dbc94ad..a355faf8 100644 --- a/example/nested/query.sql.go +++ b/example/nested/query.sql.go @@ -89,8 +89,6 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - var _ = addTypeToRegister("\"dimensions\"") var _ = addTypeToRegister("\"product_image_set_type\"") diff --git a/example/separate_out_dir/out/alpha_query.sql.0.go b/example/separate_out_dir/out/alpha_query.sql.0.go index 2b724fc5..a23c0727 100644 --- a/example/separate_out_dir/out/alpha_query.sql.0.go +++ b/example/separate_out_dir/out/alpha_query.sql.0.go @@ -79,8 +79,6 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - var _ = addTypeToRegister("\"alpha\"") var _ = addTypeToRegister("\"_alpha\"") diff --git a/example/slices/query.sql.go b/example/slices/query.sql.go index 629503bb..2fb73c70 100644 --- a/example/slices/query.sql.go +++ b/example/slices/query.sql.go @@ -75,10 +75,6 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - - - const getBoolsSQL = `SELECT $1::boolean[];` // GetBools implements Querier.GetBools. diff --git a/internal/codegen/golang/declarer.go b/internal/codegen/golang/declarer.go index e35ed33e..61ca79fe 100644 --- a/internal/codegen/golang/declarer.go +++ b/internal/codegen/golang/declarer.go @@ -44,6 +44,21 @@ func (d DeclarerSet) ListAll() []Declarer { return decls } +func emitDeclarers(decls []Declarer, pkgPath string) (string, error) { + declarations := make([]string, 0, len(decls)) + for _, decl := range decls { + declaration, err := decl.Declare(pkgPath) + if err != nil { + return "", err + } + if strings.TrimSpace(declaration) == "" { + continue + } + declarations = append(declarations, declaration) + } + return strings.Join(declarations, "\n\n"), nil +} + func pgTypeNameForLoadType(name string) string { return `"` + strings.ReplaceAll(name, `"`, `""`) + `"` } diff --git a/internal/codegen/golang/declarer_test.go b/internal/codegen/golang/declarer_test.go index e1c141f5..96c4ef3a 100644 --- a/internal/codegen/golang/declarer_test.go +++ b/internal/codegen/golang/declarer_test.go @@ -3,7 +3,6 @@ package golang import ( "flag" "os" - "strings" "testing" "github.com/jschaf/pggen/internal/casing" @@ -132,18 +131,10 @@ func TestDeclarers(t *testing.T) { t.Run(tt.name+"_input", func(t *testing.T) { golden := "testdata/declarer_" + tt.name + ".input.golden" decls := FindInputDeclarers(tt.typ).ListAll() - sb := &strings.Builder{} - for i, decl := range decls { - s, err := decl.Declare(tt.pkgPath) - if err != nil { - t.Fatal(err) - } - sb.WriteString(s) - if i < len(decls)-1 { - sb.WriteString("\n\n") - } + got, err := emitDeclarers(decls, tt.pkgPath) + if err != nil { + t.Fatal(err) } - got := sb.String() if *update { err := os.WriteFile(golden, []byte(got), 0o600) @@ -159,18 +150,10 @@ func TestDeclarers(t *testing.T) { t.Run(tt.name+"_output", func(t *testing.T) { golden := "testdata/declarer_" + tt.name + ".output.golden" decls := FindOutputDeclarers(tt.typ).ListAll() - sb := &strings.Builder{} - for i, decl := range decls { - s, err := decl.Declare(tt.pkgPath) - if err != nil { - t.Fatal(err) - } - sb.WriteString(s) - if i < len(decls)-1 { - sb.WriteString("\n\n") - } + got, err := emitDeclarers(decls, tt.pkgPath) + if err != nil { + t.Fatal(err) } - got := sb.String() if *update { err := os.WriteFile(golden, []byte(got), 0o600) diff --git a/internal/codegen/golang/generate.go b/internal/codegen/golang/generate.go index ffb9726f..080f2036 100644 --- a/internal/codegen/golang/generate.go +++ b/internal/codegen/golang/generate.go @@ -71,7 +71,9 @@ func Generate(opts GenerateOptions, queryFiles []codegen.QueryFile) error { var queryTemplate string func parseQueryTemplate() (*template.Template, error) { - tmpl, err := template.New("gen_query").Parse(queryTemplate) + tmpl, err := template.New("gen_query").Funcs(template.FuncMap{ + "emitDeclarers": emitDeclarers, + }).Parse(queryTemplate) if err != nil { return nil, fmt.Errorf("parse query.gotemplate: %w", err) } diff --git a/internal/codegen/golang/query.gotemplate b/internal/codegen/golang/query.gotemplate index b1dafd3f..4b8729e8 100644 --- a/internal/codegen/golang/query.gotemplate +++ b/internal/codegen/golang/query.gotemplate @@ -44,7 +44,7 @@ func NewQuerier(conn genericConn) *DBQuerier { return &DBQuerier{conn: conn} } -{{- range .Declarers}}{{- "\n\n" -}}{{ .Declare $.PkgPath }}{{ end -}} +{{- with emitDeclarers .Declarers $.PkgPath }}{{- "\n\n" -}}{{ . }}{{- end -}} {{- end -}} {{- range $i, $q := .Queries -}} diff --git a/internal/codegen/golang/testdata/declarer_composite.input.golden b/internal/codegen/golang/testdata/declarer_composite.input.golden index 690e99a7..2b86b954 100644 --- a/internal/codegen/golang/testdata/declarer_composite.input.golden +++ b/internal/codegen/golang/testdata/declarer_composite.input.golden @@ -38,9 +38,4 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - -var _ = addTypeToRegister("\"some_table\"") - - - +var _ = addTypeToRegister("\"some_table\"") \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_composite.output.golden b/internal/codegen/golang/testdata/declarer_composite.output.golden index 49f809b6..2b86b954 100644 --- a/internal/codegen/golang/testdata/declarer_composite.output.golden +++ b/internal/codegen/golang/testdata/declarer_composite.output.golden @@ -38,6 +38,4 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - var _ = addTypeToRegister("\"some_table\"") \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_composite_array.input.golden b/internal/codegen/golang/testdata/declarer_composite_array.input.golden index 6c4e9a20..2ca012c7 100644 --- a/internal/codegen/golang/testdata/declarer_composite_array.input.golden +++ b/internal/codegen/golang/testdata/declarer_composite_array.input.golden @@ -38,13 +38,6 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - var _ = addTypeToRegister("\"some_table\"") - - -var _ = addTypeToRegister("\"_some_array\"") - - - +var _ = addTypeToRegister("\"_some_array\"") \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_composite_array.output.golden b/internal/codegen/golang/testdata/declarer_composite_array.output.golden index 31714f83..2ca012c7 100644 --- a/internal/codegen/golang/testdata/declarer_composite_array.output.golden +++ b/internal/codegen/golang/testdata/declarer_composite_array.output.golden @@ -38,8 +38,6 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - var _ = addTypeToRegister("\"some_table\"") var _ = addTypeToRegister("\"_some_array\"") \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_composite_enum.input.golden b/internal/codegen/golang/testdata/declarer_composite_enum.input.golden index 12afb781..f5430b1b 100644 --- a/internal/codegen/golang/testdata/declarer_composite_enum.input.golden +++ b/internal/codegen/golang/testdata/declarer_composite_enum.input.golden @@ -47,11 +47,6 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - var _ = addTypeToRegister("\"device_type\"") -var _ = addTypeToRegister("\"some_table_enum\"") - - - +var _ = addTypeToRegister("\"some_table_enum\"") \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_composite_enum.output.golden b/internal/codegen/golang/testdata/declarer_composite_enum.output.golden index 5dcdb847..f5430b1b 100644 --- a/internal/codegen/golang/testdata/declarer_composite_enum.output.golden +++ b/internal/codegen/golang/testdata/declarer_composite_enum.output.golden @@ -47,8 +47,6 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - var _ = addTypeToRegister("\"device_type\"") var _ = addTypeToRegister("\"some_table_enum\"") \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_composite_nested.input.golden b/internal/codegen/golang/testdata/declarer_composite_nested.input.golden index 034a967e..4fc1b1d3 100644 --- a/internal/codegen/golang/testdata/declarer_composite_nested.input.golden +++ b/internal/codegen/golang/testdata/declarer_composite_nested.input.golden @@ -43,13 +43,6 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - var _ = addTypeToRegister("\"foo_type\"") - - -var _ = addTypeToRegister("\"some_table_nested\"") - - - +var _ = addTypeToRegister("\"some_table_nested\"") \ No newline at end of file diff --git a/internal/codegen/golang/testdata/declarer_composite_nested.output.golden b/internal/codegen/golang/testdata/declarer_composite_nested.output.golden index c7658db8..4fc1b1d3 100644 --- a/internal/codegen/golang/testdata/declarer_composite_nested.output.golden +++ b/internal/codegen/golang/testdata/declarer_composite_nested.output.golden @@ -43,8 +43,6 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - var _ = addTypeToRegister("\"foo_type\"") var _ = addTypeToRegister("\"some_table_nested\"") \ No newline at end of file diff --git a/internal/pg/query.sql.go b/internal/pg/query.sql.go index 815a8c72..e07077b7 100644 --- a/internal/pg/query.sql.go +++ b/internal/pg/query.sql.go @@ -85,8 +85,6 @@ func addTypeToRegister(typ string) struct{} { return struct{}{} } - - const findEnumTypesSQL = `WITH enums AS ( SELECT enumtypid::int8 AS enum_type, From ddc4ef26b84cecc040fb364c1317a04d583c16a0 Mon Sep 17 00:00:00 2001 From: meoyawn Date: Wed, 6 May 2026 21:11:52 +0000 Subject: [PATCH 13/27] Fix zero returns for custom scalar results --- Makefile | 3 +++ example/author/query.sql.go | 24 +++++++++++------ example/complex_params/query.sql.go | 15 +++++++---- example/composite/query.sql.go | 15 +++++++---- example/custom_types/query.sql | 3 +++ example/custom_types/query.sql.go | 25 ++++++++++++++--- example/custom_types/query.sql_test.go | 11 ++++++++ example/device/query.sql.go | 15 +++++++---- example/domain/query.sql.go | 3 ++- example/enums/query.sql.go | 15 +++++++---- example/erp/order/customer.sql.go | 15 +++++++---- example/erp/order/price.sql.go | 6 +++-- example/function/query.sql.go | 3 ++- example/go_pointer_types/query.sql.go | 18 ++++++++----- .../inline_param_count/inline0/query.sql.go | 9 ++++--- .../inline_param_count/inline1/query.sql.go | 9 ++++--- .../inline_param_count/inline2/query.sql.go | 9 ++++--- .../inline_param_count/inline3/query.sql.go | 9 ++++--- example/ltree/query.sql.go | 9 ++++--- example/nested/query.sql.go | 6 +++-- example/pgcrypto/query.sql.go | 3 ++- .../separate_out_dir/out/alpha_query.sql.0.go | 6 +++-- .../separate_out_dir/out/alpha_query.sql.1.go | 3 ++- .../separate_out_dir/out/bravo_query.sql.go | 3 ++- example/slices/query.sql.go | 12 ++++++--- example/syntax/query.sql.go | 27 ++++++++++++------- example/void/query.sql.go | 9 ++++--- internal/codegen/golang/query.gotemplate | 3 ++- internal/codegen/golang/templated_file.go | 11 +++++++- internal/pg/query.sql.go | 21 ++++++++++----- 30 files changed, 227 insertions(+), 93 deletions(-) diff --git a/Makefile b/Makefile index 6de01999..282c8f72 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,9 @@ ldflags := -ldflags "-X 'main.version=${version}' -X 'main.commit=${commit}'" .PHONY: all all: lint test acceptance-test +.PHONY: check +check: lint test update-acceptance-test + .PHONY: start start: docker-compose up -d diff --git a/example/author/query.sql.go b/example/author/query.sql.go index f950e443..45ed19d2 100644 --- a/example/author/query.sql.go +++ b/example/author/query.sql.go @@ -112,7 +112,8 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") rows, err := q.conn.Query(ctx, findAuthorByIDSQL, authorID) if err != nil { - return FindAuthorByIDRow{}, fmt.Errorf("query FindAuthorByID: %w", err) + var zero FindAuthorByIDRow + return zero, fmt.Errorf("query FindAuthorByID: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) @@ -132,7 +133,8 @@ func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAu ctx = context.WithValue(ctx, QueryName{}, "FindAuthors") rows, err := q.conn.Query(ctx, findAuthorsSQL, firstName) if err != nil { - return nil, fmt.Errorf("query FindAuthors: %w", err) + var zero []FindAuthorsRow + return zero, fmt.Errorf("query FindAuthors: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[FindAuthorsRow]) @@ -150,7 +152,8 @@ func (q *DBQuerier) FindAuthorNames(ctx context.Context, authorID int32) ([]Find ctx = context.WithValue(ctx, QueryName{}, "FindAuthorNames") rows, err := q.conn.Query(ctx, findAuthorNamesSQL, authorID) if err != nil { - return nil, fmt.Errorf("query FindAuthorNames: %w", err) + var zero []FindAuthorNamesRow + return zero, fmt.Errorf("query FindAuthorNames: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[FindAuthorNamesRow]) @@ -163,7 +166,8 @@ func (q *DBQuerier) FindFirstNames(ctx context.Context, authorID int32) ([]*stri ctx = context.WithValue(ctx, QueryName{}, "FindFirstNames") rows, err := q.conn.Query(ctx, findFirstNamesSQL, authorID) if err != nil { - return nil, fmt.Errorf("query FindFirstNames: %w", err) + var zero []*string + return zero, fmt.Errorf("query FindFirstNames: %w", err) } return pgx.CollectRows(rows, pgx.RowTo[*string]) @@ -224,7 +228,8 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, firstName string, lastName ctx = context.WithValue(ctx, QueryName{}, "InsertAuthor") rows, err := q.conn.Query(ctx, insertAuthorSQL, firstName, lastName) if err != nil { - return 0, fmt.Errorf("query InsertAuthor: %w", err) + var zero int32 + return zero, fmt.Errorf("query InsertAuthor: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int32]) @@ -252,7 +257,8 @@ func (q *DBQuerier) InsertAuthorSuffix(ctx context.Context, params InsertAuthorS ctx = context.WithValue(ctx, QueryName{}, "InsertAuthorSuffix") rows, err := q.conn.Query(ctx, insertAuthorSuffixSQL, params.FirstName, params.LastName, params.Suffix) if err != nil { - return InsertAuthorSuffixRow{}, fmt.Errorf("query InsertAuthorSuffix: %w", err) + var zero InsertAuthorSuffixRow + return zero, fmt.Errorf("query InsertAuthorSuffix: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[InsertAuthorSuffixRow]) @@ -265,7 +271,8 @@ func (q *DBQuerier) StringAggFirstName(ctx context.Context, authorID int32) (*st ctx = context.WithValue(ctx, QueryName{}, "StringAggFirstName") rows, err := q.conn.Query(ctx, stringAggFirstNameSQL, authorID) if err != nil { - return nil, fmt.Errorf("query StringAggFirstName: %w", err) + var zero *string + return zero, fmt.Errorf("query StringAggFirstName: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*string]) @@ -278,7 +285,8 @@ func (q *DBQuerier) ArrayAggFirstName(ctx context.Context, authorID int32) ([]st ctx = context.WithValue(ctx, QueryName{}, "ArrayAggFirstName") rows, err := q.conn.Query(ctx, arrayAggFirstNameSQL, authorID) if err != nil { - return nil, fmt.Errorf("query ArrayAggFirstName: %w", err) + var zero []string + return zero, fmt.Errorf("query ArrayAggFirstName: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]string]) diff --git a/example/complex_params/query.sql.go b/example/complex_params/query.sql.go index e881273a..b4aae0f7 100644 --- a/example/complex_params/query.sql.go +++ b/example/complex_params/query.sql.go @@ -110,7 +110,8 @@ func (q *DBQuerier) ParamArrayInt(ctx context.Context, ints []int) ([]int, error ctx = context.WithValue(ctx, QueryName{}, "ParamArrayInt") rows, err := q.conn.Query(ctx, paramArrayIntSQL, ints) if err != nil { - return nil, fmt.Errorf("query ParamArrayInt: %w", err) + var zero []int + return zero, fmt.Errorf("query ParamArrayInt: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]int]) @@ -123,7 +124,8 @@ func (q *DBQuerier) ParamNested1(ctx context.Context, dimensions Dimensions) (Di ctx = context.WithValue(ctx, QueryName{}, "ParamNested1") rows, err := q.conn.Query(ctx, paramNested1SQL, dimensions) if err != nil { - return Dimensions{}, fmt.Errorf("query ParamNested1: %w", err) + var zero Dimensions + return zero, fmt.Errorf("query ParamNested1: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[Dimensions]) @@ -136,7 +138,8 @@ func (q *DBQuerier) ParamNested2(ctx context.Context, image ProductImageType) (P ctx = context.WithValue(ctx, QueryName{}, "ParamNested2") rows, err := q.conn.Query(ctx, paramNested2SQL, image) if err != nil { - return ProductImageType{}, fmt.Errorf("query ParamNested2: %w", err) + var zero ProductImageType + return zero, fmt.Errorf("query ParamNested2: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[ProductImageType]) @@ -149,7 +152,8 @@ func (q *DBQuerier) ParamNested2Array(ctx context.Context, images []ProductImage ctx = context.WithValue(ctx, QueryName{}, "ParamNested2Array") rows, err := q.conn.Query(ctx, paramNested2ArraySQL, images) if err != nil { - return nil, fmt.Errorf("query ParamNested2Array: %w", err) + var zero []ProductImageType + return zero, fmt.Errorf("query ParamNested2Array: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]ProductImageType]) @@ -162,7 +166,8 @@ func (q *DBQuerier) ParamNested3(ctx context.Context, imageSet ProductImageSetTy ctx = context.WithValue(ctx, QueryName{}, "ParamNested3") rows, err := q.conn.Query(ctx, paramNested3SQL, imageSet) if err != nil { - return ProductImageSetType{}, fmt.Errorf("query ParamNested3: %w", err) + var zero ProductImageSetType + return zero, fmt.Errorf("query ParamNested3: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[ProductImageSetType]) diff --git a/example/composite/query.sql.go b/example/composite/query.sql.go index 94dc3ec1..e13e59b8 100644 --- a/example/composite/query.sql.go +++ b/example/composite/query.sql.go @@ -132,7 +132,8 @@ func (q *DBQuerier) SearchScreenshots(ctx context.Context, params SearchScreensh ctx = context.WithValue(ctx, QueryName{}, "SearchScreenshots") rows, err := q.conn.Query(ctx, searchScreenshotsSQL, params.Body, params.Limit, params.Offset) if err != nil { - return nil, fmt.Errorf("query SearchScreenshots: %w", err) + var zero []SearchScreenshotsRow + return zero, fmt.Errorf("query SearchScreenshots: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[SearchScreenshotsRow]) @@ -158,7 +159,8 @@ func (q *DBQuerier) SearchScreenshotsOneCol(ctx context.Context, params SearchSc ctx = context.WithValue(ctx, QueryName{}, "SearchScreenshotsOneCol") rows, err := q.conn.Query(ctx, searchScreenshotsOneColSQL, params.Body, params.Limit, params.Offset) if err != nil { - return nil, fmt.Errorf("query SearchScreenshotsOneCol: %w", err) + var zero [][]Blocks + return zero, fmt.Errorf("query SearchScreenshotsOneCol: %w", err) } return pgx.CollectRows(rows, pgx.RowTo[[]Blocks]) @@ -184,7 +186,8 @@ func (q *DBQuerier) InsertScreenshotBlocks(ctx context.Context, screenshotID int ctx = context.WithValue(ctx, QueryName{}, "InsertScreenshotBlocks") rows, err := q.conn.Query(ctx, insertScreenshotBlocksSQL, screenshotID, body) if err != nil { - return InsertScreenshotBlocksRow{}, fmt.Errorf("query InsertScreenshotBlocks: %w", err) + var zero InsertScreenshotBlocksRow + return zero, fmt.Errorf("query InsertScreenshotBlocks: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[InsertScreenshotBlocksRow]) @@ -197,7 +200,8 @@ func (q *DBQuerier) ArraysInput(ctx context.Context, arrays Arrays) (Arrays, err ctx = context.WithValue(ctx, QueryName{}, "ArraysInput") rows, err := q.conn.Query(ctx, arraysInputSQL, arrays) if err != nil { - return Arrays{}, fmt.Errorf("query ArraysInput: %w", err) + var zero Arrays + return zero, fmt.Errorf("query ArraysInput: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[Arrays]) @@ -210,7 +214,8 @@ func (q *DBQuerier) UserEmails(ctx context.Context) (UserEmail, error) { ctx = context.WithValue(ctx, QueryName{}, "UserEmails") rows, err := q.conn.Query(ctx, userEmailsSQL) if err != nil { - return UserEmail{}, fmt.Errorf("query UserEmails: %w", err) + var zero UserEmail + return zero, fmt.Errorf("query UserEmails: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[UserEmail]) diff --git a/example/custom_types/query.sql b/example/custom_types/query.sql index 653aa7ed..c4050ed4 100644 --- a/example/custom_types/query.sql +++ b/example/custom_types/query.sql @@ -1,6 +1,9 @@ -- name: CustomTypes :one SELECT 'some_text', 1::bigint; +-- name: CustomString :one +SELECT 'some_text'::text; + -- name: CustomMyInt :one SELECT '5'::my_int as int5; diff --git a/example/custom_types/query.sql.go b/example/custom_types/query.sql.go index 6b4dbc74..32c5ee4f 100644 --- a/example/custom_types/query.sql.go +++ b/example/custom_types/query.sql.go @@ -16,6 +16,8 @@ type QueryName struct{} type Querier interface { CustomTypes(ctx context.Context) (CustomTypesRow, error) + CustomString(ctx context.Context) (mytype.String, error) + CustomMyInt(ctx context.Context) (int, error) IntArray(ctx context.Context) ([][]int32, error) @@ -85,12 +87,27 @@ func (q *DBQuerier) CustomTypes(ctx context.Context) (CustomTypesRow, error) { ctx = context.WithValue(ctx, QueryName{}, "CustomTypes") rows, err := q.conn.Query(ctx, customTypesSQL) if err != nil { - return CustomTypesRow{}, fmt.Errorf("query CustomTypes: %w", err) + var zero CustomTypesRow + return zero, fmt.Errorf("query CustomTypes: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CustomTypesRow]) } +const customStringSQL = `SELECT 'some_text'::text;` + +// CustomString implements Querier.CustomString. +func (q *DBQuerier) CustomString(ctx context.Context) (mytype.String, error) { + ctx = context.WithValue(ctx, QueryName{}, "CustomString") + rows, err := q.conn.Query(ctx, customStringSQL) + if err != nil { + var zero mytype.String + return zero, fmt.Errorf("query CustomString: %w", err) + } + + return pgx.CollectExactlyOneRow(rows, pgx.RowTo[mytype.String]) +} + const customMyIntSQL = `SELECT '5'::my_int as int5;` // CustomMyInt implements Querier.CustomMyInt. @@ -98,7 +115,8 @@ func (q *DBQuerier) CustomMyInt(ctx context.Context) (int, error) { ctx = context.WithValue(ctx, QueryName{}, "CustomMyInt") rows, err := q.conn.Query(ctx, customMyIntSQL) if err != nil { - return 0, fmt.Errorf("query CustomMyInt: %w", err) + var zero int + return zero, fmt.Errorf("query CustomMyInt: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int]) @@ -111,7 +129,8 @@ func (q *DBQuerier) IntArray(ctx context.Context) ([][]int32, error) { ctx = context.WithValue(ctx, QueryName{}, "IntArray") rows, err := q.conn.Query(ctx, intArraySQL) if err != nil { - return nil, fmt.Errorf("query IntArray: %w", err) + var zero [][]int32 + return zero, fmt.Errorf("query IntArray: %w", err) } return pgx.CollectRows(rows, pgx.RowTo[[]int32]) diff --git a/example/custom_types/query.sql_test.go b/example/custom_types/query.sql_test.go index e2153007..77d8d422 100644 --- a/example/custom_types/query.sql_test.go +++ b/example/custom_types/query.sql_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/jackc/pgx/v5/pgtype" + "github.com/jschaf/pggen/example/custom_types/mytype" "github.com/jschaf/pggen/internal/pgtest" "github.com/jschaf/pggen/internal/texts" "github.com/stretchr/testify/assert" @@ -27,6 +28,16 @@ func TestQuerier_CustomTypes(t *testing.T) { }) } +func TestQuerier_CustomString(t *testing.T) { + conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) + defer cleanup() + q := NewQuerier(conn) + + val, err := q.CustomString(t.Context()) + require.NoError(t, err) + assert.Equal(t, mytype.String("some_text"), val) +} + func TestQuerier_CustomMyInt(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() diff --git a/example/device/query.sql.go b/example/device/query.sql.go index 2cffae10..73c7cb3a 100644 --- a/example/device/query.sql.go +++ b/example/device/query.sql.go @@ -123,7 +123,8 @@ func (q *DBQuerier) FindDevicesByUser(ctx context.Context, id int) ([]FindDevice ctx = context.WithValue(ctx, QueryName{}, "FindDevicesByUser") rows, err := q.conn.Query(ctx, findDevicesByUserSQL, id) if err != nil { - return nil, fmt.Errorf("query FindDevicesByUser: %w", err) + var zero []FindDevicesByUserRow + return zero, fmt.Errorf("query FindDevicesByUser: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[FindDevicesByUserRow]) @@ -147,7 +148,8 @@ func (q *DBQuerier) CompositeUser(ctx context.Context) ([]CompositeUserRow, erro ctx = context.WithValue(ctx, QueryName{}, "CompositeUser") rows, err := q.conn.Query(ctx, compositeUserSQL) if err != nil { - return nil, fmt.Errorf("query CompositeUser: %w", err) + var zero []CompositeUserRow + return zero, fmt.Errorf("query CompositeUser: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[CompositeUserRow]) @@ -160,7 +162,8 @@ func (q *DBQuerier) CompositeUserOne(ctx context.Context) (User, error) { ctx = context.WithValue(ctx, QueryName{}, "CompositeUserOne") rows, err := q.conn.Query(ctx, compositeUserOneSQL) if err != nil { - return User{}, fmt.Errorf("query CompositeUserOne: %w", err) + var zero User + return zero, fmt.Errorf("query CompositeUserOne: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[User]) @@ -178,7 +181,8 @@ func (q *DBQuerier) CompositeUserOneTwoCols(ctx context.Context) (CompositeUserO ctx = context.WithValue(ctx, QueryName{}, "CompositeUserOneTwoCols") rows, err := q.conn.Query(ctx, compositeUserOneTwoColsSQL) if err != nil { - return CompositeUserOneTwoColsRow{}, fmt.Errorf("query CompositeUserOneTwoCols: %w", err) + var zero CompositeUserOneTwoColsRow + return zero, fmt.Errorf("query CompositeUserOneTwoCols: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CompositeUserOneTwoColsRow]) @@ -191,7 +195,8 @@ func (q *DBQuerier) CompositeUserMany(ctx context.Context) ([]User, error) { ctx = context.WithValue(ctx, QueryName{}, "CompositeUserMany") rows, err := q.conn.Query(ctx, compositeUserManySQL) if err != nil { - return nil, fmt.Errorf("query CompositeUserMany: %w", err) + var zero []User + return zero, fmt.Errorf("query CompositeUserMany: %w", err) } return pgx.CollectRows(rows, pgx.RowTo[User]) diff --git a/example/domain/query.sql.go b/example/domain/query.sql.go index 059565e4..333ca36c 100644 --- a/example/domain/query.sql.go +++ b/example/domain/query.sql.go @@ -75,7 +75,8 @@ func (q *DBQuerier) DomainOne(ctx context.Context) (string, error) { ctx = context.WithValue(ctx, QueryName{}, "DomainOne") rows, err := q.conn.Query(ctx, domainOneSQL) if err != nil { - return "", fmt.Errorf("query DomainOne: %w", err) + var zero string + return zero, fmt.Errorf("query DomainOne: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) diff --git a/example/enums/query.sql.go b/example/enums/query.sql.go index fe1f8848..e7b266f6 100644 --- a/example/enums/query.sql.go +++ b/example/enums/query.sql.go @@ -122,7 +122,8 @@ func (q *DBQuerier) FindAllDevices(ctx context.Context) ([]FindAllDevicesRow, er ctx = context.WithValue(ctx, QueryName{}, "FindAllDevices") rows, err := q.conn.Query(ctx, findAllDevicesSQL) if err != nil { - return nil, fmt.Errorf("query FindAllDevices: %w", err) + var zero []FindAllDevicesRow + return zero, fmt.Errorf("query FindAllDevices: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[FindAllDevicesRow]) @@ -148,7 +149,8 @@ func (q *DBQuerier) FindOneDeviceArray(ctx context.Context) ([]DeviceType, error ctx = context.WithValue(ctx, QueryName{}, "FindOneDeviceArray") rows, err := q.conn.Query(ctx, findOneDeviceArraySQL) if err != nil { - return nil, fmt.Errorf("query FindOneDeviceArray: %w", err) + var zero []DeviceType + return zero, fmt.Errorf("query FindOneDeviceArray: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]DeviceType]) @@ -163,7 +165,8 @@ func (q *DBQuerier) FindManyDeviceArray(ctx context.Context) ([][]DeviceType, er ctx = context.WithValue(ctx, QueryName{}, "FindManyDeviceArray") rows, err := q.conn.Query(ctx, findManyDeviceArraySQL) if err != nil { - return nil, fmt.Errorf("query FindManyDeviceArray: %w", err) + var zero [][]DeviceType + return zero, fmt.Errorf("query FindManyDeviceArray: %w", err) } return pgx.CollectRows(rows, pgx.RowTo[[]DeviceType]) @@ -183,7 +186,8 @@ func (q *DBQuerier) FindManyDeviceArrayWithNum(ctx context.Context) ([]FindManyD ctx = context.WithValue(ctx, QueryName{}, "FindManyDeviceArrayWithNum") rows, err := q.conn.Query(ctx, findManyDeviceArrayWithNumSQL) if err != nil { - return nil, fmt.Errorf("query FindManyDeviceArrayWithNum: %w", err) + var zero []FindManyDeviceArrayWithNumRow + return zero, fmt.Errorf("query FindManyDeviceArrayWithNum: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[FindManyDeviceArrayWithNumRow]) @@ -196,7 +200,8 @@ func (q *DBQuerier) EnumInsideComposite(ctx context.Context) (Device, error) { ctx = context.WithValue(ctx, QueryName{}, "EnumInsideComposite") rows, err := q.conn.Query(ctx, enumInsideCompositeSQL) if err != nil { - return Device{}, fmt.Errorf("query EnumInsideComposite: %w", err) + var zero Device + return zero, fmt.Errorf("query EnumInsideComposite: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[Device]) diff --git a/example/erp/order/customer.sql.go b/example/erp/order/customer.sql.go index e91a598d..259365f5 100644 --- a/example/erp/order/customer.sql.go +++ b/example/erp/order/customer.sql.go @@ -96,7 +96,8 @@ func (q *DBQuerier) CreateTenant(ctx context.Context, key string, name string) ( ctx = context.WithValue(ctx, QueryName{}, "CreateTenant") rows, err := q.conn.Query(ctx, createTenantSQL, key, name) if err != nil { - return CreateTenantRow{}, fmt.Errorf("query CreateTenant: %w", err) + var zero CreateTenantRow + return zero, fmt.Errorf("query CreateTenant: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CreateTenantRow]) @@ -118,7 +119,8 @@ func (q *DBQuerier) FindOrdersByCustomer(ctx context.Context, customerID int32) ctx = context.WithValue(ctx, QueryName{}, "FindOrdersByCustomer") rows, err := q.conn.Query(ctx, findOrdersByCustomerSQL, customerID) if err != nil { - return nil, fmt.Errorf("query FindOrdersByCustomer: %w", err) + var zero []FindOrdersByCustomerRow + return zero, fmt.Errorf("query FindOrdersByCustomer: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[FindOrdersByCustomerRow]) @@ -141,7 +143,8 @@ func (q *DBQuerier) FindProductsInOrder(ctx context.Context, orderID int32) ([]F ctx = context.WithValue(ctx, QueryName{}, "FindProductsInOrder") rows, err := q.conn.Query(ctx, findProductsInOrderSQL, orderID) if err != nil { - return nil, fmt.Errorf("query FindProductsInOrder: %w", err) + var zero []FindProductsInOrderRow + return zero, fmt.Errorf("query FindProductsInOrder: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[FindProductsInOrderRow]) @@ -169,7 +172,8 @@ func (q *DBQuerier) InsertCustomer(ctx context.Context, params InsertCustomerPar ctx = context.WithValue(ctx, QueryName{}, "InsertCustomer") rows, err := q.conn.Query(ctx, insertCustomerSQL, params.FirstName, params.LastName, params.Email) if err != nil { - return InsertCustomerRow{}, fmt.Errorf("query InsertCustomer: %w", err) + var zero InsertCustomerRow + return zero, fmt.Errorf("query InsertCustomer: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[InsertCustomerRow]) @@ -197,7 +201,8 @@ func (q *DBQuerier) InsertOrder(ctx context.Context, params InsertOrderParams) ( ctx = context.WithValue(ctx, QueryName{}, "InsertOrder") rows, err := q.conn.Query(ctx, insertOrderSQL, params.OrderDate, params.OrderTotal, params.CustID) if err != nil { - return InsertOrderRow{}, fmt.Errorf("query InsertOrder: %w", err) + var zero InsertOrderRow + return zero, fmt.Errorf("query InsertOrder: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[InsertOrderRow]) diff --git a/example/erp/order/price.sql.go b/example/erp/order/price.sql.go index f610d29b..a021695a 100644 --- a/example/erp/order/price.sql.go +++ b/example/erp/order/price.sql.go @@ -23,7 +23,8 @@ func (q *DBQuerier) FindOrdersByPrice(ctx context.Context, minTotal pgtype.Numer ctx = context.WithValue(ctx, QueryName{}, "FindOrdersByPrice") rows, err := q.conn.Query(ctx, findOrdersByPriceSQL, minTotal) if err != nil { - return nil, fmt.Errorf("query FindOrdersByPrice: %w", err) + var zero []FindOrdersByPriceRow + return zero, fmt.Errorf("query FindOrdersByPrice: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[FindOrdersByPriceRow]) @@ -43,7 +44,8 @@ func (q *DBQuerier) FindOrdersMRR(ctx context.Context) ([]FindOrdersMRRRow, erro ctx = context.WithValue(ctx, QueryName{}, "FindOrdersMRR") rows, err := q.conn.Query(ctx, findOrdersMRRSQL) if err != nil { - return nil, fmt.Errorf("query FindOrdersMRR: %w", err) + var zero []FindOrdersMRRRow + return zero, fmt.Errorf("query FindOrdersMRR: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[FindOrdersMRRRow]) diff --git a/example/function/query.sql.go b/example/function/query.sql.go index eb07b9c1..36659102 100644 --- a/example/function/query.sql.go +++ b/example/function/query.sql.go @@ -98,7 +98,8 @@ func (q *DBQuerier) OutParams(ctx context.Context) ([]OutParamsRow, error) { ctx = context.WithValue(ctx, QueryName{}, "OutParams") rows, err := q.conn.Query(ctx, outParamsSQL) if err != nil { - return nil, fmt.Errorf("query OutParams: %w", err) + var zero []OutParamsRow + return zero, fmt.Errorf("query OutParams: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[OutParamsRow]) diff --git a/example/go_pointer_types/query.sql.go b/example/go_pointer_types/query.sql.go index 49378119..db8f6132 100644 --- a/example/go_pointer_types/query.sql.go +++ b/example/go_pointer_types/query.sql.go @@ -87,7 +87,8 @@ func (q *DBQuerier) GenSeries1(ctx context.Context) (*int, error) { ctx = context.WithValue(ctx, QueryName{}, "GenSeries1") rows, err := q.conn.Query(ctx, genSeries1SQL) if err != nil { - return nil, fmt.Errorf("query GenSeries1: %w", err) + var zero *int + return zero, fmt.Errorf("query GenSeries1: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*int]) @@ -101,7 +102,8 @@ func (q *DBQuerier) GenSeries(ctx context.Context) ([]*int, error) { ctx = context.WithValue(ctx, QueryName{}, "GenSeries") rows, err := q.conn.Query(ctx, genSeriesSQL) if err != nil { - return nil, fmt.Errorf("query GenSeries: %w", err) + var zero []*int + return zero, fmt.Errorf("query GenSeries: %w", err) } return pgx.CollectRows(rows, pgx.RowTo[*int]) @@ -115,7 +117,8 @@ func (q *DBQuerier) GenSeriesArr1(ctx context.Context) ([]int, error) { ctx = context.WithValue(ctx, QueryName{}, "GenSeriesArr1") rows, err := q.conn.Query(ctx, genSeriesArr1SQL) if err != nil { - return nil, fmt.Errorf("query GenSeriesArr1: %w", err) + var zero []int + return zero, fmt.Errorf("query GenSeriesArr1: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]int]) @@ -129,7 +132,8 @@ func (q *DBQuerier) GenSeriesArr(ctx context.Context) ([][]int, error) { ctx = context.WithValue(ctx, QueryName{}, "GenSeriesArr") rows, err := q.conn.Query(ctx, genSeriesArrSQL) if err != nil { - return nil, fmt.Errorf("query GenSeriesArr: %w", err) + var zero [][]int + return zero, fmt.Errorf("query GenSeriesArr: %w", err) } return pgx.CollectRows(rows, pgx.RowTo[[]int]) @@ -144,7 +148,8 @@ func (q *DBQuerier) GenSeriesStr1(ctx context.Context) (*string, error) { ctx = context.WithValue(ctx, QueryName{}, "GenSeriesStr1") rows, err := q.conn.Query(ctx, genSeriesStr1SQL) if err != nil { - return nil, fmt.Errorf("query GenSeriesStr1: %w", err) + var zero *string + return zero, fmt.Errorf("query GenSeriesStr1: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*string]) @@ -158,7 +163,8 @@ func (q *DBQuerier) GenSeriesStr(ctx context.Context) ([]*string, error) { ctx = context.WithValue(ctx, QueryName{}, "GenSeriesStr") rows, err := q.conn.Query(ctx, genSeriesStrSQL) if err != nil { - return nil, fmt.Errorf("query GenSeriesStr: %w", err) + var zero []*string + return zero, fmt.Errorf("query GenSeriesStr: %w", err) } return pgx.CollectRows(rows, pgx.RowTo[*string]) diff --git a/example/inline_param_count/inline0/query.sql.go b/example/inline_param_count/inline0/query.sql.go index 0a7aa26f..e69deb7c 100644 --- a/example/inline_param_count/inline0/query.sql.go +++ b/example/inline_param_count/inline0/query.sql.go @@ -85,7 +85,8 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { ctx = context.WithValue(ctx, QueryName{}, "CountAuthors") rows, err := q.conn.Query(ctx, countAuthorsSQL) if err != nil { - return nil, fmt.Errorf("query CountAuthors: %w", err) + var zero *int + return zero, fmt.Errorf("query CountAuthors: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*int]) @@ -109,7 +110,8 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, params FindAuthorByIDPar ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") rows, err := q.conn.Query(ctx, findAuthorByIDSQL, params.AuthorID) if err != nil { - return FindAuthorByIDRow{}, fmt.Errorf("query FindAuthorByID: %w", err) + var zero FindAuthorByIDRow + return zero, fmt.Errorf("query FindAuthorByID: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) @@ -129,7 +131,8 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, params InsertAuthorParams) ctx = context.WithValue(ctx, QueryName{}, "InsertAuthor") rows, err := q.conn.Query(ctx, insertAuthorSQL, params.FirstName, params.LastName) if err != nil { - return 0, fmt.Errorf("query InsertAuthor: %w", err) + var zero int32 + return zero, fmt.Errorf("query InsertAuthor: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int32]) diff --git a/example/inline_param_count/inline1/query.sql.go b/example/inline_param_count/inline1/query.sql.go index 4a09e23f..a7b486bb 100644 --- a/example/inline_param_count/inline1/query.sql.go +++ b/example/inline_param_count/inline1/query.sql.go @@ -85,7 +85,8 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { ctx = context.WithValue(ctx, QueryName{}, "CountAuthors") rows, err := q.conn.Query(ctx, countAuthorsSQL) if err != nil { - return nil, fmt.Errorf("query CountAuthors: %w", err) + var zero *int + return zero, fmt.Errorf("query CountAuthors: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*int]) @@ -105,7 +106,8 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") rows, err := q.conn.Query(ctx, findAuthorByIDSQL, authorID) if err != nil { - return FindAuthorByIDRow{}, fmt.Errorf("query FindAuthorByID: %w", err) + var zero FindAuthorByIDRow + return zero, fmt.Errorf("query FindAuthorByID: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) @@ -125,7 +127,8 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, params InsertAuthorParams) ctx = context.WithValue(ctx, QueryName{}, "InsertAuthor") rows, err := q.conn.Query(ctx, insertAuthorSQL, params.FirstName, params.LastName) if err != nil { - return 0, fmt.Errorf("query InsertAuthor: %w", err) + var zero int32 + return zero, fmt.Errorf("query InsertAuthor: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int32]) diff --git a/example/inline_param_count/inline2/query.sql.go b/example/inline_param_count/inline2/query.sql.go index 3408f117..909ce1c3 100644 --- a/example/inline_param_count/inline2/query.sql.go +++ b/example/inline_param_count/inline2/query.sql.go @@ -85,7 +85,8 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { ctx = context.WithValue(ctx, QueryName{}, "CountAuthors") rows, err := q.conn.Query(ctx, countAuthorsSQL) if err != nil { - return nil, fmt.Errorf("query CountAuthors: %w", err) + var zero *int + return zero, fmt.Errorf("query CountAuthors: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*int]) @@ -105,7 +106,8 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") rows, err := q.conn.Query(ctx, findAuthorByIDSQL, authorID) if err != nil { - return FindAuthorByIDRow{}, fmt.Errorf("query FindAuthorByID: %w", err) + var zero FindAuthorByIDRow + return zero, fmt.Errorf("query FindAuthorByID: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) @@ -120,7 +122,8 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, firstName string, lastName ctx = context.WithValue(ctx, QueryName{}, "InsertAuthor") rows, err := q.conn.Query(ctx, insertAuthorSQL, firstName, lastName) if err != nil { - return 0, fmt.Errorf("query InsertAuthor: %w", err) + var zero int32 + return zero, fmt.Errorf("query InsertAuthor: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int32]) diff --git a/example/inline_param_count/inline3/query.sql.go b/example/inline_param_count/inline3/query.sql.go index c19d9f29..d903de87 100644 --- a/example/inline_param_count/inline3/query.sql.go +++ b/example/inline_param_count/inline3/query.sql.go @@ -85,7 +85,8 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { ctx = context.WithValue(ctx, QueryName{}, "CountAuthors") rows, err := q.conn.Query(ctx, countAuthorsSQL) if err != nil { - return nil, fmt.Errorf("query CountAuthors: %w", err) + var zero *int + return zero, fmt.Errorf("query CountAuthors: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*int]) @@ -105,7 +106,8 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") rows, err := q.conn.Query(ctx, findAuthorByIDSQL, authorID) if err != nil { - return FindAuthorByIDRow{}, fmt.Errorf("query FindAuthorByID: %w", err) + var zero FindAuthorByIDRow + return zero, fmt.Errorf("query FindAuthorByID: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) @@ -120,7 +122,8 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, firstName string, lastName ctx = context.WithValue(ctx, QueryName{}, "InsertAuthor") rows, err := q.conn.Query(ctx, insertAuthorSQL, firstName, lastName) if err != nil { - return 0, fmt.Errorf("query InsertAuthor: %w", err) + var zero int32 + return zero, fmt.Errorf("query InsertAuthor: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int32]) diff --git a/example/ltree/query.sql.go b/example/ltree/query.sql.go index d771c53d..747f0d2d 100644 --- a/example/ltree/query.sql.go +++ b/example/ltree/query.sql.go @@ -84,7 +84,8 @@ func (q *DBQuerier) FindTopScienceChildren(ctx context.Context) ([]pgtype.Text, ctx = context.WithValue(ctx, QueryName{}, "FindTopScienceChildren") rows, err := q.conn.Query(ctx, findTopScienceChildrenSQL) if err != nil { - return nil, fmt.Errorf("query FindTopScienceChildren: %w", err) + var zero []pgtype.Text + return zero, fmt.Errorf("query FindTopScienceChildren: %w", err) } return pgx.CollectRows(rows, pgx.RowTo[pgtype.Text]) @@ -99,7 +100,8 @@ func (q *DBQuerier) FindTopScienceChildrenAgg(ctx context.Context) (pgtype.Array ctx = context.WithValue(ctx, QueryName{}, "FindTopScienceChildrenAgg") rows, err := q.conn.Query(ctx, findTopScienceChildrenAggSQL) if err != nil { - return pgtype.Array[pgtype.Text]{}, fmt.Errorf("query FindTopScienceChildrenAgg: %w", err) + var zero pgtype.Array[pgtype.Text] + return zero, fmt.Errorf("query FindTopScienceChildrenAgg: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[pgtype.Array[pgtype.Text]]) @@ -151,7 +153,8 @@ func (q *DBQuerier) FindLtreeInput(ctx context.Context, inLtree pgtype.Text, inL ctx = context.WithValue(ctx, QueryName{}, "FindLtreeInput") rows, err := q.conn.Query(ctx, findLtreeInputSQL, inLtree, inLtreeArray) if err != nil { - return FindLtreeInputRow{}, fmt.Errorf("query FindLtreeInput: %w", err) + var zero FindLtreeInputRow + return zero, fmt.Errorf("query FindLtreeInput: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindLtreeInputRow]) diff --git a/example/nested/query.sql.go b/example/nested/query.sql.go index a355faf8..1a110bf0 100644 --- a/example/nested/query.sql.go +++ b/example/nested/query.sql.go @@ -108,7 +108,8 @@ func (q *DBQuerier) ArrayNested2(ctx context.Context) ([]ProductImageType, error ctx = context.WithValue(ctx, QueryName{}, "ArrayNested2") rows, err := q.conn.Query(ctx, arrayNested2SQL) if err != nil { - return nil, fmt.Errorf("query ArrayNested2: %w", err) + var zero []ProductImageType + return zero, fmt.Errorf("query ArrayNested2: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]ProductImageType]) @@ -129,7 +130,8 @@ func (q *DBQuerier) Nested3(ctx context.Context) ([]ProductImageSetType, error) ctx = context.WithValue(ctx, QueryName{}, "Nested3") rows, err := q.conn.Query(ctx, nested3SQL) if err != nil { - return nil, fmt.Errorf("query Nested3: %w", err) + var zero []ProductImageSetType + return zero, fmt.Errorf("query Nested3: %w", err) } return pgx.CollectRows(rows, pgx.RowTo[ProductImageSetType]) diff --git a/example/pgcrypto/query.sql.go b/example/pgcrypto/query.sql.go index 3eb98c33..9ba5af04 100644 --- a/example/pgcrypto/query.sql.go +++ b/example/pgcrypto/query.sql.go @@ -96,7 +96,8 @@ func (q *DBQuerier) FindUser(ctx context.Context, email string) (FindUserRow, er ctx = context.WithValue(ctx, QueryName{}, "FindUser") rows, err := q.conn.Query(ctx, findUserSQL, email) if err != nil { - return FindUserRow{}, fmt.Errorf("query FindUser: %w", err) + var zero FindUserRow + return zero, fmt.Errorf("query FindUser: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindUserRow]) diff --git a/example/separate_out_dir/out/alpha_query.sql.0.go b/example/separate_out_dir/out/alpha_query.sql.0.go index a23c0727..5c185fb8 100644 --- a/example/separate_out_dir/out/alpha_query.sql.0.go +++ b/example/separate_out_dir/out/alpha_query.sql.0.go @@ -90,7 +90,8 @@ func (q *DBQuerier) AlphaNested(ctx context.Context) (string, error) { ctx = context.WithValue(ctx, QueryName{}, "AlphaNested") rows, err := q.conn.Query(ctx, alphaNestedSQL) if err != nil { - return "", fmt.Errorf("query AlphaNested: %w", err) + var zero string + return zero, fmt.Errorf("query AlphaNested: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) @@ -103,7 +104,8 @@ func (q *DBQuerier) AlphaCompositeArray(ctx context.Context) ([]Alpha, error) { ctx = context.WithValue(ctx, QueryName{}, "AlphaCompositeArray") rows, err := q.conn.Query(ctx, alphaCompositeArraySQL) if err != nil { - return nil, fmt.Errorf("query AlphaCompositeArray: %w", err) + var zero []Alpha + return zero, fmt.Errorf("query AlphaCompositeArray: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]Alpha]) diff --git a/example/separate_out_dir/out/alpha_query.sql.1.go b/example/separate_out_dir/out/alpha_query.sql.1.go index ae6dcfed..eef44b67 100644 --- a/example/separate_out_dir/out/alpha_query.sql.1.go +++ b/example/separate_out_dir/out/alpha_query.sql.1.go @@ -15,7 +15,8 @@ func (q *DBQuerier) Alpha(ctx context.Context) (string, error) { ctx = context.WithValue(ctx, QueryName{}, "Alpha") rows, err := q.conn.Query(ctx, alphaSQL) if err != nil { - return "", fmt.Errorf("query Alpha: %w", err) + var zero string + return zero, fmt.Errorf("query Alpha: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) diff --git a/example/separate_out_dir/out/bravo_query.sql.go b/example/separate_out_dir/out/bravo_query.sql.go index 876f6e9e..18968ef0 100644 --- a/example/separate_out_dir/out/bravo_query.sql.go +++ b/example/separate_out_dir/out/bravo_query.sql.go @@ -15,7 +15,8 @@ func (q *DBQuerier) Bravo(ctx context.Context) (string, error) { ctx = context.WithValue(ctx, QueryName{}, "Bravo") rows, err := q.conn.Query(ctx, bravoSQL) if err != nil { - return "", fmt.Errorf("query Bravo: %w", err) + var zero string + return zero, fmt.Errorf("query Bravo: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) diff --git a/example/slices/query.sql.go b/example/slices/query.sql.go index 2fb73c70..628324b5 100644 --- a/example/slices/query.sql.go +++ b/example/slices/query.sql.go @@ -82,7 +82,8 @@ func (q *DBQuerier) GetBools(ctx context.Context, data []bool) ([]bool, error) { ctx = context.WithValue(ctx, QueryName{}, "GetBools") rows, err := q.conn.Query(ctx, getBoolsSQL, data) if err != nil { - return nil, fmt.Errorf("query GetBools: %w", err) + var zero []bool + return zero, fmt.Errorf("query GetBools: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]bool]) @@ -95,7 +96,8 @@ func (q *DBQuerier) GetOneTimestamp(ctx context.Context, data *time.Time) (*time ctx = context.WithValue(ctx, QueryName{}, "GetOneTimestamp") rows, err := q.conn.Query(ctx, getOneTimestampSQL, data) if err != nil { - return nil, fmt.Errorf("query GetOneTimestamp: %w", err) + var zero *time.Time + return zero, fmt.Errorf("query GetOneTimestamp: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*time.Time]) @@ -109,7 +111,8 @@ func (q *DBQuerier) GetManyTimestamptzs(ctx context.Context, data []time.Time) ( ctx = context.WithValue(ctx, QueryName{}, "GetManyTimestamptzs") rows, err := q.conn.Query(ctx, getManyTimestamptzsSQL, data) if err != nil { - return nil, fmt.Errorf("query GetManyTimestamptzs: %w", err) + var zero []*time.Time + return zero, fmt.Errorf("query GetManyTimestamptzs: %w", err) } return pgx.CollectRows(rows, pgx.RowTo[*time.Time]) @@ -123,7 +126,8 @@ func (q *DBQuerier) GetManyTimestamps(ctx context.Context, data []*time.Time) ([ ctx = context.WithValue(ctx, QueryName{}, "GetManyTimestamps") rows, err := q.conn.Query(ctx, getManyTimestampsSQL, data) if err != nil { - return nil, fmt.Errorf("query GetManyTimestamps: %w", err) + var zero []*time.Time + return zero, fmt.Errorf("query GetManyTimestamps: %w", err) } return pgx.CollectRows(rows, pgx.RowTo[*time.Time]) diff --git a/example/syntax/query.sql.go b/example/syntax/query.sql.go index f085f89f..d160e0b6 100644 --- a/example/syntax/query.sql.go +++ b/example/syntax/query.sql.go @@ -113,7 +113,8 @@ func (q *DBQuerier) Backtick(ctx context.Context) (string, error) { ctx = context.WithValue(ctx, QueryName{}, "Backtick") rows, err := q.conn.Query(ctx, backtickSQL) if err != nil { - return "", fmt.Errorf("query Backtick: %w", err) + var zero string + return zero, fmt.Errorf("query Backtick: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) @@ -126,7 +127,8 @@ func (q *DBQuerier) BacktickQuoteBacktick(ctx context.Context) (string, error) { ctx = context.WithValue(ctx, QueryName{}, "BacktickQuoteBacktick") rows, err := q.conn.Query(ctx, backtickQuoteBacktickSQL) if err != nil { - return "", fmt.Errorf("query BacktickQuoteBacktick: %w", err) + var zero string + return zero, fmt.Errorf("query BacktickQuoteBacktick: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) @@ -139,7 +141,8 @@ func (q *DBQuerier) BacktickNewline(ctx context.Context) (string, error) { ctx = context.WithValue(ctx, QueryName{}, "BacktickNewline") rows, err := q.conn.Query(ctx, backtickNewlineSQL) if err != nil { - return "", fmt.Errorf("query BacktickNewline: %w", err) + var zero string + return zero, fmt.Errorf("query BacktickNewline: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) @@ -152,7 +155,8 @@ func (q *DBQuerier) BacktickDoubleQuote(ctx context.Context) (string, error) { ctx = context.WithValue(ctx, QueryName{}, "BacktickDoubleQuote") rows, err := q.conn.Query(ctx, backtickDoubleQuoteSQL) if err != nil { - return "", fmt.Errorf("query BacktickDoubleQuote: %w", err) + var zero string + return zero, fmt.Errorf("query BacktickDoubleQuote: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) @@ -165,7 +169,8 @@ func (q *DBQuerier) BacktickBackslashN(ctx context.Context) (string, error) { ctx = context.WithValue(ctx, QueryName{}, "BacktickBackslashN") rows, err := q.conn.Query(ctx, backtickBackslashNSQL) if err != nil { - return "", fmt.Errorf("query BacktickBackslashN: %w", err) + var zero string + return zero, fmt.Errorf("query BacktickBackslashN: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) @@ -183,7 +188,8 @@ func (q *DBQuerier) IllegalNameSymbols(ctx context.Context, helloWorld string) ( ctx = context.WithValue(ctx, QueryName{}, "IllegalNameSymbols") rows, err := q.conn.Query(ctx, illegalNameSymbolsSQL, helloWorld) if err != nil { - return IllegalNameSymbolsRow{}, fmt.Errorf("query IllegalNameSymbols: %w", err) + var zero IllegalNameSymbolsRow + return zero, fmt.Errorf("query IllegalNameSymbols: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[IllegalNameSymbolsRow]) @@ -196,7 +202,8 @@ func (q *DBQuerier) SpaceAfter(ctx context.Context, space string) (string, error ctx = context.WithValue(ctx, QueryName{}, "SpaceAfter") rows, err := q.conn.Query(ctx, spaceAfterSQL, space) if err != nil { - return "", fmt.Errorf("query SpaceAfter: %w", err) + var zero string + return zero, fmt.Errorf("query SpaceAfter: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) @@ -209,7 +216,8 @@ func (q *DBQuerier) BadEnumName(ctx context.Context) (UnnamedEnum123, error) { ctx = context.WithValue(ctx, QueryName{}, "BadEnumName") rows, err := q.conn.Query(ctx, badEnumNameSQL) if err != nil { - return "", fmt.Errorf("query BadEnumName: %w", err) + var zero UnnamedEnum123 + return zero, fmt.Errorf("query BadEnumName: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[UnnamedEnum123]) @@ -222,7 +230,8 @@ func (q *DBQuerier) GoKeyword(ctx context.Context, go_ string) (string, error) { ctx = context.WithValue(ctx, QueryName{}, "GoKeyword") rows, err := q.conn.Query(ctx, goKeywordSQL, go_) if err != nil { - return "", fmt.Errorf("query GoKeyword: %w", err) + var zero string + return zero, fmt.Errorf("query GoKeyword: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) diff --git a/example/void/query.sql.go b/example/void/query.sql.go index 94790852..140cc346 100644 --- a/example/void/query.sql.go +++ b/example/void/query.sql.go @@ -107,7 +107,8 @@ func (q *DBQuerier) VoidTwo(ctx context.Context) (string, error) { ctx = context.WithValue(ctx, QueryName{}, "VoidTwo") rows, err := q.conn.Query(ctx, voidTwoSQL) if err != nil { - return "", fmt.Errorf("query VoidTwo: %w", err) + var zero string + return zero, fmt.Errorf("query VoidTwo: %w", err) } return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (string, error) { @@ -131,7 +132,8 @@ func (q *DBQuerier) VoidThree(ctx context.Context) (VoidThreeRow, error) { ctx = context.WithValue(ctx, QueryName{}, "VoidThree") rows, err := q.conn.Query(ctx, voidThreeSQL) if err != nil { - return VoidThreeRow{}, fmt.Errorf("query VoidThree: %w", err) + var zero VoidThreeRow + return zero, fmt.Errorf("query VoidThree: %w", err) } return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (VoidThreeRow, error) { @@ -150,7 +152,8 @@ func (q *DBQuerier) VoidThree2(ctx context.Context) ([]string, error) { ctx = context.WithValue(ctx, QueryName{}, "VoidThree2") rows, err := q.conn.Query(ctx, voidThree2SQL) if err != nil { - return nil, fmt.Errorf("query VoidThree2: %w", err) + var zero []string + return zero, fmt.Errorf("query VoidThree2: %w", err) } return pgx.CollectRows(rows, func(row pgx.CollectableRow) (string, error) { diff --git a/internal/codegen/golang/query.gotemplate b/internal/codegen/golang/query.gotemplate index 4b8729e8..3091030d 100644 --- a/internal/codegen/golang/query.gotemplate +++ b/internal/codegen/golang/query.gotemplate @@ -65,7 +65,8 @@ func (q *DBQuerier) {{ $q.Name }}(ctx context.Context {{- $q.EmitParams }}) ({{ {{- else }} rows, err := q.conn.Query(ctx, {{ $q.SQLVarName }} {{- $q.EmitParamNames }}) if err != nil { - return {{ $q.EmitZeroResult }}, fmt.Errorf("query {{ $q.Name }}: %w", err) + {{ $q.EmitZeroResultInit "zero" }} + return zero, fmt.Errorf("query {{ $q.Name }}: %w", err) } return {{ $q.EmitCollectionFunc }}(rows, {{ $q.EmitRowMapper }}) diff --git a/internal/codegen/golang/templated_file.go b/internal/codegen/golang/templated_file.go index 674eadeb..2f8e4685 100644 --- a/internal/codegen/golang/templated_file.go +++ b/internal/codegen/golang/templated_file.go @@ -392,6 +392,15 @@ func (tq TemplatedQuery) EmitResultTypeInit(name string) (string, error) { } } +// EmitZeroResultInit returns a var declaration for the zero value of a result. +func (tq TemplatedQuery) EmitZeroResultInit(name string) (string, error) { + result, err := tq.EmitResultType() + if err != nil { + return "", fmt.Errorf("create result type for EmitZeroResultInit: %w", err) + } + return "var " + name + " " + result, nil +} + // EmitZeroResult returns the string representing the zero value of a result. func (tq TemplatedQuery) EmitZeroResult() (string, error) { switch tq.ResultKind { @@ -421,7 +430,7 @@ func (tq TemplatedQuery) EmitZeroResult() (string, error) { case "bool": return "false", nil default: - return tq.Outputs[0].QualType + "{}", nil // won't work for type Foo int + return "*new(" + tq.Outputs[0].QualType + ")", nil } default: return "", fmt.Errorf("unhandled EmitZeroResult kind: %s", tq.ResultKind) diff --git a/internal/pg/query.sql.go b/internal/pg/query.sql.go index e07077b7..2375efcf 100644 --- a/internal/pg/query.sql.go +++ b/internal/pg/query.sql.go @@ -144,7 +144,8 @@ func (q *DBQuerier) FindEnumTypes(ctx context.Context, oids []uint32) ([]FindEnu ctx = context.WithValue(ctx, QueryName{}, "FindEnumTypes") rows, err := q.conn.Query(ctx, findEnumTypesSQL, oids) if err != nil { - return nil, fmt.Errorf("query FindEnumTypes: %w", err) + var zero []FindEnumTypesRow + return zero, fmt.Errorf("query FindEnumTypes: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[FindEnumTypesRow]) @@ -191,7 +192,8 @@ func (q *DBQuerier) FindArrayTypes(ctx context.Context, oids []uint32) ([]FindAr ctx = context.WithValue(ctx, QueryName{}, "FindArrayTypes") rows, err := q.conn.Query(ctx, findArrayTypesSQL, oids) if err != nil { - return nil, fmt.Errorf("query FindArrayTypes: %w", err) + var zero []FindArrayTypesRow + return zero, fmt.Errorf("query FindArrayTypes: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[FindArrayTypesRow]) @@ -243,7 +245,8 @@ func (q *DBQuerier) FindCompositeTypes(ctx context.Context, oids []uint32) ([]Fi ctx = context.WithValue(ctx, QueryName{}, "FindCompositeTypes") rows, err := q.conn.Query(ctx, findCompositeTypesSQL, oids) if err != nil { - return nil, fmt.Errorf("query FindCompositeTypes: %w", err) + var zero []FindCompositeTypesRow + return zero, fmt.Errorf("query FindCompositeTypes: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[FindCompositeTypesRow]) @@ -282,7 +285,8 @@ func (q *DBQuerier) FindDescendantOIDs(ctx context.Context, oids []uint32) ([]ui ctx = context.WithValue(ctx, QueryName{}, "FindDescendantOIDs") rows, err := q.conn.Query(ctx, findDescendantOIDsSQL, oids) if err != nil { - return nil, fmt.Errorf("query FindDescendantOIDs: %w", err) + var zero []uint32 + return zero, fmt.Errorf("query FindDescendantOIDs: %w", err) } return pgx.CollectRows(rows, pgx.RowTo[uint32]) @@ -299,7 +303,8 @@ func (q *DBQuerier) FindOIDByName(ctx context.Context, name string) (uint32, err ctx = context.WithValue(ctx, QueryName{}, "FindOIDByName") rows, err := q.conn.Query(ctx, findOIDByNameSQL, name) if err != nil { - return 0, fmt.Errorf("query FindOIDByName: %w", err) + var zero uint32 + return zero, fmt.Errorf("query FindOIDByName: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[uint32]) @@ -314,7 +319,8 @@ func (q *DBQuerier) FindOIDName(ctx context.Context, oid uint32) (string, error) ctx = context.WithValue(ctx, QueryName{}, "FindOIDName") rows, err := q.conn.Query(ctx, findOIDNameSQL, oid) if err != nil { - return "", fmt.Errorf("query FindOIDName: %w", err) + var zero string + return zero, fmt.Errorf("query FindOIDName: %w", err) } return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) @@ -335,7 +341,8 @@ func (q *DBQuerier) FindOIDNames(ctx context.Context, oid []uint32) ([]FindOIDNa ctx = context.WithValue(ctx, QueryName{}, "FindOIDNames") rows, err := q.conn.Query(ctx, findOIDNamesSQL, oid) if err != nil { - return nil, fmt.Errorf("query FindOIDNames: %w", err) + var zero []FindOIDNamesRow + return zero, fmt.Errorf("query FindOIDNames: %w", err) } return pgx.CollectRows(rows, pgx.RowToStructByName[FindOIDNamesRow]) From 24c47e8eb95c973dd04dae0ecd17fe064c56688d Mon Sep 17 00:00:00 2001 From: meoyawn Date: Wed, 6 May 2026 21:23:30 +0000 Subject: [PATCH 14/27] Use CollectOneRow for one queries --- example/author/query.sql | 4 +++ example/author/query.sql.go | 34 ++++++++++++++++--- example/author/query.sql_test.go | 18 ++++++++++ example/complex_params/query.sql.go | 10 +++--- example/composite/query.sql.go | 6 ++-- example/custom_types/query.sql.go | 6 ++-- example/device/query.sql.go | 4 +-- example/domain/query.sql.go | 2 +- example/enums/query.sql.go | 4 +-- example/erp/order/customer.sql.go | 6 ++-- example/go_pointer_types/query.sql.go | 6 ++-- .../inline_param_count/inline0/query.sql.go | 6 ++-- .../inline_param_count/inline1/query.sql.go | 6 ++-- .../inline_param_count/inline2/query.sql.go | 6 ++-- .../inline_param_count/inline3/query.sql.go | 6 ++-- example/ltree/query.sql.go | 4 +-- example/nested/query.sql.go | 2 +- example/pgcrypto/query.sql.go | 2 +- .../separate_out_dir/out/alpha_query.sql.0.go | 4 +-- .../separate_out_dir/out/alpha_query.sql.1.go | 2 +- .../separate_out_dir/out/bravo_query.sql.go | 2 +- example/slices/query.sql.go | 4 +-- example/syntax/query.sql.go | 18 +++++----- example/void/query.sql.go | 4 +-- internal/codegen/golang/templated_file.go | 2 +- internal/pg/query.sql.go | 4 +-- 26 files changed, 109 insertions(+), 63 deletions(-) diff --git a/example/author/query.sql b/example/author/query.sql index a312ae24..963cd109 100644 --- a/example/author/query.sql +++ b/example/author/query.sql @@ -14,6 +14,10 @@ SELECT first_name, last_name FROM author ORDER BY author_id = pggen.arg('AuthorI -- name: FindFirstNames :many SELECT first_name FROM author ORDER BY author_id = pggen.arg('AuthorID'); +-- FindFirstAuthor finds the first author by ID. +-- name: FindFirstAuthor :one +SELECT * FROM author ORDER BY author_id; + -- DeleteAuthors deletes authors with a first name of "joe". -- name: DeleteAuthors :exec DELETE FROM author WHERE first_name = 'joe'; diff --git a/example/author/query.sql.go b/example/author/query.sql.go index 45ed19d2..7e96c7b9 100644 --- a/example/author/query.sql.go +++ b/example/author/query.sql.go @@ -25,6 +25,9 @@ type Querier interface { // FindFirstNames finds one (or zero) authors by ID. FindFirstNames(ctx context.Context, authorID int32) ([]*string, error) + // FindFirstAuthor finds the first author by ID. + FindFirstAuthor(ctx context.Context) (FindFirstAuthorRow, error) + // DeleteAuthors deletes authors with a first name of "joe". DeleteAuthors(ctx context.Context) (pgconn.CommandTag, error) @@ -116,7 +119,7 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut return zero, fmt.Errorf("query FindAuthorByID: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) } const findAuthorsSQL = `SELECT * FROM author WHERE first_name = $1;` @@ -173,6 +176,27 @@ func (q *DBQuerier) FindFirstNames(ctx context.Context, authorID int32) ([]*stri return pgx.CollectRows(rows, pgx.RowTo[*string]) } +const findFirstAuthorSQL = `SELECT * FROM author ORDER BY author_id;` + +type FindFirstAuthorRow struct { + AuthorID *int32 `json:"author_id" db:"author_id"` + FirstName *string `json:"first_name" db:"first_name"` + LastName *string `json:"last_name" db:"last_name"` + Suffix *string `json:"suffix" db:"suffix"` +} + +// FindFirstAuthor implements Querier.FindFirstAuthor. +func (q *DBQuerier) FindFirstAuthor(ctx context.Context) (FindFirstAuthorRow, error) { + ctx = context.WithValue(ctx, QueryName{}, "FindFirstAuthor") + rows, err := q.conn.Query(ctx, findFirstAuthorSQL) + if err != nil { + var zero FindFirstAuthorRow + return zero, fmt.Errorf("query FindFirstAuthor: %w", err) + } + + return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindFirstAuthorRow]) +} + const deleteAuthorsSQL = `DELETE FROM author WHERE first_name = 'joe';` // DeleteAuthors implements Querier.DeleteAuthors. @@ -232,7 +256,7 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, firstName string, lastName return zero, fmt.Errorf("query InsertAuthor: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int32]) + return pgx.CollectOneRow(rows, pgx.RowTo[int32]) } const insertAuthorSuffixSQL = `INSERT INTO author (first_name, last_name, suffix) @@ -261,7 +285,7 @@ func (q *DBQuerier) InsertAuthorSuffix(ctx context.Context, params InsertAuthorS return zero, fmt.Errorf("query InsertAuthorSuffix: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[InsertAuthorSuffixRow]) + return pgx.CollectOneRow(rows, pgx.RowToStructByName[InsertAuthorSuffixRow]) } const stringAggFirstNameSQL = `SELECT string_agg(first_name, ',') AS names FROM author WHERE author_id = $1;` @@ -275,7 +299,7 @@ func (q *DBQuerier) StringAggFirstName(ctx context.Context, authorID int32) (*st return zero, fmt.Errorf("query StringAggFirstName: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*string]) + return pgx.CollectOneRow(rows, pgx.RowTo[*string]) } const arrayAggFirstNameSQL = `SELECT array_agg(first_name) AS names FROM author WHERE author_id = $1;` @@ -289,5 +313,5 @@ func (q *DBQuerier) ArrayAggFirstName(ctx context.Context, authorID int32) ([]st return zero, fmt.Errorf("query ArrayAggFirstName: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]string]) + return pgx.CollectOneRow(rows, pgx.RowTo[[]string]) } diff --git a/example/author/query.sql_test.go b/example/author/query.sql_test.go index 2e15de91..3c32f710 100644 --- a/example/author/query.sql_test.go +++ b/example/author/query.sql_test.go @@ -115,6 +115,24 @@ func TestNewQuerier_FindFirstNames(t *testing.T) { }) } +func TestNewQuerier_FindFirstAuthor(t *testing.T) { + conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) + defer cleanup() + + q := NewQuerier(conn) + adamsID := insertAuthor(t, q, "john", "adams") + insertAuthor(t, q, "george", "washington") + + author, err := q.FindFirstAuthor(t.Context()) + require.NoError(t, err) + assert.Equal(t, FindFirstAuthorRow{ + AuthorID: ptrs.Int32(adamsID), + FirstName: ptrs.String("john"), + LastName: ptrs.String("adams"), + Suffix: nil, + }, author) +} + func TestNewQuerier_InsertAuthorSuffix(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() diff --git a/example/complex_params/query.sql.go b/example/complex_params/query.sql.go index b4aae0f7..70fbcf65 100644 --- a/example/complex_params/query.sql.go +++ b/example/complex_params/query.sql.go @@ -114,7 +114,7 @@ func (q *DBQuerier) ParamArrayInt(ctx context.Context, ints []int) ([]int, error return zero, fmt.Errorf("query ParamArrayInt: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]int]) + return pgx.CollectOneRow(rows, pgx.RowTo[[]int]) } const paramNested1SQL = `SELECT $1::dimensions;` @@ -128,7 +128,7 @@ func (q *DBQuerier) ParamNested1(ctx context.Context, dimensions Dimensions) (Di return zero, fmt.Errorf("query ParamNested1: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[Dimensions]) + return pgx.CollectOneRow(rows, pgx.RowTo[Dimensions]) } const paramNested2SQL = `SELECT $1::product_image_type;` @@ -142,7 +142,7 @@ func (q *DBQuerier) ParamNested2(ctx context.Context, image ProductImageType) (P return zero, fmt.Errorf("query ParamNested2: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[ProductImageType]) + return pgx.CollectOneRow(rows, pgx.RowTo[ProductImageType]) } const paramNested2ArraySQL = `SELECT $1::product_image_type[];` @@ -156,7 +156,7 @@ func (q *DBQuerier) ParamNested2Array(ctx context.Context, images []ProductImage return zero, fmt.Errorf("query ParamNested2Array: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]ProductImageType]) + return pgx.CollectOneRow(rows, pgx.RowTo[[]ProductImageType]) } const paramNested3SQL = `SELECT $1::product_image_set_type;` @@ -170,5 +170,5 @@ func (q *DBQuerier) ParamNested3(ctx context.Context, imageSet ProductImageSetTy return zero, fmt.Errorf("query ParamNested3: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[ProductImageSetType]) + return pgx.CollectOneRow(rows, pgx.RowTo[ProductImageSetType]) } diff --git a/example/composite/query.sql.go b/example/composite/query.sql.go index e13e59b8..c98c2556 100644 --- a/example/composite/query.sql.go +++ b/example/composite/query.sql.go @@ -190,7 +190,7 @@ func (q *DBQuerier) InsertScreenshotBlocks(ctx context.Context, screenshotID int return zero, fmt.Errorf("query InsertScreenshotBlocks: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[InsertScreenshotBlocksRow]) + return pgx.CollectOneRow(rows, pgx.RowToStructByName[InsertScreenshotBlocksRow]) } const arraysInputSQL = `SELECT $1::arrays;` @@ -204,7 +204,7 @@ func (q *DBQuerier) ArraysInput(ctx context.Context, arrays Arrays) (Arrays, err return zero, fmt.Errorf("query ArraysInput: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[Arrays]) + return pgx.CollectOneRow(rows, pgx.RowTo[Arrays]) } const userEmailsSQL = `SELECT ('foo', 'bar@example.com')::user_email;` @@ -218,5 +218,5 @@ func (q *DBQuerier) UserEmails(ctx context.Context) (UserEmail, error) { return zero, fmt.Errorf("query UserEmails: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[UserEmail]) + return pgx.CollectOneRow(rows, pgx.RowTo[UserEmail]) } diff --git a/example/custom_types/query.sql.go b/example/custom_types/query.sql.go index 32c5ee4f..30d81d47 100644 --- a/example/custom_types/query.sql.go +++ b/example/custom_types/query.sql.go @@ -91,7 +91,7 @@ func (q *DBQuerier) CustomTypes(ctx context.Context) (CustomTypesRow, error) { return zero, fmt.Errorf("query CustomTypes: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CustomTypesRow]) + return pgx.CollectOneRow(rows, pgx.RowToStructByName[CustomTypesRow]) } const customStringSQL = `SELECT 'some_text'::text;` @@ -105,7 +105,7 @@ func (q *DBQuerier) CustomString(ctx context.Context) (mytype.String, error) { return zero, fmt.Errorf("query CustomString: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[mytype.String]) + return pgx.CollectOneRow(rows, pgx.RowTo[mytype.String]) } const customMyIntSQL = `SELECT '5'::my_int as int5;` @@ -119,7 +119,7 @@ func (q *DBQuerier) CustomMyInt(ctx context.Context) (int, error) { return zero, fmt.Errorf("query CustomMyInt: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int]) + return pgx.CollectOneRow(rows, pgx.RowTo[int]) } const intArraySQL = `SELECT ARRAY ['5', '6', '7']::int[] as ints;` diff --git a/example/device/query.sql.go b/example/device/query.sql.go index 73c7cb3a..5f62a7c0 100644 --- a/example/device/query.sql.go +++ b/example/device/query.sql.go @@ -166,7 +166,7 @@ func (q *DBQuerier) CompositeUserOne(ctx context.Context) (User, error) { return zero, fmt.Errorf("query CompositeUserOne: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[User]) + return pgx.CollectOneRow(rows, pgx.RowTo[User]) } const compositeUserOneTwoColsSQL = `SELECT 1 AS num, ROW (15, 'qux')::"user" AS "user";` @@ -185,7 +185,7 @@ func (q *DBQuerier) CompositeUserOneTwoCols(ctx context.Context) (CompositeUserO return zero, fmt.Errorf("query CompositeUserOneTwoCols: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CompositeUserOneTwoColsRow]) + return pgx.CollectOneRow(rows, pgx.RowToStructByName[CompositeUserOneTwoColsRow]) } const compositeUserManySQL = `SELECT ROW (15, 'qux')::"user" AS "user";` diff --git a/example/domain/query.sql.go b/example/domain/query.sql.go index 333ca36c..d2122ecc 100644 --- a/example/domain/query.sql.go +++ b/example/domain/query.sql.go @@ -79,5 +79,5 @@ func (q *DBQuerier) DomainOne(ctx context.Context) (string, error) { return zero, fmt.Errorf("query DomainOne: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) + return pgx.CollectOneRow(rows, pgx.RowTo[string]) } diff --git a/example/enums/query.sql.go b/example/enums/query.sql.go index e7b266f6..bc1e910c 100644 --- a/example/enums/query.sql.go +++ b/example/enums/query.sql.go @@ -153,7 +153,7 @@ func (q *DBQuerier) FindOneDeviceArray(ctx context.Context) ([]DeviceType, error return zero, fmt.Errorf("query FindOneDeviceArray: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]DeviceType]) + return pgx.CollectOneRow(rows, pgx.RowTo[[]DeviceType]) } const findManyDeviceArraySQL = `SELECT enum_range('ipad'::device_type, 'iot'::device_type) AS device_types @@ -204,5 +204,5 @@ func (q *DBQuerier) EnumInsideComposite(ctx context.Context) (Device, error) { return zero, fmt.Errorf("query EnumInsideComposite: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[Device]) + return pgx.CollectOneRow(rows, pgx.RowTo[Device]) } diff --git a/example/erp/order/customer.sql.go b/example/erp/order/customer.sql.go index 259365f5..c7da2c60 100644 --- a/example/erp/order/customer.sql.go +++ b/example/erp/order/customer.sql.go @@ -100,7 +100,7 @@ func (q *DBQuerier) CreateTenant(ctx context.Context, key string, name string) ( return zero, fmt.Errorf("query CreateTenant: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CreateTenantRow]) + return pgx.CollectOneRow(rows, pgx.RowToStructByName[CreateTenantRow]) } const findOrdersByCustomerSQL = `SELECT * @@ -176,7 +176,7 @@ func (q *DBQuerier) InsertCustomer(ctx context.Context, params InsertCustomerPar return zero, fmt.Errorf("query InsertCustomer: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[InsertCustomerRow]) + return pgx.CollectOneRow(rows, pgx.RowToStructByName[InsertCustomerRow]) } const insertOrderSQL = `INSERT INTO orders (order_date, order_total, customer_id) @@ -205,5 +205,5 @@ func (q *DBQuerier) InsertOrder(ctx context.Context, params InsertOrderParams) ( return zero, fmt.Errorf("query InsertOrder: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[InsertOrderRow]) + return pgx.CollectOneRow(rows, pgx.RowToStructByName[InsertOrderRow]) } diff --git a/example/go_pointer_types/query.sql.go b/example/go_pointer_types/query.sql.go index db8f6132..75625a4c 100644 --- a/example/go_pointer_types/query.sql.go +++ b/example/go_pointer_types/query.sql.go @@ -91,7 +91,7 @@ func (q *DBQuerier) GenSeries1(ctx context.Context) (*int, error) { return zero, fmt.Errorf("query GenSeries1: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*int]) + return pgx.CollectOneRow(rows, pgx.RowTo[*int]) } const genSeriesSQL = `SELECT n @@ -121,7 +121,7 @@ func (q *DBQuerier) GenSeriesArr1(ctx context.Context) ([]int, error) { return zero, fmt.Errorf("query GenSeriesArr1: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]int]) + return pgx.CollectOneRow(rows, pgx.RowTo[[]int]) } const genSeriesArrSQL = `SELECT array_agg(n) @@ -152,7 +152,7 @@ func (q *DBQuerier) GenSeriesStr1(ctx context.Context) (*string, error) { return zero, fmt.Errorf("query GenSeriesStr1: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*string]) + return pgx.CollectOneRow(rows, pgx.RowTo[*string]) } const genSeriesStrSQL = `SELECT n::text diff --git a/example/inline_param_count/inline0/query.sql.go b/example/inline_param_count/inline0/query.sql.go index e69deb7c..76a90e47 100644 --- a/example/inline_param_count/inline0/query.sql.go +++ b/example/inline_param_count/inline0/query.sql.go @@ -89,7 +89,7 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { return zero, fmt.Errorf("query CountAuthors: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*int]) + return pgx.CollectOneRow(rows, pgx.RowTo[*int]) } const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` @@ -114,7 +114,7 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, params FindAuthorByIDPar return zero, fmt.Errorf("query FindAuthorByID: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) } const insertAuthorSQL = `INSERT INTO author (first_name, last_name) @@ -135,7 +135,7 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, params InsertAuthorParams) return zero, fmt.Errorf("query InsertAuthor: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int32]) + return pgx.CollectOneRow(rows, pgx.RowTo[int32]) } const deleteAuthorsByFullNameSQL = `DELETE diff --git a/example/inline_param_count/inline1/query.sql.go b/example/inline_param_count/inline1/query.sql.go index a7b486bb..77bfe4a4 100644 --- a/example/inline_param_count/inline1/query.sql.go +++ b/example/inline_param_count/inline1/query.sql.go @@ -89,7 +89,7 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { return zero, fmt.Errorf("query CountAuthors: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*int]) + return pgx.CollectOneRow(rows, pgx.RowTo[*int]) } const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` @@ -110,7 +110,7 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut return zero, fmt.Errorf("query FindAuthorByID: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) } const insertAuthorSQL = `INSERT INTO author (first_name, last_name) @@ -131,7 +131,7 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, params InsertAuthorParams) return zero, fmt.Errorf("query InsertAuthor: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int32]) + return pgx.CollectOneRow(rows, pgx.RowTo[int32]) } const deleteAuthorsByFullNameSQL = `DELETE diff --git a/example/inline_param_count/inline2/query.sql.go b/example/inline_param_count/inline2/query.sql.go index 909ce1c3..b4d05f3b 100644 --- a/example/inline_param_count/inline2/query.sql.go +++ b/example/inline_param_count/inline2/query.sql.go @@ -89,7 +89,7 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { return zero, fmt.Errorf("query CountAuthors: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*int]) + return pgx.CollectOneRow(rows, pgx.RowTo[*int]) } const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` @@ -110,7 +110,7 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut return zero, fmt.Errorf("query FindAuthorByID: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) } const insertAuthorSQL = `INSERT INTO author (first_name, last_name) @@ -126,7 +126,7 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, firstName string, lastName return zero, fmt.Errorf("query InsertAuthor: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int32]) + return pgx.CollectOneRow(rows, pgx.RowTo[int32]) } const deleteAuthorsByFullNameSQL = `DELETE diff --git a/example/inline_param_count/inline3/query.sql.go b/example/inline_param_count/inline3/query.sql.go index d903de87..4dfb3550 100644 --- a/example/inline_param_count/inline3/query.sql.go +++ b/example/inline_param_count/inline3/query.sql.go @@ -89,7 +89,7 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { return zero, fmt.Errorf("query CountAuthors: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*int]) + return pgx.CollectOneRow(rows, pgx.RowTo[*int]) } const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` @@ -110,7 +110,7 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut return zero, fmt.Errorf("query FindAuthorByID: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) } const insertAuthorSQL = `INSERT INTO author (first_name, last_name) @@ -126,7 +126,7 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, firstName string, lastName return zero, fmt.Errorf("query InsertAuthor: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[int32]) + return pgx.CollectOneRow(rows, pgx.RowTo[int32]) } const deleteAuthorsByFullNameSQL = `DELETE diff --git a/example/ltree/query.sql.go b/example/ltree/query.sql.go index 747f0d2d..3e3fbc87 100644 --- a/example/ltree/query.sql.go +++ b/example/ltree/query.sql.go @@ -104,7 +104,7 @@ func (q *DBQuerier) FindTopScienceChildrenAgg(ctx context.Context) (pgtype.Array return zero, fmt.Errorf("query FindTopScienceChildrenAgg: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[pgtype.Array[pgtype.Text]]) + return pgx.CollectOneRow(rows, pgx.RowTo[pgtype.Array[pgtype.Text]]) } const insertSampleDataSQL = `INSERT INTO test @@ -157,5 +157,5 @@ func (q *DBQuerier) FindLtreeInput(ctx context.Context, inLtree pgtype.Text, inL return zero, fmt.Errorf("query FindLtreeInput: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindLtreeInputRow]) + return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindLtreeInputRow]) } diff --git a/example/nested/query.sql.go b/example/nested/query.sql.go index 1a110bf0..1452f753 100644 --- a/example/nested/query.sql.go +++ b/example/nested/query.sql.go @@ -112,7 +112,7 @@ func (q *DBQuerier) ArrayNested2(ctx context.Context) ([]ProductImageType, error return zero, fmt.Errorf("query ArrayNested2: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]ProductImageType]) + return pgx.CollectOneRow(rows, pgx.RowTo[[]ProductImageType]) } const nested3SQL = `SELECT diff --git a/example/pgcrypto/query.sql.go b/example/pgcrypto/query.sql.go index 9ba5af04..99696518 100644 --- a/example/pgcrypto/query.sql.go +++ b/example/pgcrypto/query.sql.go @@ -100,5 +100,5 @@ func (q *DBQuerier) FindUser(ctx context.Context, email string) (FindUserRow, er return zero, fmt.Errorf("query FindUser: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[FindUserRow]) + return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindUserRow]) } diff --git a/example/separate_out_dir/out/alpha_query.sql.0.go b/example/separate_out_dir/out/alpha_query.sql.0.go index 5c185fb8..aba2656e 100644 --- a/example/separate_out_dir/out/alpha_query.sql.0.go +++ b/example/separate_out_dir/out/alpha_query.sql.0.go @@ -94,7 +94,7 @@ func (q *DBQuerier) AlphaNested(ctx context.Context) (string, error) { return zero, fmt.Errorf("query AlphaNested: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) + return pgx.CollectOneRow(rows, pgx.RowTo[string]) } const alphaCompositeArraySQL = `SELECT ARRAY[ROW('key')]::alpha[];` @@ -108,5 +108,5 @@ func (q *DBQuerier) AlphaCompositeArray(ctx context.Context) ([]Alpha, error) { return zero, fmt.Errorf("query AlphaCompositeArray: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]Alpha]) + return pgx.CollectOneRow(rows, pgx.RowTo[[]Alpha]) } diff --git a/example/separate_out_dir/out/alpha_query.sql.1.go b/example/separate_out_dir/out/alpha_query.sql.1.go index eef44b67..d8e775b6 100644 --- a/example/separate_out_dir/out/alpha_query.sql.1.go +++ b/example/separate_out_dir/out/alpha_query.sql.1.go @@ -19,5 +19,5 @@ func (q *DBQuerier) Alpha(ctx context.Context) (string, error) { return zero, fmt.Errorf("query Alpha: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) + return pgx.CollectOneRow(rows, pgx.RowTo[string]) } diff --git a/example/separate_out_dir/out/bravo_query.sql.go b/example/separate_out_dir/out/bravo_query.sql.go index 18968ef0..5db88c2d 100644 --- a/example/separate_out_dir/out/bravo_query.sql.go +++ b/example/separate_out_dir/out/bravo_query.sql.go @@ -19,5 +19,5 @@ func (q *DBQuerier) Bravo(ctx context.Context) (string, error) { return zero, fmt.Errorf("query Bravo: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) + return pgx.CollectOneRow(rows, pgx.RowTo[string]) } diff --git a/example/slices/query.sql.go b/example/slices/query.sql.go index 628324b5..5234c926 100644 --- a/example/slices/query.sql.go +++ b/example/slices/query.sql.go @@ -86,7 +86,7 @@ func (q *DBQuerier) GetBools(ctx context.Context, data []bool) ([]bool, error) { return zero, fmt.Errorf("query GetBools: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[[]bool]) + return pgx.CollectOneRow(rows, pgx.RowTo[[]bool]) } const getOneTimestampSQL = `SELECT $1::timestamp;` @@ -100,7 +100,7 @@ func (q *DBQuerier) GetOneTimestamp(ctx context.Context, data *time.Time) (*time return zero, fmt.Errorf("query GetOneTimestamp: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[*time.Time]) + return pgx.CollectOneRow(rows, pgx.RowTo[*time.Time]) } const getManyTimestamptzsSQL = `SELECT * diff --git a/example/syntax/query.sql.go b/example/syntax/query.sql.go index d160e0b6..e961e293 100644 --- a/example/syntax/query.sql.go +++ b/example/syntax/query.sql.go @@ -117,7 +117,7 @@ func (q *DBQuerier) Backtick(ctx context.Context) (string, error) { return zero, fmt.Errorf("query Backtick: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) + return pgx.CollectOneRow(rows, pgx.RowTo[string]) } const backtickQuoteBacktickSQL = "SELECT '`\"`';" @@ -131,7 +131,7 @@ func (q *DBQuerier) BacktickQuoteBacktick(ctx context.Context) (string, error) { return zero, fmt.Errorf("query BacktickQuoteBacktick: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) + return pgx.CollectOneRow(rows, pgx.RowTo[string]) } const backtickNewlineSQL = "SELECT '`\n';" @@ -145,7 +145,7 @@ func (q *DBQuerier) BacktickNewline(ctx context.Context) (string, error) { return zero, fmt.Errorf("query BacktickNewline: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) + return pgx.CollectOneRow(rows, pgx.RowTo[string]) } const backtickDoubleQuoteSQL = "SELECT '`\"';" @@ -159,7 +159,7 @@ func (q *DBQuerier) BacktickDoubleQuote(ctx context.Context) (string, error) { return zero, fmt.Errorf("query BacktickDoubleQuote: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) + return pgx.CollectOneRow(rows, pgx.RowTo[string]) } const backtickBackslashNSQL = "SELECT '`\\n';" @@ -173,7 +173,7 @@ func (q *DBQuerier) BacktickBackslashN(ctx context.Context) (string, error) { return zero, fmt.Errorf("query BacktickBackslashN: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) + return pgx.CollectOneRow(rows, pgx.RowTo[string]) } const illegalNameSymbolsSQL = "SELECT '`\\n' as \"$\", $1 as \"foo.bar!@#$%&*()\"\"--+\";" @@ -192,7 +192,7 @@ func (q *DBQuerier) IllegalNameSymbols(ctx context.Context, helloWorld string) ( return zero, fmt.Errorf("query IllegalNameSymbols: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[IllegalNameSymbolsRow]) + return pgx.CollectOneRow(rows, pgx.RowToStructByName[IllegalNameSymbolsRow]) } const spaceAfterSQL = `SELECT $1;` @@ -206,7 +206,7 @@ func (q *DBQuerier) SpaceAfter(ctx context.Context, space string) (string, error return zero, fmt.Errorf("query SpaceAfter: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) + return pgx.CollectOneRow(rows, pgx.RowTo[string]) } const badEnumNameSQL = `SELECT 'inconvertible_enum_name'::"123";` @@ -220,7 +220,7 @@ func (q *DBQuerier) BadEnumName(ctx context.Context) (UnnamedEnum123, error) { return zero, fmt.Errorf("query BadEnumName: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[UnnamedEnum123]) + return pgx.CollectOneRow(rows, pgx.RowTo[UnnamedEnum123]) } const goKeywordSQL = `SELECT $1::text;` @@ -234,5 +234,5 @@ func (q *DBQuerier) GoKeyword(ctx context.Context, go_ string) (string, error) { return zero, fmt.Errorf("query GoKeyword: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) + return pgx.CollectOneRow(rows, pgx.RowTo[string]) } diff --git a/example/void/query.sql.go b/example/void/query.sql.go index 140cc346..bb73bb98 100644 --- a/example/void/query.sql.go +++ b/example/void/query.sql.go @@ -111,7 +111,7 @@ func (q *DBQuerier) VoidTwo(ctx context.Context) (string, error) { return zero, fmt.Errorf("query VoidTwo: %w", err) } - return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (string, error) { + return pgx.CollectOneRow(rows, func(row pgx.CollectableRow) (string, error) { var item string if err := row.Scan(nil, &item); err != nil { return item, err @@ -136,7 +136,7 @@ func (q *DBQuerier) VoidThree(ctx context.Context) (VoidThreeRow, error) { return zero, fmt.Errorf("query VoidThree: %w", err) } - return pgx.CollectExactlyOneRow(rows, func(row pgx.CollectableRow) (VoidThreeRow, error) { + return pgx.CollectOneRow(rows, func(row pgx.CollectableRow) (VoidThreeRow, error) { var item VoidThreeRow if err := row.Scan(nil, &item.Foo, &item.Bar); err != nil { return item, err diff --git a/internal/codegen/golang/templated_file.go b/internal/codegen/golang/templated_file.go index 2f8e4685..12efebfc 100644 --- a/internal/codegen/golang/templated_file.go +++ b/internal/codegen/golang/templated_file.go @@ -264,7 +264,7 @@ func (tq TemplatedQuery) EmitCollectionFunc() (string, error) { case ast.ResultKindMany: return "pgx.CollectRows", nil case ast.ResultKindOne: - return "pgx.CollectExactlyOneRow", nil + return "pgx.CollectOneRow", nil default: return "", fmt.Errorf("unhandled EmitCollectionFunc type: %s", tq.ResultKind) } diff --git a/internal/pg/query.sql.go b/internal/pg/query.sql.go index 2375efcf..efd521c6 100644 --- a/internal/pg/query.sql.go +++ b/internal/pg/query.sql.go @@ -307,7 +307,7 @@ func (q *DBQuerier) FindOIDByName(ctx context.Context, name string) (uint32, err return zero, fmt.Errorf("query FindOIDByName: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[uint32]) + return pgx.CollectOneRow(rows, pgx.RowTo[uint32]) } const findOIDNameSQL = `SELECT typname AS name @@ -323,7 +323,7 @@ func (q *DBQuerier) FindOIDName(ctx context.Context, oid uint32) (string, error) return zero, fmt.Errorf("query FindOIDName: %w", err) } - return pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) + return pgx.CollectOneRow(rows, pgx.RowTo[string]) } const findOIDNamesSQL = `SELECT oid, typname AS name, typtype AS kind From adb89a8cd471ee6da0e769e05eca493f83973709 Mon Sep 17 00:00:00 2001 From: meoyawn Date: Wed, 6 May 2026 21:32:31 +0000 Subject: [PATCH 15/27] Wrap collection errors with query context --- example/author/query.sql.go | 63 ++++++++++++++++--- example/author/query.sql_test.go | 1 + example/complex_params/query.sql.go | 35 +++++++++-- example/composite/query.sql.go | 35 +++++++++-- example/custom_types/query.sql.go | 28 +++++++-- example/device/query.sql.go | 35 +++++++++-- example/domain/query.sql.go | 7 ++- example/enums/query.sql.go | 35 +++++++++-- example/erp/order/customer.sql.go | 35 +++++++++-- example/erp/order/price.sql.go | 14 ++++- example/function/query.sql.go | 7 ++- example/go_pointer_types/query.sql.go | 42 +++++++++++-- .../inline_param_count/inline0/query.sql.go | 21 ++++++- .../inline_param_count/inline1/query.sql.go | 21 ++++++- .../inline_param_count/inline2/query.sql.go | 21 ++++++- .../inline_param_count/inline3/query.sql.go | 21 ++++++- example/ltree/query.sql.go | 21 ++++++- example/nested/query.sql.go | 14 ++++- example/pgcrypto/query.sql.go | 7 ++- .../separate_out_dir/out/alpha_query.sql.0.go | 14 ++++- .../separate_out_dir/out/alpha_query.sql.1.go | 7 ++- .../separate_out_dir/out/bravo_query.sql.go | 7 ++- example/slices/query.sql.go | 28 +++++++-- example/syntax/query.sql.go | 63 ++++++++++++++++--- example/void/query.sql.go | 51 +++++++++------ internal/codegen/golang/query.gotemplate | 9 ++- internal/codegen/golang/templated_file.go | 25 ++++++-- internal/pg/query.sql.go | 49 ++++++++++++--- 28 files changed, 600 insertions(+), 116 deletions(-) diff --git a/example/author/query.sql.go b/example/author/query.sql.go index 7e96c7b9..8c19d2cf 100644 --- a/example/author/query.sql.go +++ b/example/author/query.sql.go @@ -119,7 +119,12 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut return zero, fmt.Errorf("query FindAuthorByID: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + if err != nil { + var zero FindAuthorByIDRow + return zero, fmt.Errorf("query FindAuthorByID: %w", err) + } + return result, nil } const findAuthorsSQL = `SELECT * FROM author WHERE first_name = $1;` @@ -140,7 +145,12 @@ func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAu return zero, fmt.Errorf("query FindAuthors: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[FindAuthorsRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindAuthorsRow]) + if err != nil { + var zero []FindAuthorsRow + return zero, fmt.Errorf("scan FindAuthors row: %w", err) + } + return result, nil } const findAuthorNamesSQL = `SELECT first_name, last_name FROM author ORDER BY author_id = $1;` @@ -159,7 +169,12 @@ func (q *DBQuerier) FindAuthorNames(ctx context.Context, authorID int32) ([]Find return zero, fmt.Errorf("query FindAuthorNames: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[FindAuthorNamesRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindAuthorNamesRow]) + if err != nil { + var zero []FindAuthorNamesRow + return zero, fmt.Errorf("scan FindAuthorNames row: %w", err) + } + return result, nil } const findFirstNamesSQL = `SELECT first_name FROM author ORDER BY author_id = $1;` @@ -173,7 +188,12 @@ func (q *DBQuerier) FindFirstNames(ctx context.Context, authorID int32) ([]*stri return zero, fmt.Errorf("query FindFirstNames: %w", err) } - return pgx.CollectRows(rows, pgx.RowTo[*string]) + result, err := pgx.CollectRows(rows, pgx.RowTo[*string]) + if err != nil { + var zero []*string + return zero, fmt.Errorf("scan FindFirstNames row: %w", err) + } + return result, nil } const findFirstAuthorSQL = `SELECT * FROM author ORDER BY author_id;` @@ -194,7 +214,12 @@ func (q *DBQuerier) FindFirstAuthor(ctx context.Context) (FindFirstAuthorRow, er return zero, fmt.Errorf("query FindFirstAuthor: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindFirstAuthorRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[FindFirstAuthorRow]) + if err != nil { + var zero FindFirstAuthorRow + return zero, fmt.Errorf("query FindFirstAuthor: %w", err) + } + return result, nil } const deleteAuthorsSQL = `DELETE FROM author WHERE first_name = 'joe';` @@ -256,7 +281,12 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, firstName string, lastName return zero, fmt.Errorf("query InsertAuthor: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[int32]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[int32]) + if err != nil { + var zero int32 + return zero, fmt.Errorf("query InsertAuthor: %w", err) + } + return result, nil } const insertAuthorSuffixSQL = `INSERT INTO author (first_name, last_name, suffix) @@ -285,7 +315,12 @@ func (q *DBQuerier) InsertAuthorSuffix(ctx context.Context, params InsertAuthorS return zero, fmt.Errorf("query InsertAuthorSuffix: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[InsertAuthorSuffixRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[InsertAuthorSuffixRow]) + if err != nil { + var zero InsertAuthorSuffixRow + return zero, fmt.Errorf("query InsertAuthorSuffix: %w", err) + } + return result, nil } const stringAggFirstNameSQL = `SELECT string_agg(first_name, ',') AS names FROM author WHERE author_id = $1;` @@ -299,7 +334,12 @@ func (q *DBQuerier) StringAggFirstName(ctx context.Context, authorID int32) (*st return zero, fmt.Errorf("query StringAggFirstName: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[*string]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[*string]) + if err != nil { + var zero *string + return zero, fmt.Errorf("query StringAggFirstName: %w", err) + } + return result, nil } const arrayAggFirstNameSQL = `SELECT array_agg(first_name) AS names FROM author WHERE author_id = $1;` @@ -313,5 +353,10 @@ func (q *DBQuerier) ArrayAggFirstName(ctx context.Context, authorID int32) ([]st return zero, fmt.Errorf("query ArrayAggFirstName: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[[]string]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[[]string]) + if err != nil { + var zero []string + return zero, fmt.Errorf("query ArrayAggFirstName: %w", err) + } + return result, nil } diff --git a/example/author/query.sql_test.go b/example/author/query.sql_test.go index 3c32f710..8622b3e6 100644 --- a/example/author/query.sql_test.go +++ b/example/author/query.sql_test.go @@ -34,6 +34,7 @@ func TestNewQuerier_FindAuthorByID(t *testing.T) { t.Run("FindAuthorByID - none-exists", func(t *testing.T) { missingAuthorByID, err := q.FindAuthorByID(t.Context(), 888) require.Error(t, err, "expected error when finding author ID that doesn't match") + assert.ErrorContains(t, err, "query FindAuthorByID:") assert.Zero(t, missingAuthorByID, "expected zero value when error") if !errors.Is(err, pgx.ErrNoRows) { t.Fatalf("expected no rows error to wrap pgx.ErrNoRows; got %s", err) diff --git a/example/complex_params/query.sql.go b/example/complex_params/query.sql.go index 70fbcf65..c49bbb99 100644 --- a/example/complex_params/query.sql.go +++ b/example/complex_params/query.sql.go @@ -114,7 +114,12 @@ func (q *DBQuerier) ParamArrayInt(ctx context.Context, ints []int) ([]int, error return zero, fmt.Errorf("query ParamArrayInt: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[[]int]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[[]int]) + if err != nil { + var zero []int + return zero, fmt.Errorf("query ParamArrayInt: %w", err) + } + return result, nil } const paramNested1SQL = `SELECT $1::dimensions;` @@ -128,7 +133,12 @@ func (q *DBQuerier) ParamNested1(ctx context.Context, dimensions Dimensions) (Di return zero, fmt.Errorf("query ParamNested1: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[Dimensions]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[Dimensions]) + if err != nil { + var zero Dimensions + return zero, fmt.Errorf("query ParamNested1: %w", err) + } + return result, nil } const paramNested2SQL = `SELECT $1::product_image_type;` @@ -142,7 +152,12 @@ func (q *DBQuerier) ParamNested2(ctx context.Context, image ProductImageType) (P return zero, fmt.Errorf("query ParamNested2: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[ProductImageType]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[ProductImageType]) + if err != nil { + var zero ProductImageType + return zero, fmt.Errorf("query ParamNested2: %w", err) + } + return result, nil } const paramNested2ArraySQL = `SELECT $1::product_image_type[];` @@ -156,7 +171,12 @@ func (q *DBQuerier) ParamNested2Array(ctx context.Context, images []ProductImage return zero, fmt.Errorf("query ParamNested2Array: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[[]ProductImageType]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[[]ProductImageType]) + if err != nil { + var zero []ProductImageType + return zero, fmt.Errorf("query ParamNested2Array: %w", err) + } + return result, nil } const paramNested3SQL = `SELECT $1::product_image_set_type;` @@ -170,5 +190,10 @@ func (q *DBQuerier) ParamNested3(ctx context.Context, imageSet ProductImageSetTy return zero, fmt.Errorf("query ParamNested3: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[ProductImageSetType]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[ProductImageSetType]) + if err != nil { + var zero ProductImageSetType + return zero, fmt.Errorf("query ParamNested3: %w", err) + } + return result, nil } diff --git a/example/composite/query.sql.go b/example/composite/query.sql.go index c98c2556..4edda308 100644 --- a/example/composite/query.sql.go +++ b/example/composite/query.sql.go @@ -136,7 +136,12 @@ func (q *DBQuerier) SearchScreenshots(ctx context.Context, params SearchScreensh return zero, fmt.Errorf("query SearchScreenshots: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[SearchScreenshotsRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[SearchScreenshotsRow]) + if err != nil { + var zero []SearchScreenshotsRow + return zero, fmt.Errorf("scan SearchScreenshots row: %w", err) + } + return result, nil } const searchScreenshotsOneColSQL = `SELECT @@ -163,7 +168,12 @@ func (q *DBQuerier) SearchScreenshotsOneCol(ctx context.Context, params SearchSc return zero, fmt.Errorf("query SearchScreenshotsOneCol: %w", err) } - return pgx.CollectRows(rows, pgx.RowTo[[]Blocks]) + result, err := pgx.CollectRows(rows, pgx.RowTo[[]Blocks]) + if err != nil { + var zero [][]Blocks + return zero, fmt.Errorf("scan SearchScreenshotsOneCol row: %w", err) + } + return result, nil } const insertScreenshotBlocksSQL = `WITH screens AS ( @@ -190,7 +200,12 @@ func (q *DBQuerier) InsertScreenshotBlocks(ctx context.Context, screenshotID int return zero, fmt.Errorf("query InsertScreenshotBlocks: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[InsertScreenshotBlocksRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[InsertScreenshotBlocksRow]) + if err != nil { + var zero InsertScreenshotBlocksRow + return zero, fmt.Errorf("query InsertScreenshotBlocks: %w", err) + } + return result, nil } const arraysInputSQL = `SELECT $1::arrays;` @@ -204,7 +219,12 @@ func (q *DBQuerier) ArraysInput(ctx context.Context, arrays Arrays) (Arrays, err return zero, fmt.Errorf("query ArraysInput: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[Arrays]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[Arrays]) + if err != nil { + var zero Arrays + return zero, fmt.Errorf("query ArraysInput: %w", err) + } + return result, nil } const userEmailsSQL = `SELECT ('foo', 'bar@example.com')::user_email;` @@ -218,5 +238,10 @@ func (q *DBQuerier) UserEmails(ctx context.Context) (UserEmail, error) { return zero, fmt.Errorf("query UserEmails: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[UserEmail]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[UserEmail]) + if err != nil { + var zero UserEmail + return zero, fmt.Errorf("query UserEmails: %w", err) + } + return result, nil } diff --git a/example/custom_types/query.sql.go b/example/custom_types/query.sql.go index 30d81d47..7a1f4e15 100644 --- a/example/custom_types/query.sql.go +++ b/example/custom_types/query.sql.go @@ -91,7 +91,12 @@ func (q *DBQuerier) CustomTypes(ctx context.Context) (CustomTypesRow, error) { return zero, fmt.Errorf("query CustomTypes: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[CustomTypesRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[CustomTypesRow]) + if err != nil { + var zero CustomTypesRow + return zero, fmt.Errorf("query CustomTypes: %w", err) + } + return result, nil } const customStringSQL = `SELECT 'some_text'::text;` @@ -105,7 +110,12 @@ func (q *DBQuerier) CustomString(ctx context.Context) (mytype.String, error) { return zero, fmt.Errorf("query CustomString: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[mytype.String]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[mytype.String]) + if err != nil { + var zero mytype.String + return zero, fmt.Errorf("query CustomString: %w", err) + } + return result, nil } const customMyIntSQL = `SELECT '5'::my_int as int5;` @@ -119,7 +129,12 @@ func (q *DBQuerier) CustomMyInt(ctx context.Context) (int, error) { return zero, fmt.Errorf("query CustomMyInt: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[int]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[int]) + if err != nil { + var zero int + return zero, fmt.Errorf("query CustomMyInt: %w", err) + } + return result, nil } const intArraySQL = `SELECT ARRAY ['5', '6', '7']::int[] as ints;` @@ -133,5 +148,10 @@ func (q *DBQuerier) IntArray(ctx context.Context) ([][]int32, error) { return zero, fmt.Errorf("query IntArray: %w", err) } - return pgx.CollectRows(rows, pgx.RowTo[[]int32]) + result, err := pgx.CollectRows(rows, pgx.RowTo[[]int32]) + if err != nil { + var zero [][]int32 + return zero, fmt.Errorf("scan IntArray row: %w", err) + } + return result, nil } diff --git a/example/device/query.sql.go b/example/device/query.sql.go index 5f62a7c0..5fe9dd59 100644 --- a/example/device/query.sql.go +++ b/example/device/query.sql.go @@ -127,7 +127,12 @@ func (q *DBQuerier) FindDevicesByUser(ctx context.Context, id int) ([]FindDevice return zero, fmt.Errorf("query FindDevicesByUser: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[FindDevicesByUserRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindDevicesByUserRow]) + if err != nil { + var zero []FindDevicesByUserRow + return zero, fmt.Errorf("scan FindDevicesByUser row: %w", err) + } + return result, nil } const compositeUserSQL = `SELECT @@ -152,7 +157,12 @@ func (q *DBQuerier) CompositeUser(ctx context.Context) ([]CompositeUserRow, erro return zero, fmt.Errorf("query CompositeUser: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[CompositeUserRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[CompositeUserRow]) + if err != nil { + var zero []CompositeUserRow + return zero, fmt.Errorf("scan CompositeUser row: %w", err) + } + return result, nil } const compositeUserOneSQL = `SELECT ROW (15, 'qux')::"user" AS "user";` @@ -166,7 +176,12 @@ func (q *DBQuerier) CompositeUserOne(ctx context.Context) (User, error) { return zero, fmt.Errorf("query CompositeUserOne: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[User]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[User]) + if err != nil { + var zero User + return zero, fmt.Errorf("query CompositeUserOne: %w", err) + } + return result, nil } const compositeUserOneTwoColsSQL = `SELECT 1 AS num, ROW (15, 'qux')::"user" AS "user";` @@ -185,7 +200,12 @@ func (q *DBQuerier) CompositeUserOneTwoCols(ctx context.Context) (CompositeUserO return zero, fmt.Errorf("query CompositeUserOneTwoCols: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[CompositeUserOneTwoColsRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[CompositeUserOneTwoColsRow]) + if err != nil { + var zero CompositeUserOneTwoColsRow + return zero, fmt.Errorf("query CompositeUserOneTwoCols: %w", err) + } + return result, nil } const compositeUserManySQL = `SELECT ROW (15, 'qux')::"user" AS "user";` @@ -199,7 +219,12 @@ func (q *DBQuerier) CompositeUserMany(ctx context.Context) ([]User, error) { return zero, fmt.Errorf("query CompositeUserMany: %w", err) } - return pgx.CollectRows(rows, pgx.RowTo[User]) + result, err := pgx.CollectRows(rows, pgx.RowTo[User]) + if err != nil { + var zero []User + return zero, fmt.Errorf("scan CompositeUserMany row: %w", err) + } + return result, nil } const insertUserSQL = `INSERT INTO "user" (id, name) diff --git a/example/domain/query.sql.go b/example/domain/query.sql.go index d2122ecc..ccdf872c 100644 --- a/example/domain/query.sql.go +++ b/example/domain/query.sql.go @@ -79,5 +79,10 @@ func (q *DBQuerier) DomainOne(ctx context.Context) (string, error) { return zero, fmt.Errorf("query DomainOne: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[string]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[string]) + if err != nil { + var zero string + return zero, fmt.Errorf("query DomainOne: %w", err) + } + return result, nil } diff --git a/example/enums/query.sql.go b/example/enums/query.sql.go index bc1e910c..6c23538b 100644 --- a/example/enums/query.sql.go +++ b/example/enums/query.sql.go @@ -126,7 +126,12 @@ func (q *DBQuerier) FindAllDevices(ctx context.Context) ([]FindAllDevicesRow, er return zero, fmt.Errorf("query FindAllDevices: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[FindAllDevicesRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindAllDevicesRow]) + if err != nil { + var zero []FindAllDevicesRow + return zero, fmt.Errorf("scan FindAllDevices row: %w", err) + } + return result, nil } const insertDeviceSQL = `INSERT INTO device (mac, type) @@ -153,7 +158,12 @@ func (q *DBQuerier) FindOneDeviceArray(ctx context.Context) ([]DeviceType, error return zero, fmt.Errorf("query FindOneDeviceArray: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[[]DeviceType]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[[]DeviceType]) + if err != nil { + var zero []DeviceType + return zero, fmt.Errorf("query FindOneDeviceArray: %w", err) + } + return result, nil } const findManyDeviceArraySQL = `SELECT enum_range('ipad'::device_type, 'iot'::device_type) AS device_types @@ -169,7 +179,12 @@ func (q *DBQuerier) FindManyDeviceArray(ctx context.Context) ([][]DeviceType, er return zero, fmt.Errorf("query FindManyDeviceArray: %w", err) } - return pgx.CollectRows(rows, pgx.RowTo[[]DeviceType]) + result, err := pgx.CollectRows(rows, pgx.RowTo[[]DeviceType]) + if err != nil { + var zero [][]DeviceType + return zero, fmt.Errorf("scan FindManyDeviceArray row: %w", err) + } + return result, nil } const findManyDeviceArrayWithNumSQL = `SELECT 1 AS num, enum_range('ipad'::device_type, 'iot'::device_type) AS device_types @@ -190,7 +205,12 @@ func (q *DBQuerier) FindManyDeviceArrayWithNum(ctx context.Context) ([]FindManyD return zero, fmt.Errorf("query FindManyDeviceArrayWithNum: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[FindManyDeviceArrayWithNumRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindManyDeviceArrayWithNumRow]) + if err != nil { + var zero []FindManyDeviceArrayWithNumRow + return zero, fmt.Errorf("scan FindManyDeviceArrayWithNum row: %w", err) + } + return result, nil } const enumInsideCompositeSQL = `SELECT ROW('08:00:2b:01:02:03'::macaddr, 'phone'::device_type) ::device;` @@ -204,5 +224,10 @@ func (q *DBQuerier) EnumInsideComposite(ctx context.Context) (Device, error) { return zero, fmt.Errorf("query EnumInsideComposite: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[Device]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[Device]) + if err != nil { + var zero Device + return zero, fmt.Errorf("query EnumInsideComposite: %w", err) + } + return result, nil } diff --git a/example/erp/order/customer.sql.go b/example/erp/order/customer.sql.go index c7da2c60..17a1a3f8 100644 --- a/example/erp/order/customer.sql.go +++ b/example/erp/order/customer.sql.go @@ -100,7 +100,12 @@ func (q *DBQuerier) CreateTenant(ctx context.Context, key string, name string) ( return zero, fmt.Errorf("query CreateTenant: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[CreateTenantRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[CreateTenantRow]) + if err != nil { + var zero CreateTenantRow + return zero, fmt.Errorf("query CreateTenant: %w", err) + } + return result, nil } const findOrdersByCustomerSQL = `SELECT * @@ -123,7 +128,12 @@ func (q *DBQuerier) FindOrdersByCustomer(ctx context.Context, customerID int32) return zero, fmt.Errorf("query FindOrdersByCustomer: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[FindOrdersByCustomerRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindOrdersByCustomerRow]) + if err != nil { + var zero []FindOrdersByCustomerRow + return zero, fmt.Errorf("scan FindOrdersByCustomer row: %w", err) + } + return result, nil } const findProductsInOrderSQL = `SELECT o.order_id, p.product_id, p.name @@ -147,7 +157,12 @@ func (q *DBQuerier) FindProductsInOrder(ctx context.Context, orderID int32) ([]F return zero, fmt.Errorf("query FindProductsInOrder: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[FindProductsInOrderRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindProductsInOrderRow]) + if err != nil { + var zero []FindProductsInOrderRow + return zero, fmt.Errorf("scan FindProductsInOrder row: %w", err) + } + return result, nil } const insertCustomerSQL = `INSERT INTO customer (first_name, last_name, email) @@ -176,7 +191,12 @@ func (q *DBQuerier) InsertCustomer(ctx context.Context, params InsertCustomerPar return zero, fmt.Errorf("query InsertCustomer: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[InsertCustomerRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[InsertCustomerRow]) + if err != nil { + var zero InsertCustomerRow + return zero, fmt.Errorf("query InsertCustomer: %w", err) + } + return result, nil } const insertOrderSQL = `INSERT INTO orders (order_date, order_total, customer_id) @@ -205,5 +225,10 @@ func (q *DBQuerier) InsertOrder(ctx context.Context, params InsertOrderParams) ( return zero, fmt.Errorf("query InsertOrder: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[InsertOrderRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[InsertOrderRow]) + if err != nil { + var zero InsertOrderRow + return zero, fmt.Errorf("query InsertOrder: %w", err) + } + return result, nil } diff --git a/example/erp/order/price.sql.go b/example/erp/order/price.sql.go index a021695a..cd0d7190 100644 --- a/example/erp/order/price.sql.go +++ b/example/erp/order/price.sql.go @@ -27,7 +27,12 @@ func (q *DBQuerier) FindOrdersByPrice(ctx context.Context, minTotal pgtype.Numer return zero, fmt.Errorf("query FindOrdersByPrice: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[FindOrdersByPriceRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindOrdersByPriceRow]) + if err != nil { + var zero []FindOrdersByPriceRow + return zero, fmt.Errorf("scan FindOrdersByPrice row: %w", err) + } + return result, nil } const findOrdersMRRSQL = `SELECT date_trunc('month', order_date) AS month, sum(order_total) AS order_mrr @@ -48,5 +53,10 @@ func (q *DBQuerier) FindOrdersMRR(ctx context.Context) ([]FindOrdersMRRRow, erro return zero, fmt.Errorf("query FindOrdersMRR: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[FindOrdersMRRRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindOrdersMRRRow]) + if err != nil { + var zero []FindOrdersMRRRow + return zero, fmt.Errorf("scan FindOrdersMRR row: %w", err) + } + return result, nil } diff --git a/example/function/query.sql.go b/example/function/query.sql.go index 36659102..4d3b3aee 100644 --- a/example/function/query.sql.go +++ b/example/function/query.sql.go @@ -102,5 +102,10 @@ func (q *DBQuerier) OutParams(ctx context.Context) ([]OutParamsRow, error) { return zero, fmt.Errorf("query OutParams: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[OutParamsRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[OutParamsRow]) + if err != nil { + var zero []OutParamsRow + return zero, fmt.Errorf("scan OutParams row: %w", err) + } + return result, nil } diff --git a/example/go_pointer_types/query.sql.go b/example/go_pointer_types/query.sql.go index 75625a4c..2bca0609 100644 --- a/example/go_pointer_types/query.sql.go +++ b/example/go_pointer_types/query.sql.go @@ -91,7 +91,12 @@ func (q *DBQuerier) GenSeries1(ctx context.Context) (*int, error) { return zero, fmt.Errorf("query GenSeries1: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[*int]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[*int]) + if err != nil { + var zero *int + return zero, fmt.Errorf("query GenSeries1: %w", err) + } + return result, nil } const genSeriesSQL = `SELECT n @@ -106,7 +111,12 @@ func (q *DBQuerier) GenSeries(ctx context.Context) ([]*int, error) { return zero, fmt.Errorf("query GenSeries: %w", err) } - return pgx.CollectRows(rows, pgx.RowTo[*int]) + result, err := pgx.CollectRows(rows, pgx.RowTo[*int]) + if err != nil { + var zero []*int + return zero, fmt.Errorf("scan GenSeries row: %w", err) + } + return result, nil } const genSeriesArr1SQL = `SELECT array_agg(n) @@ -121,7 +131,12 @@ func (q *DBQuerier) GenSeriesArr1(ctx context.Context) ([]int, error) { return zero, fmt.Errorf("query GenSeriesArr1: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[[]int]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[[]int]) + if err != nil { + var zero []int + return zero, fmt.Errorf("query GenSeriesArr1: %w", err) + } + return result, nil } const genSeriesArrSQL = `SELECT array_agg(n) @@ -136,7 +151,12 @@ func (q *DBQuerier) GenSeriesArr(ctx context.Context) ([][]int, error) { return zero, fmt.Errorf("query GenSeriesArr: %w", err) } - return pgx.CollectRows(rows, pgx.RowTo[[]int]) + result, err := pgx.CollectRows(rows, pgx.RowTo[[]int]) + if err != nil { + var zero [][]int + return zero, fmt.Errorf("scan GenSeriesArr row: %w", err) + } + return result, nil } const genSeriesStr1SQL = `SELECT n::text @@ -152,7 +172,12 @@ func (q *DBQuerier) GenSeriesStr1(ctx context.Context) (*string, error) { return zero, fmt.Errorf("query GenSeriesStr1: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[*string]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[*string]) + if err != nil { + var zero *string + return zero, fmt.Errorf("query GenSeriesStr1: %w", err) + } + return result, nil } const genSeriesStrSQL = `SELECT n::text @@ -167,5 +192,10 @@ func (q *DBQuerier) GenSeriesStr(ctx context.Context) ([]*string, error) { return zero, fmt.Errorf("query GenSeriesStr: %w", err) } - return pgx.CollectRows(rows, pgx.RowTo[*string]) + result, err := pgx.CollectRows(rows, pgx.RowTo[*string]) + if err != nil { + var zero []*string + return zero, fmt.Errorf("scan GenSeriesStr row: %w", err) + } + return result, nil } diff --git a/example/inline_param_count/inline0/query.sql.go b/example/inline_param_count/inline0/query.sql.go index 76a90e47..61529bf7 100644 --- a/example/inline_param_count/inline0/query.sql.go +++ b/example/inline_param_count/inline0/query.sql.go @@ -89,7 +89,12 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { return zero, fmt.Errorf("query CountAuthors: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[*int]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[*int]) + if err != nil { + var zero *int + return zero, fmt.Errorf("query CountAuthors: %w", err) + } + return result, nil } const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` @@ -114,7 +119,12 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, params FindAuthorByIDPar return zero, fmt.Errorf("query FindAuthorByID: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + if err != nil { + var zero FindAuthorByIDRow + return zero, fmt.Errorf("query FindAuthorByID: %w", err) + } + return result, nil } const insertAuthorSQL = `INSERT INTO author (first_name, last_name) @@ -135,7 +145,12 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, params InsertAuthorParams) return zero, fmt.Errorf("query InsertAuthor: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[int32]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[int32]) + if err != nil { + var zero int32 + return zero, fmt.Errorf("query InsertAuthor: %w", err) + } + return result, nil } const deleteAuthorsByFullNameSQL = `DELETE diff --git a/example/inline_param_count/inline1/query.sql.go b/example/inline_param_count/inline1/query.sql.go index 77bfe4a4..134befbf 100644 --- a/example/inline_param_count/inline1/query.sql.go +++ b/example/inline_param_count/inline1/query.sql.go @@ -89,7 +89,12 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { return zero, fmt.Errorf("query CountAuthors: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[*int]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[*int]) + if err != nil { + var zero *int + return zero, fmt.Errorf("query CountAuthors: %w", err) + } + return result, nil } const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` @@ -110,7 +115,12 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut return zero, fmt.Errorf("query FindAuthorByID: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + if err != nil { + var zero FindAuthorByIDRow + return zero, fmt.Errorf("query FindAuthorByID: %w", err) + } + return result, nil } const insertAuthorSQL = `INSERT INTO author (first_name, last_name) @@ -131,7 +141,12 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, params InsertAuthorParams) return zero, fmt.Errorf("query InsertAuthor: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[int32]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[int32]) + if err != nil { + var zero int32 + return zero, fmt.Errorf("query InsertAuthor: %w", err) + } + return result, nil } const deleteAuthorsByFullNameSQL = `DELETE diff --git a/example/inline_param_count/inline2/query.sql.go b/example/inline_param_count/inline2/query.sql.go index b4d05f3b..96dd4320 100644 --- a/example/inline_param_count/inline2/query.sql.go +++ b/example/inline_param_count/inline2/query.sql.go @@ -89,7 +89,12 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { return zero, fmt.Errorf("query CountAuthors: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[*int]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[*int]) + if err != nil { + var zero *int + return zero, fmt.Errorf("query CountAuthors: %w", err) + } + return result, nil } const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` @@ -110,7 +115,12 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut return zero, fmt.Errorf("query FindAuthorByID: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + if err != nil { + var zero FindAuthorByIDRow + return zero, fmt.Errorf("query FindAuthorByID: %w", err) + } + return result, nil } const insertAuthorSQL = `INSERT INTO author (first_name, last_name) @@ -126,7 +136,12 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, firstName string, lastName return zero, fmt.Errorf("query InsertAuthor: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[int32]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[int32]) + if err != nil { + var zero int32 + return zero, fmt.Errorf("query InsertAuthor: %w", err) + } + return result, nil } const deleteAuthorsByFullNameSQL = `DELETE diff --git a/example/inline_param_count/inline3/query.sql.go b/example/inline_param_count/inline3/query.sql.go index 4dfb3550..8ef8b669 100644 --- a/example/inline_param_count/inline3/query.sql.go +++ b/example/inline_param_count/inline3/query.sql.go @@ -89,7 +89,12 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { return zero, fmt.Errorf("query CountAuthors: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[*int]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[*int]) + if err != nil { + var zero *int + return zero, fmt.Errorf("query CountAuthors: %w", err) + } + return result, nil } const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` @@ -110,7 +115,12 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut return zero, fmt.Errorf("query FindAuthorByID: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + if err != nil { + var zero FindAuthorByIDRow + return zero, fmt.Errorf("query FindAuthorByID: %w", err) + } + return result, nil } const insertAuthorSQL = `INSERT INTO author (first_name, last_name) @@ -126,7 +136,12 @@ func (q *DBQuerier) InsertAuthor(ctx context.Context, firstName string, lastName return zero, fmt.Errorf("query InsertAuthor: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[int32]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[int32]) + if err != nil { + var zero int32 + return zero, fmt.Errorf("query InsertAuthor: %w", err) + } + return result, nil } const deleteAuthorsByFullNameSQL = `DELETE diff --git a/example/ltree/query.sql.go b/example/ltree/query.sql.go index 3e3fbc87..323cab16 100644 --- a/example/ltree/query.sql.go +++ b/example/ltree/query.sql.go @@ -88,7 +88,12 @@ func (q *DBQuerier) FindTopScienceChildren(ctx context.Context) ([]pgtype.Text, return zero, fmt.Errorf("query FindTopScienceChildren: %w", err) } - return pgx.CollectRows(rows, pgx.RowTo[pgtype.Text]) + result, err := pgx.CollectRows(rows, pgx.RowTo[pgtype.Text]) + if err != nil { + var zero []pgtype.Text + return zero, fmt.Errorf("scan FindTopScienceChildren row: %w", err) + } + return result, nil } const findTopScienceChildrenAggSQL = `SELECT array_agg(path) @@ -104,7 +109,12 @@ func (q *DBQuerier) FindTopScienceChildrenAgg(ctx context.Context) (pgtype.Array return zero, fmt.Errorf("query FindTopScienceChildrenAgg: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[pgtype.Array[pgtype.Text]]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[pgtype.Array[pgtype.Text]]) + if err != nil { + var zero pgtype.Array[pgtype.Text] + return zero, fmt.Errorf("query FindTopScienceChildrenAgg: %w", err) + } + return result, nil } const insertSampleDataSQL = `INSERT INTO test @@ -157,5 +167,10 @@ func (q *DBQuerier) FindLtreeInput(ctx context.Context, inLtree pgtype.Text, inL return zero, fmt.Errorf("query FindLtreeInput: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindLtreeInputRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[FindLtreeInputRow]) + if err != nil { + var zero FindLtreeInputRow + return zero, fmt.Errorf("query FindLtreeInput: %w", err) + } + return result, nil } diff --git a/example/nested/query.sql.go b/example/nested/query.sql.go index 1452f753..da84e485 100644 --- a/example/nested/query.sql.go +++ b/example/nested/query.sql.go @@ -112,7 +112,12 @@ func (q *DBQuerier) ArrayNested2(ctx context.Context) ([]ProductImageType, error return zero, fmt.Errorf("query ArrayNested2: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[[]ProductImageType]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[[]ProductImageType]) + if err != nil { + var zero []ProductImageType + return zero, fmt.Errorf("query ArrayNested2: %w", err) + } + return result, nil } const nested3SQL = `SELECT @@ -134,5 +139,10 @@ func (q *DBQuerier) Nested3(ctx context.Context) ([]ProductImageSetType, error) return zero, fmt.Errorf("query Nested3: %w", err) } - return pgx.CollectRows(rows, pgx.RowTo[ProductImageSetType]) + result, err := pgx.CollectRows(rows, pgx.RowTo[ProductImageSetType]) + if err != nil { + var zero []ProductImageSetType + return zero, fmt.Errorf("scan Nested3 row: %w", err) + } + return result, nil } diff --git a/example/pgcrypto/query.sql.go b/example/pgcrypto/query.sql.go index 99696518..8432a28f 100644 --- a/example/pgcrypto/query.sql.go +++ b/example/pgcrypto/query.sql.go @@ -100,5 +100,10 @@ func (q *DBQuerier) FindUser(ctx context.Context, email string) (FindUserRow, er return zero, fmt.Errorf("query FindUser: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[FindUserRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[FindUserRow]) + if err != nil { + var zero FindUserRow + return zero, fmt.Errorf("query FindUser: %w", err) + } + return result, nil } diff --git a/example/separate_out_dir/out/alpha_query.sql.0.go b/example/separate_out_dir/out/alpha_query.sql.0.go index aba2656e..66d2b38d 100644 --- a/example/separate_out_dir/out/alpha_query.sql.0.go +++ b/example/separate_out_dir/out/alpha_query.sql.0.go @@ -94,7 +94,12 @@ func (q *DBQuerier) AlphaNested(ctx context.Context) (string, error) { return zero, fmt.Errorf("query AlphaNested: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[string]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[string]) + if err != nil { + var zero string + return zero, fmt.Errorf("query AlphaNested: %w", err) + } + return result, nil } const alphaCompositeArraySQL = `SELECT ARRAY[ROW('key')]::alpha[];` @@ -108,5 +113,10 @@ func (q *DBQuerier) AlphaCompositeArray(ctx context.Context) ([]Alpha, error) { return zero, fmt.Errorf("query AlphaCompositeArray: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[[]Alpha]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[[]Alpha]) + if err != nil { + var zero []Alpha + return zero, fmt.Errorf("query AlphaCompositeArray: %w", err) + } + return result, nil } diff --git a/example/separate_out_dir/out/alpha_query.sql.1.go b/example/separate_out_dir/out/alpha_query.sql.1.go index d8e775b6..1de74a95 100644 --- a/example/separate_out_dir/out/alpha_query.sql.1.go +++ b/example/separate_out_dir/out/alpha_query.sql.1.go @@ -19,5 +19,10 @@ func (q *DBQuerier) Alpha(ctx context.Context) (string, error) { return zero, fmt.Errorf("query Alpha: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[string]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[string]) + if err != nil { + var zero string + return zero, fmt.Errorf("query Alpha: %w", err) + } + return result, nil } diff --git a/example/separate_out_dir/out/bravo_query.sql.go b/example/separate_out_dir/out/bravo_query.sql.go index 5db88c2d..455610a6 100644 --- a/example/separate_out_dir/out/bravo_query.sql.go +++ b/example/separate_out_dir/out/bravo_query.sql.go @@ -19,5 +19,10 @@ func (q *DBQuerier) Bravo(ctx context.Context) (string, error) { return zero, fmt.Errorf("query Bravo: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[string]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[string]) + if err != nil { + var zero string + return zero, fmt.Errorf("query Bravo: %w", err) + } + return result, nil } diff --git a/example/slices/query.sql.go b/example/slices/query.sql.go index 5234c926..83573803 100644 --- a/example/slices/query.sql.go +++ b/example/slices/query.sql.go @@ -86,7 +86,12 @@ func (q *DBQuerier) GetBools(ctx context.Context, data []bool) ([]bool, error) { return zero, fmt.Errorf("query GetBools: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[[]bool]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[[]bool]) + if err != nil { + var zero []bool + return zero, fmt.Errorf("query GetBools: %w", err) + } + return result, nil } const getOneTimestampSQL = `SELECT $1::timestamp;` @@ -100,7 +105,12 @@ func (q *DBQuerier) GetOneTimestamp(ctx context.Context, data *time.Time) (*time return zero, fmt.Errorf("query GetOneTimestamp: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[*time.Time]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[*time.Time]) + if err != nil { + var zero *time.Time + return zero, fmt.Errorf("query GetOneTimestamp: %w", err) + } + return result, nil } const getManyTimestamptzsSQL = `SELECT * @@ -115,7 +125,12 @@ func (q *DBQuerier) GetManyTimestamptzs(ctx context.Context, data []time.Time) ( return zero, fmt.Errorf("query GetManyTimestamptzs: %w", err) } - return pgx.CollectRows(rows, pgx.RowTo[*time.Time]) + result, err := pgx.CollectRows(rows, pgx.RowTo[*time.Time]) + if err != nil { + var zero []*time.Time + return zero, fmt.Errorf("scan GetManyTimestamptzs row: %w", err) + } + return result, nil } const getManyTimestampsSQL = `SELECT * @@ -130,5 +145,10 @@ func (q *DBQuerier) GetManyTimestamps(ctx context.Context, data []*time.Time) ([ return zero, fmt.Errorf("query GetManyTimestamps: %w", err) } - return pgx.CollectRows(rows, pgx.RowTo[*time.Time]) + result, err := pgx.CollectRows(rows, pgx.RowTo[*time.Time]) + if err != nil { + var zero []*time.Time + return zero, fmt.Errorf("scan GetManyTimestamps row: %w", err) + } + return result, nil } diff --git a/example/syntax/query.sql.go b/example/syntax/query.sql.go index e961e293..18702757 100644 --- a/example/syntax/query.sql.go +++ b/example/syntax/query.sql.go @@ -117,7 +117,12 @@ func (q *DBQuerier) Backtick(ctx context.Context) (string, error) { return zero, fmt.Errorf("query Backtick: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[string]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[string]) + if err != nil { + var zero string + return zero, fmt.Errorf("query Backtick: %w", err) + } + return result, nil } const backtickQuoteBacktickSQL = "SELECT '`\"`';" @@ -131,7 +136,12 @@ func (q *DBQuerier) BacktickQuoteBacktick(ctx context.Context) (string, error) { return zero, fmt.Errorf("query BacktickQuoteBacktick: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[string]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[string]) + if err != nil { + var zero string + return zero, fmt.Errorf("query BacktickQuoteBacktick: %w", err) + } + return result, nil } const backtickNewlineSQL = "SELECT '`\n';" @@ -145,7 +155,12 @@ func (q *DBQuerier) BacktickNewline(ctx context.Context) (string, error) { return zero, fmt.Errorf("query BacktickNewline: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[string]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[string]) + if err != nil { + var zero string + return zero, fmt.Errorf("query BacktickNewline: %w", err) + } + return result, nil } const backtickDoubleQuoteSQL = "SELECT '`\"';" @@ -159,7 +174,12 @@ func (q *DBQuerier) BacktickDoubleQuote(ctx context.Context) (string, error) { return zero, fmt.Errorf("query BacktickDoubleQuote: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[string]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[string]) + if err != nil { + var zero string + return zero, fmt.Errorf("query BacktickDoubleQuote: %w", err) + } + return result, nil } const backtickBackslashNSQL = "SELECT '`\\n';" @@ -173,7 +193,12 @@ func (q *DBQuerier) BacktickBackslashN(ctx context.Context) (string, error) { return zero, fmt.Errorf("query BacktickBackslashN: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[string]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[string]) + if err != nil { + var zero string + return zero, fmt.Errorf("query BacktickBackslashN: %w", err) + } + return result, nil } const illegalNameSymbolsSQL = "SELECT '`\\n' as \"$\", $1 as \"foo.bar!@#$%&*()\"\"--+\";" @@ -192,7 +217,12 @@ func (q *DBQuerier) IllegalNameSymbols(ctx context.Context, helloWorld string) ( return zero, fmt.Errorf("query IllegalNameSymbols: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowToStructByName[IllegalNameSymbolsRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[IllegalNameSymbolsRow]) + if err != nil { + var zero IllegalNameSymbolsRow + return zero, fmt.Errorf("query IllegalNameSymbols: %w", err) + } + return result, nil } const spaceAfterSQL = `SELECT $1;` @@ -206,7 +236,12 @@ func (q *DBQuerier) SpaceAfter(ctx context.Context, space string) (string, error return zero, fmt.Errorf("query SpaceAfter: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[string]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[string]) + if err != nil { + var zero string + return zero, fmt.Errorf("query SpaceAfter: %w", err) + } + return result, nil } const badEnumNameSQL = `SELECT 'inconvertible_enum_name'::"123";` @@ -220,7 +255,12 @@ func (q *DBQuerier) BadEnumName(ctx context.Context) (UnnamedEnum123, error) { return zero, fmt.Errorf("query BadEnumName: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[UnnamedEnum123]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[UnnamedEnum123]) + if err != nil { + var zero UnnamedEnum123 + return zero, fmt.Errorf("query BadEnumName: %w", err) + } + return result, nil } const goKeywordSQL = `SELECT $1::text;` @@ -234,5 +274,10 @@ func (q *DBQuerier) GoKeyword(ctx context.Context, go_ string) (string, error) { return zero, fmt.Errorf("query GoKeyword: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[string]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[string]) + if err != nil { + var zero string + return zero, fmt.Errorf("query GoKeyword: %w", err) + } + return result, nil } diff --git a/example/void/query.sql.go b/example/void/query.sql.go index bb73bb98..05939016 100644 --- a/example/void/query.sql.go +++ b/example/void/query.sql.go @@ -111,13 +111,18 @@ func (q *DBQuerier) VoidTwo(ctx context.Context) (string, error) { return zero, fmt.Errorf("query VoidTwo: %w", err) } - return pgx.CollectOneRow(rows, func(row pgx.CollectableRow) (string, error) { - var item string - if err := row.Scan(nil, &item); err != nil { - return item, err + result, err := pgx.CollectOneRow(rows, func(row pgx.CollectableRow) (string, error) { + var item string + if err := row.Scan(nil, &item); err != nil { + return item, err + } + return item, nil + }) + if err != nil { + var zero string + return zero, fmt.Errorf("query VoidTwo: %w", err) } - return item, nil -}) + return result, nil } const voidThreeSQL = `SELECT void_fn(), 'foo' as foo, 'bar' as bar;` @@ -136,13 +141,18 @@ func (q *DBQuerier) VoidThree(ctx context.Context) (VoidThreeRow, error) { return zero, fmt.Errorf("query VoidThree: %w", err) } - return pgx.CollectOneRow(rows, func(row pgx.CollectableRow) (VoidThreeRow, error) { - var item VoidThreeRow - if err := row.Scan(nil, &item.Foo, &item.Bar); err != nil { - return item, err + result, err := pgx.CollectOneRow(rows, func(row pgx.CollectableRow) (VoidThreeRow, error) { + var item VoidThreeRow + if err := row.Scan(nil, &item.Foo, &item.Bar); err != nil { + return item, err + } + return item, nil + }) + if err != nil { + var zero VoidThreeRow + return zero, fmt.Errorf("query VoidThree: %w", err) } - return item, nil -}) + return result, nil } const voidThree2SQL = `SELECT 'foo' as foo, void_fn(), void_fn();` @@ -156,11 +166,16 @@ func (q *DBQuerier) VoidThree2(ctx context.Context) ([]string, error) { return zero, fmt.Errorf("query VoidThree2: %w", err) } - return pgx.CollectRows(rows, func(row pgx.CollectableRow) (string, error) { - var item string - if err := row.Scan(&item, nil, nil); err != nil { - return item, err + result, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (string, error) { + var item string + if err := row.Scan(&item, nil, nil); err != nil { + return item, err + } + return item, nil + }) + if err != nil { + var zero []string + return zero, fmt.Errorf("scan VoidThree2 row: %w", err) } - return item, nil -}) + return result, nil } diff --git a/internal/codegen/golang/query.gotemplate b/internal/codegen/golang/query.gotemplate index 3091030d..0617b8c2 100644 --- a/internal/codegen/golang/query.gotemplate +++ b/internal/codegen/golang/query.gotemplate @@ -69,8 +69,13 @@ func (q *DBQuerier) {{ $q.Name }}(ctx context.Context {{- $q.EmitParams }}) ({{ return zero, fmt.Errorf("query {{ $q.Name }}: %w", err) } - return {{ $q.EmitCollectionFunc }}(rows, {{ $q.EmitRowMapper }}) -{{- end }} + result, err := {{ $q.EmitCollectionFunc }}(rows, {{ $q.EmitRowMapper }}) + if err != nil { + {{ $q.EmitZeroResultInit "zero" }} + return zero, fmt.Errorf("{{ $q.EmitCollectionErrContext }}: %w", err) + } + return result, nil + {{- end }} } {{- end -}} {{- "\n" -}} diff --git a/internal/codegen/golang/templated_file.go b/internal/codegen/golang/templated_file.go index 12efebfc..1ea064c4 100644 --- a/internal/codegen/golang/templated_file.go +++ b/internal/codegen/golang/templated_file.go @@ -270,6 +270,19 @@ func (tq TemplatedQuery) EmitCollectionFunc() (string, error) { } } +func (tq TemplatedQuery) EmitCollectionErrContext() (string, error) { + switch tq.ResultKind { + case ast.ResultKindExec: + return "", fmt.Errorf("cannot EmitCollectionErrContext for :exec query %s", tq.Name) + case ast.ResultKindMany: + return "scan " + tq.Name + " row", nil + case ast.ResultKindOne: + return "query " + tq.Name, nil + default: + return "", fmt.Errorf("unhandled EmitCollectionErrContext type: %s", tq.ResultKind) + } +} + func (tq TemplatedQuery) EmitRowToFunc() (string, error) { switch tq.ResultKind { case ast.ResultKindExec: @@ -298,12 +311,12 @@ func (tq TemplatedQuery) EmitRowMapper() (string, error) { return "", err } return fmt.Sprintf(`func(row pgx.CollectableRow) (%s, error) { - var item %s - if err := row.Scan(%s); err != nil { - return item, err - } - return item, nil -}`, resultType, resultType, scanTargets), nil + var item %s + if err := row.Scan(%s); err != nil { + return item, err + } + return item, nil + }`, resultType, resultType, scanTargets), nil } func (tq TemplatedQuery) EmitScanTargets() (string, error) { diff --git a/internal/pg/query.sql.go b/internal/pg/query.sql.go index efd521c6..8eaa9f19 100644 --- a/internal/pg/query.sql.go +++ b/internal/pg/query.sql.go @@ -148,7 +148,12 @@ func (q *DBQuerier) FindEnumTypes(ctx context.Context, oids []uint32) ([]FindEnu return zero, fmt.Errorf("query FindEnumTypes: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[FindEnumTypesRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindEnumTypesRow]) + if err != nil { + var zero []FindEnumTypesRow + return zero, fmt.Errorf("scan FindEnumTypes row: %w", err) + } + return result, nil } const findArrayTypesSQL = `SELECT @@ -196,7 +201,12 @@ func (q *DBQuerier) FindArrayTypes(ctx context.Context, oids []uint32) ([]FindAr return zero, fmt.Errorf("query FindArrayTypes: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[FindArrayTypesRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindArrayTypesRow]) + if err != nil { + var zero []FindArrayTypesRow + return zero, fmt.Errorf("scan FindArrayTypes row: %w", err) + } + return result, nil } const findCompositeTypesSQL = `WITH table_cols AS ( @@ -249,7 +259,12 @@ func (q *DBQuerier) FindCompositeTypes(ctx context.Context, oids []uint32) ([]Fi return zero, fmt.Errorf("query FindCompositeTypes: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[FindCompositeTypesRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindCompositeTypesRow]) + if err != nil { + var zero []FindCompositeTypesRow + return zero, fmt.Errorf("scan FindCompositeTypes row: %w", err) + } + return result, nil } const findDescendantOIDsSQL = `WITH RECURSIVE oid_descs(oid) AS ( @@ -289,7 +304,12 @@ func (q *DBQuerier) FindDescendantOIDs(ctx context.Context, oids []uint32) ([]ui return zero, fmt.Errorf("query FindDescendantOIDs: %w", err) } - return pgx.CollectRows(rows, pgx.RowTo[uint32]) + result, err := pgx.CollectRows(rows, pgx.RowTo[uint32]) + if err != nil { + var zero []uint32 + return zero, fmt.Errorf("scan FindDescendantOIDs row: %w", err) + } + return result, nil } const findOIDByNameSQL = `SELECT oid @@ -307,7 +327,12 @@ func (q *DBQuerier) FindOIDByName(ctx context.Context, name string) (uint32, err return zero, fmt.Errorf("query FindOIDByName: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[uint32]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[uint32]) + if err != nil { + var zero uint32 + return zero, fmt.Errorf("query FindOIDByName: %w", err) + } + return result, nil } const findOIDNameSQL = `SELECT typname AS name @@ -323,7 +348,12 @@ func (q *DBQuerier) FindOIDName(ctx context.Context, oid uint32) (string, error) return zero, fmt.Errorf("query FindOIDName: %w", err) } - return pgx.CollectOneRow(rows, pgx.RowTo[string]) + result, err := pgx.CollectOneRow(rows, pgx.RowTo[string]) + if err != nil { + var zero string + return zero, fmt.Errorf("query FindOIDName: %w", err) + } + return result, nil } const findOIDNamesSQL = `SELECT oid, typname AS name, typtype AS kind @@ -345,5 +375,10 @@ func (q *DBQuerier) FindOIDNames(ctx context.Context, oid []uint32) ([]FindOIDNa return zero, fmt.Errorf("query FindOIDNames: %w", err) } - return pgx.CollectRows(rows, pgx.RowToStructByName[FindOIDNamesRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindOIDNamesRow]) + if err != nil { + var zero []FindOIDNamesRow + return zero, fmt.Errorf("scan FindOIDNames row: %w", err) + } + return result, nil } From a6e9a749132d9fea68f873819ef9b009c39328f5 Mon Sep 17 00:00:00 2001 From: meoyawn Date: Wed, 6 May 2026 21:42:44 +0000 Subject: [PATCH 16/27] Update README for pgx v5 --- README.md | 165 +++++++++++++++++++++++++++--------------------------- 1 file changed, 81 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 705cc4a9..bb952c34 100644 --- a/README.md +++ b/README.md @@ -105,12 +105,9 @@ Why should you use `pggen` instead of the [myriad] of Go SQL bindings? - pggen uses [pgx], a faster replacement for [lib/pq], the original Go Postgres library that's now in maintenance mode. -- pggen provides a batch (aka query pipelining) interface for each generated - query with [`pgx.Batch`]. Query pipelining is the reason Postgres sits atop - the [TechEmpower benchmarks]. Using a batch enables sending multiple queries - in a single network round-trip instead of one network round-trip per query. +- pggen-generated queriers work with pgx connection transports like + [`*pgx.Conn`], [`pgx.Tx`], and [`*pgxpool.Pool`]. -[TechEmpower benchmarks]: https://www.techempower.com/benchmarks/#section=data-r20&hw=ph&test=query [pgx]: https://github.com/jackc/pgx [lib/pq]: https://github.com/lib/pq @@ -368,31 +365,53 @@ Examples embedded in the repo: --go-type '_text=[]*github.com/jschaf/pggen/mytype.String' ``` - pgx must be able to decode the Postgres type using the given Go type. That - means the Go type must fulfill at least one of following: + pggen only changes the generated Go signatures and scan destinations. pgx + must still be able to decode the Postgres type using the given Go type. + That means the Go type must fulfill at least one of the following: - The Go type is a wrapper around primitive type, like `type AuthorID int`. pgx will use decode methods on the underlying primitive type. - - The Go type implements both [`pgtype.BinaryDecoder`] and - [`pgtype.TextDecoder`]. pgx will use the correct decoder based on the wire - format. See the [pgtype repo] for many example types. - - - The pgx connection executing the query must have registered a data type - using the Go type with [`ConnInfo.RegisterDataType`]. See the + - The Go type implements the scanner interfaces supported by the + [`pgtype.Codec`] for that Postgres type. See the [pgtype package] for + built-in codecs and interfaces. + + - The Go type implements [`sql.Scanner`]. Query parameters can also + implement [`driver.Valuer`]. + + - The pgx connection executing the query has registered the Postgres type + in its pgx v5 type map. For enum, composite, and array types that + pgx can inspect, pggen generates `RegisterTypes(ctx, *pgx.Conn)`, which + calls [`pgx.Conn.LoadType`] and [`pgtype.Map.RegisterType`]. + + ```go + conn, err := pgx.Connect(ctx, url) + if err != nil { + return err + } + if err := RegisterTypes(ctx, conn); err != nil { + return err + } + + q := NewQuerier(conn) + ``` + + If you use [`*pgxpool.Pool`], call `RegisterTypes` from + [`pgxpool.Config.AfterConnect`] so every pooled connection has the same + type map. + + - The pgx connection has a custom [`pgtype.Type`] registered with a + [`pgtype.Codec`]. This is useful for user-defined base types and extension + types that need application-provided codecs. See the [example/custom_types test] for an example. - + ```go - ci := conn.ConnInfo() - - ci.RegisterDataType(pgtype.DataType{ - Value: new(pgtype.Int2), - Name: "my_int", - OID: myIntOID, + conn.TypeMap().RegisterType(&pgtype.Type{ + Name: "my_int", + OID: myIntOID, + Codec: pgtype.Int2Codec{}, }) ``` - - - The Go type implements [`sql.Scanner`]. - pgx is able to use reflection to build an object to write fields into. @@ -416,11 +435,13 @@ Examples embedded in the repo: func (q *DBQuerier) FindCompositeUser(ctx context.Context) (User, error) {} ``` -[pgtype repo]: https://github.com/jackc/pgtype -[`pgtype.BinaryDecoder`]: https://pkg.go.dev/github.com/jackc/pgtype#BinaryDecoder -[`pgtype.TextDecoder`]: https://pkg.go.dev/github.com/jackc/pgtype#TextDecoder -[`ConnInfo.RegisterDataType`]: https://pkg.go.dev/github.com/jackc/pgtype#ConnInfo.RegisterDataType +[pgtype package]: https://pkg.go.dev/github.com/jackc/pgx/v5/pgtype +[`pgtype.Codec`]: https://pkg.go.dev/github.com/jackc/pgx/v5/pgtype#Codec +[`pgtype.Type`]: https://pkg.go.dev/github.com/jackc/pgx/v5/pgtype#Type +[`pgtype.Map.RegisterType`]: https://pkg.go.dev/github.com/jackc/pgx/v5/pgtype#Map.RegisterType +[`pgx.Conn.LoadType`]: https://pkg.go.dev/github.com/jackc/pgx/v5#Conn.LoadType [`sql.Scanner`]: https://golang.org/pkg/database/sql/#Scanner +[`driver.Valuer`]: https://pkg.go.dev/database/sql/driver#Valuer [composite types]: https://www.postgresql.org/docs/current/rowtypes.html [example/custom_types test]: ./example/custom_types/query.sql_test.go @@ -472,42 +493,15 @@ pggen gen go \ We'll walk through the generated file `author/query.sql.go`: -- The `Querier` interface defines the interface with methods for each SQL - query. Each SQL query compiles into three methods, one method for to run - the query by itself, and two methods to support batching a query with - [`pgx.Batch`]. +- The `Querier` interface defines one method for each SQL query. ```go // Querier is a typesafe Go interface backed by SQL queries. - // - // Methods ending with Batch enqueue a query to run later in a pgx.Batch. After - // calling SendBatch on pgx.Conn, pgxpool.Pool, or pgx.Tx, use the Scan methods - // to parse the results. type Querier interface { // FindAuthors finds authors by first name. FindAuthors(ctx context.Context, firstName string) ([]FindAuthorsRow, error) - // FindAuthorsBatch enqueues a FindAuthors query into batch to be executed - // later by the batch. - FindAuthorsBatch(batch *pgx.Batch, firstName string) - // FindAuthorsScan scans the result of an executed FindAuthorsBatch query. - FindAuthorsScan(results pgx.BatchResults) ([]FindAuthorsRow, error) } ``` - - To use the batch interface, create a `*pgx.Batch`, call the - `Batch` methods, send the batch, and finally get the results - with the `Scan` methods. See [example/author/query.sql_test.go] - for complete example. - - ```sql - q := NewQuerier(conn) - batch := &pgx.Batch{} - q.FindAuthorsBatch(batch, "alice") - q.FindAuthorsBatch(batch, "bob") - results := conn.SendBatch(context.Background(), batch) - aliceAuthors, err := q.FindAuthorsScan(results) - bobAuthors, err := q.FindAuthorsScan(results) - ``` - The `DBQuerier` struct implements the `Querier` interface with concrete implementations of each query method. @@ -532,6 +526,15 @@ We'll walk through the generated file `author/query.sql.go`: } } ``` + +- For custom PostgreSQL types, pggen generates `RegisterTypes`. Call it once + for each `*pgx.Conn` before running queries that use generated enum, + composite, or array types. + + ```go + // RegisterTypes loads custom PostgreSQL types into conn's pgx type map. + func RegisterTypes(ctx context.Context, conn *pgx.Conn) error {} + ``` - pggen embeds the SQL query formatted for a Postgres `PREPARE` statement with parameters indicated by `$1`, `$2`, etc. instead of @@ -549,10 +552,10 @@ We'll walk through the generated file `author/query.sql.go`: ```sql type FindAuthorsRow struct { - AuthorID int32 `json:"author_id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Suffix pgtype.Text `json:"suffix"` + AuthorID int32 `json:"author_id" db:"author_id"` + FirstName string `json:"first_name" db:"first_name"` + LastName string `json:"last_name" db:"last_name"` + Suffix *string `json:"suffix" db:"suffix"` } ``` @@ -571,11 +574,12 @@ We'll walk through the generated file `author/query.sql.go`: that column can have both `text` and `null` values. So, the Postgres `text` represented in Go can be either a `string` or `nil`. [`pgtype`] provides nullable types for all built-in Postgres types. pggen tries to infer if a - column is nullable or non-nullable. If a column is nullable, pggen uses a - `pgtype` Go type like `pgtype.Text`. If a column is non-nullable, pggen uses - a more ergonomic type like `string`. pggen's nullability inference - implemented in [internal/pginfer/nullability.go] is rudimentary; a proper - approach requires a full explain-plan with some control flow analysis. + column is nullable or non-nullable. If a column is nullable, pggen uses a + nullable Go type like `*string` or a `pgtype` type. If a column is + non-nullable, pggen uses a more ergonomic type like `string`. pggen's + nullability inference implemented in [internal/pginfer/nullability.go] is + rudimentary; a proper approach requires a full explain-plan with some + control flow analysis. - Lastly, pggen generates the implementation for each query. @@ -587,36 +591,29 @@ We'll walk through the generated file `author/query.sql.go`: ```sql // FindAuthors implements Querier.FindAuthors. func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAuthorsRow, error) { + ctx = context.WithValue(ctx, QueryName{}, "FindAuthors") rows, err := q.conn.Query(ctx, findAuthorsSQL, firstName) - if rows != nil { - defer rows.Close() - } if err != nil { - return nil, fmt.Errorf("query FindAuthors: %w", err) - } - items := []FindAuthorsRow{} - for rows.Next() { - var item FindAuthorsRow - if err := rows.Scan(&item.AuthorID, &item.FirstName, &item.LastName, &item.Suffix); err != nil { - return nil, fmt.Errorf("scan FindAuthors row: %w", err) - } - items = append(items, item) + var zero []FindAuthorsRow + return zero, fmt.Errorf("query FindAuthors: %w", err) } - if err := rows.Err(); err != nil { - return nil, err + + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindAuthorsRow]) + if err != nil { + var zero []FindAuthorsRow + return zero, fmt.Errorf("scan FindAuthors row: %w", err) } - return items, err + return result, nil } ``` -[example/author/query.sql_test.go]: ./example/author/query.sql_test.go -[`pgx.Batch`]: https://pkg.go.dev/github.com/jackc/pgx#Batch -[`*pgx.Conn`]: https://pkg.go.dev/github.com/jackc/pgx#Conn -[`pgx.Tx`]: https://pkg.go.dev/github.com/jackc/pgx#Tx -[`*pgxpool.Pool`]: https://pkg.go.dev/github.com/jackc/pgx/v4/pgxpool#Pool +[`*pgx.Conn`]: https://pkg.go.dev/github.com/jackc/pgx/v5#Conn +[`pgx.Tx`]: https://pkg.go.dev/github.com/jackc/pgx/v5#Tx +[`*pgxpool.Pool`]: https://pkg.go.dev/github.com/jackc/pgx/v5/pgxpool#Pool +[`pgxpool.Config.AfterConnect`]: https://pkg.go.dev/github.com/jackc/pgx/v5/pgxpool#Config [internal/casing/casing.go]: ./internal/casing/casing.go [internal/codegen/golang/gotype/types.go]: ./internal/codegen/golang/gotype/types.go -[`pgtype`]: https://pkg.go.dev/github.com/jackc/pgtype +[`pgtype`]: https://pkg.go.dev/github.com/jackc/pgx/v5/pgtype [internal/pginfer/nullability.go]: ./internal/pginfer/nullability.go # Contributing From 5106fe911aac7a8eabae0c3821d7806cdeaf6f7a Mon Sep 17 00:00:00 2001 From: meoyawn Date: Thu, 7 May 2026 08:25:22 +0000 Subject: [PATCH 17/27] Make pgx v5 fork go-gettable --- README.md | 22 +++++++++---------- cmd/pggen/pggen.go | 8 +++---- example/acceptance_test.go | 8 +++---- example/author/codegen_test.go | 4 ++-- example/author/query.sql_test.go | 4 ++-- example/complex_params/codegen_test.go | 4 ++-- example/complex_params/query.sql_test.go | 2 +- example/composite/codegen_test.go | 4 ++-- example/composite/query.sql_test.go | 6 ++--- example/custom_types/codegen_test.go | 8 +++---- example/custom_types/query.sql.go | 2 +- example/custom_types/query.sql_test.go | 6 ++--- example/device/codegen_test.go | 4 ++-- example/device/query.sql_test.go | 2 +- example/domain/codegen_test.go | 4 ++-- example/domain/query.sql_test.go | 2 +- example/enums/codegen_test.go | 4 ++-- example/enums/query.sql | 2 +- example/enums/query.sql.go | 2 +- example/enums/query.sql_test.go | 2 +- example/erp/order/codegen_test.go | 4 ++-- example/erp/order/customer.sql_test.go | 2 +- example/function/codegen_test.go | 4 ++-- example/function/query.sql_test.go | 6 ++--- example/go_pointer_types/codegen_test.go | 4 ++-- example/go_pointer_types/query.sql_test.go | 2 +- example/inline_param_count/codegen_test.go | 4 ++-- .../inline0/query.sql_test.go | 2 +- .../inline1/query.sql_test.go | 2 +- .../inline2/query.sql_test.go | 2 +- .../inline3/query.sql_test.go | 2 +- example/ltree/codegen_test.go | 4 ++-- example/ltree/query.sql_test.go | 2 +- example/nested/codegen_test.go | 4 ++-- example/nested/query.sql_test.go | 2 +- example/pgcrypto/codegen_test.go | 4 ++-- example/pgcrypto/query.sql_test.go | 2 +- example/separate_out_dir/out/codegen_test.go | 4 ++-- .../separate_out_dir/out/query.sql_test.go | 2 +- example/slices/codegen_test.go | 6 ++--- example/slices/query.sql_test.go | 4 ++-- example/syntax/codegen_test.go | 4 ++-- example/syntax/query.sql_test.go | 2 +- example/void/codegen_test.go | 4 ++-- example/void/query.sql_test.go | 2 +- generate.go | 14 ++++++------ generate_test.go | 4 ++-- go.mod | 2 +- internal/codegen/common.go | 2 +- internal/codegen/golang/declarer.go | 2 +- internal/codegen/golang/declarer_array.go | 2 +- internal/codegen/golang/declarer_composite.go | 2 +- internal/codegen/golang/declarer_enum.go | 2 +- internal/codegen/golang/declarer_test.go | 8 +++---- internal/codegen/golang/emitter.go | 2 +- internal/codegen/golang/generate.go | 4 ++-- internal/codegen/golang/gotype/known_types.go | 4 ++-- internal/codegen/golang/gotype/types.go | 10 ++++----- internal/codegen/golang/import_set.go | 4 ++-- internal/codegen/golang/query.gotemplate | 2 +- internal/codegen/golang/templated_file.go | 6 ++--- internal/codegen/golang/templater.go | 10 ++++----- internal/codegen/golang/type_resolver.go | 6 ++--- internal/codegen/golang/type_resolver_test.go | 10 ++++----- internal/gomod/gomod.go | 2 +- internal/gomod/gomod_test.go | 8 +++---- internal/parser/interface.go | 2 +- internal/parser/parser.go | 6 ++--- internal/parser/parser_test.go | 2 +- internal/pg/column.go | 2 +- internal/pg/column_test.go | 4 ++-- internal/pg/known_types.go | 2 +- internal/pg/type_fetcher_test.go | 6 ++--- internal/pg/types.go | 2 +- internal/pgdocker/cmd/pgdocker/main.go | 4 ++-- internal/pgdocker/pgdocker.go | 4 ++-- internal/pgdocker/template.go | 2 +- internal/pginfer/explain.go | 2 +- internal/pginfer/nullability.go | 4 ++-- internal/pginfer/pginfer.go | 4 ++-- internal/pginfer/pginfer_test.go | 10 ++++----- internal/pgplan/pgplan_test.go | 4 ++-- internal/ports/port.go | 2 +- internal/scanner/scanner.go | 2 +- internal/scanner/scanner_test.go | 2 +- 85 files changed, 176 insertions(+), 176 deletions(-) diff --git a/README.md b/README.md index bb952c34..6b444dd8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![Test](https://github.com/jschaf/pggen/workflows/Test/badge.svg)](https://github.com/jschaf/pggen/actions?query=workflow%3ATest) -[![Lint](https://github.com/jschaf/pggen/workflows/Lint/badge.svg)](https://github.com/jschaf/pggen/actions?query=workflow%3ALint) -[![GoReportCard](https://goreportcard.com/badge/github.com/jschaf/pggen)](https://goreportcard.com/report/github.com/jschaf/pggen) +[![Test](https://github.com/meoyawn/pggen/workflows/Test/badge.svg)](https://github.com/meoyawn/pggen/actions?query=workflow%3ATest) +[![Lint](https://github.com/meoyawn/pggen/workflows/Lint/badge.svg)](https://github.com/meoyawn/pggen/actions?query=workflow%3ALint) +[![GoReportCard](https://goreportcard.com/badge/github.com/meoyawn/pggen)](https://goreportcard.com/report/github.com/meoyawn/pggen) # pggen - generate type safe Go methods from Postgres SQL queries @@ -151,13 +151,13 @@ is far more revealing than the pitch. Precompiled binaries from the latest release. Change `~/bin` if you want to install to a different directory. All assets are listed on the [releases] page. -[releases]: https://github.com/jschaf/pggen/releases +[releases]: https://github.com/meoyawn/pggen/releases - MacOS Apple Silicon (arm64) ```shell mkdir -p ~/bin \ - && curl --silent --show-error --location --fail 'https://github.com/jschaf/pggen/releases/latest/download/pggen-darwin-arm64.tar.xz' \ + && curl --silent --show-error --location --fail 'https://github.com/meoyawn/pggen/releases/latest/download/pggen-darwin-arm64.tar.xz' \ | tar -xJf - -C ~/bin/ ``` @@ -165,7 +165,7 @@ install to a different directory. All assets are listed on the [releases] page. ```shell mkdir -p ~/bin \ - && curl --silent --show-error --location --fail 'https://github.com/jschaf/pggen/releases/latest/download/pggen-darwin-amd64.tar.xz' \ + && curl --silent --show-error --location --fail 'https://github.com/meoyawn/pggen/releases/latest/download/pggen-darwin-amd64.tar.xz' \ | tar -xJf - -C ~/bin/ ``` @@ -173,7 +173,7 @@ install to a different directory. All assets are listed on the [releases] page. ```shell mkdir -p ~/bin \ - && curl --silent --show-error --location --fail 'https://github.com/jschaf/pggen/releases/latest/download/pggen-linux-amd64.tar.xz' \ + && curl --silent --show-error --location --fail 'https://github.com/meoyawn/pggen/releases/latest/download/pggen-linux-amd64.tar.xz' \ | tar -xJf - -C ~/bin/ ``` @@ -181,7 +181,7 @@ install to a different directory. All assets are listed on the [releases] page. ```shell mkdir -p ~/bin \ - && curl --silent --show-error --location --fail 'https://github.com/jschaf/pggen/releases/latest/download/pggen-windows-amd64.tar.xz' \ + && curl --silent --show-error --location --fail 'https://github.com/meoyawn/pggen/releases/latest/download/pggen-windows-amd64.tar.xz' \ | tar -xJf - -C ~/bin/ ``` @@ -196,7 +196,7 @@ pggen gen go --help Requires Go 1.16 because pggen uses `go:embed`. Installs to `$GOPATH/bin`. ```shell -go install github.com/jschaf/pggen/cmd/pggen@latest +go install github.com/meoyawn/pggen/cmd/pggen@latest ``` Make sure pggen works: @@ -361,8 +361,8 @@ Examples embedded in the repo: --go-type 'int8=*int' \ --go-type 'int4=int' \ --go-type '_int4=[]int' \ - --go-type 'text=*github.com/jschaf/pggen/mytype.String' \ - --go-type '_text=[]*github.com/jschaf/pggen/mytype.String' + --go-type 'text=*github.com/meoyawn/pggen/mytype.String' \ + --go-type '_text=[]*github.com/meoyawn/pggen/mytype.String' ``` pggen only changes the generated Go signatures and scan destinations. pgx diff --git a/cmd/pggen/pggen.go b/cmd/pggen/pggen.go index 9120ec9d..98a86ba3 100644 --- a/cmd/pggen/pggen.go +++ b/cmd/pggen/pggen.go @@ -11,9 +11,9 @@ import ( "strings" "github.com/bmatcuk/doublestar" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/flags" - "github.com/jschaf/pggen/internal/texts" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/flags" + "github.com/meoyawn/pggen/internal/texts" "github.com/peterbourgon/ff/v3/ffcli" ) @@ -95,7 +95,7 @@ func newGenCmd() *ffcli.Command { "or custom mapping like 'apis=APIs'") goTypes := flags.Strings(fset, "go-type", nil, "custom type mapping from Postgres to fully qualified Go type, "+ - "like 'device_type=github.com/jschaf/pggen.DeviceType'") + "like 'device_type=github.com/meoyawn/pggen.DeviceType'") inlineParamCount := fset.Int("inline-param-count", 2, "number of params (inclusive) to inline when calling querier methods; 0 always generates a struct") goSubCmd := &ffcli.Command{ diff --git a/example/acceptance_test.go b/example/acceptance_test.go index cfe30a7e..0d68ef88 100644 --- a/example/acceptance_test.go +++ b/example/acceptance_test.go @@ -8,8 +8,8 @@ import ( "flag" "fmt" "github.com/jackc/pgx/v5" - "github.com/jschaf/pggen/internal/errs" - "github.com/jschaf/pggen/internal/pgdocker" + "github.com/meoyawn/pggen/internal/errs" + "github.com/meoyawn/pggen/internal/pgdocker" "math/rand" "os" "os/exec" @@ -195,8 +195,8 @@ func TestExamples(t *testing.T) { args: []string{ "--schema-glob", "example/custom_types/schema.sql", "--query-glob", "example/custom_types/query.sql", - "--go-type", "text=github.com/jschaf/pggen/example/custom_types/mytype.String", - "--go-type", "int8=github.com/jschaf/pggen/example/custom_types.CustomInt", + "--go-type", "text=github.com/meoyawn/pggen/example/custom_types/mytype.String", + "--go-type", "int8=github.com/meoyawn/pggen/example/custom_types.CustomInt", "--go-type", "my_int=int", "--go-type", "_my_int=[]int", }, diff --git a/example/author/codegen_test.go b/example/author/codegen_test.go index 8c576e4a..b29133d9 100644 --- a/example/author/codegen_test.go +++ b/example/author/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/author/query.sql_test.go b/example/author/query.sql_test.go index 8622b3e6..50805022 100644 --- a/example/author/query.sql_test.go +++ b/example/author/query.sql_test.go @@ -4,11 +4,11 @@ import ( "errors" "testing" - "github.com/jschaf/pggen/internal/ptrs" + "github.com/meoyawn/pggen/internal/ptrs" "github.com/stretchr/testify/require" "github.com/jackc/pgx/v5" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/complex_params/codegen_test.go b/example/complex_params/codegen_test.go index b5e69c2d..76974646 100644 --- a/example/complex_params/codegen_test.go +++ b/example/complex_params/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/complex_params/query.sql_test.go b/example/complex_params/query.sql_test.go index 42327d16..648ed94a 100644 --- a/example/complex_params/query.sql_test.go +++ b/example/complex_params/query.sql_test.go @@ -3,7 +3,7 @@ package complex_params import ( "testing" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/example/composite/codegen_test.go b/example/composite/codegen_test.go index 39613ff4..0e23ec14 100644 --- a/example/composite/codegen_test.go +++ b/example/composite/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/composite/query.sql_test.go b/example/composite/query.sql_test.go index 1da4ea88..ec43aa7d 100644 --- a/example/composite/query.sql_test.go +++ b/example/composite/query.sql_test.go @@ -5,9 +5,9 @@ import ( "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" - "github.com/jschaf/pggen/internal/difftest" - "github.com/jschaf/pggen/internal/pgtest" - "github.com/jschaf/pggen/internal/ptrs" + "github.com/meoyawn/pggen/internal/difftest" + "github.com/meoyawn/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/ptrs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/example/custom_types/codegen_test.go b/example/custom_types/codegen_test.go index b2d7d369..bc75d959 100644 --- a/example/custom_types/codegen_test.go +++ b/example/custom_types/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) @@ -24,8 +24,8 @@ func TestGenerate_Go_Example_CustomTypes(t *testing.T) { Language: pggen.LangGo, InlineParamCount: 2, TypeOverrides: map[string]string{ - "text": "github.com/jschaf/pggen/example/custom_types/mytype.String", - "int8": "github.com/jschaf/pggen/example/custom_types.CustomInt", + "text": "github.com/meoyawn/pggen/example/custom_types/mytype.String", + "int8": "github.com/meoyawn/pggen/example/custom_types.CustomInt", "my_int": "int", "_my_int": "[]int", }, diff --git a/example/custom_types/query.sql.go b/example/custom_types/query.sql.go index 7a1f4e15..990ddb1a 100644 --- a/example/custom_types/query.sql.go +++ b/example/custom_types/query.sql.go @@ -7,7 +7,7 @@ import ( "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jschaf/pggen/example/custom_types/mytype" + "github.com/meoyawn/pggen/example/custom_types/mytype" ) type QueryName struct{} diff --git a/example/custom_types/query.sql_test.go b/example/custom_types/query.sql_test.go index 77d8d422..8ab78a28 100644 --- a/example/custom_types/query.sql_test.go +++ b/example/custom_types/query.sql_test.go @@ -4,9 +4,9 @@ import ( "testing" "github.com/jackc/pgx/v5/pgtype" - "github.com/jschaf/pggen/example/custom_types/mytype" - "github.com/jschaf/pggen/internal/pgtest" - "github.com/jschaf/pggen/internal/texts" + "github.com/meoyawn/pggen/example/custom_types/mytype" + "github.com/meoyawn/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/texts" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/example/device/codegen_test.go b/example/device/codegen_test.go index 6a648a89..ba02f468 100644 --- a/example/device/codegen_test.go +++ b/example/device/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/device/query.sql_test.go b/example/device/query.sql_test.go index 571ac386..bfc6cce0 100644 --- a/example/device/query.sql_test.go +++ b/example/device/query.sql_test.go @@ -4,7 +4,7 @@ import ( "net" "testing" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/example/domain/codegen_test.go b/example/domain/codegen_test.go index d1f1b671..3b53d1d6 100644 --- a/example/domain/codegen_test.go +++ b/example/domain/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/domain/query.sql_test.go b/example/domain/query.sql_test.go index f466692d..fe9a3b95 100644 --- a/example/domain/query.sql_test.go +++ b/example/domain/query.sql_test.go @@ -3,7 +3,7 @@ package domain import ( "testing" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/example/enums/codegen_test.go b/example/enums/codegen_test.go index 4e0f3058..47651551 100644 --- a/example/enums/codegen_test.go +++ b/example/enums/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/enums/query.sql b/example/enums/query.sql index b39b51c3..0c665635 100644 --- a/example/enums/query.sql +++ b/example/enums/query.sql @@ -22,6 +22,6 @@ SELECT 1 AS num, enum_range('ipad'::device_type, 'iot'::device_type) AS device_t UNION ALL SELECT 2 as num, enum_range(NULL::device_type) AS device_types; --- Regression test for https://github.com/jschaf/pggen/issues/23. +-- Regression test for https://github.com/meoyawn/pggen/issues/23. -- name: EnumInsideComposite :one SELECT ROW('08:00:2b:01:02:03'::macaddr, 'phone'::device_type) ::device; diff --git a/example/enums/query.sql.go b/example/enums/query.sql.go index 6c23538b..ce0e31d4 100644 --- a/example/enums/query.sql.go +++ b/example/enums/query.sql.go @@ -27,7 +27,7 @@ type Querier interface { // Select many rows of device_type enum values with multiple output columns. FindManyDeviceArrayWithNum(ctx context.Context) ([]FindManyDeviceArrayWithNumRow, error) - // Regression test for https://github.com/jschaf/pggen/issues/23. + // Regression test for https://github.com/meoyawn/pggen/issues/23. EnumInsideComposite(ctx context.Context) (Device, error) } diff --git a/example/enums/query.sql_test.go b/example/enums/query.sql_test.go index ae39ba95..4e28774f 100644 --- a/example/enums/query.sql_test.go +++ b/example/enums/query.sql_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/example/erp/order/codegen_test.go b/example/erp/order/codegen_test.go index 90d19a18..b33e676e 100644 --- a/example/erp/order/codegen_test.go +++ b/example/erp/order/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/erp/order/customer.sql_test.go b/example/erp/order/customer.sql_test.go index 6f757c79..4f46e708 100644 --- a/example/erp/order/customer.sql_test.go +++ b/example/erp/order/customer.sql_test.go @@ -6,7 +6,7 @@ import ( "time" "github.com/jackc/pgx/v5/pgtype" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/example/function/codegen_test.go b/example/function/codegen_test.go index c5261fd1..52e42b53 100644 --- a/example/function/codegen_test.go +++ b/example/function/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/function/query.sql_test.go b/example/function/query.sql_test.go index 07ef3b58..13cec8ea 100644 --- a/example/function/query.sql_test.go +++ b/example/function/query.sql_test.go @@ -3,11 +3,11 @@ package function import ( "testing" - "github.com/jschaf/pggen/internal/difftest" - "github.com/jschaf/pggen/internal/ptrs" + "github.com/meoyawn/pggen/internal/difftest" + "github.com/meoyawn/pggen/internal/ptrs" "github.com/stretchr/testify/require" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" ) func TestNewQuerier_OutParams(t *testing.T) { diff --git a/example/go_pointer_types/codegen_test.go b/example/go_pointer_types/codegen_test.go index bdabaff8..d2c20787 100644 --- a/example/go_pointer_types/codegen_test.go +++ b/example/go_pointer_types/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/go_pointer_types/query.sql_test.go b/example/go_pointer_types/query.sql_test.go index b87ecb39..30a3cfc9 100644 --- a/example/go_pointer_types/query.sql_test.go +++ b/example/go_pointer_types/query.sql_test.go @@ -3,7 +3,7 @@ package go_pointer_types import ( "testing" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/example/inline_param_count/codegen_test.go b/example/inline_param_count/codegen_test.go index b7323f3c..6bbb37ec 100644 --- a/example/inline_param_count/codegen_test.go +++ b/example/inline_param_count/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/inline_param_count/inline0/query.sql_test.go b/example/inline_param_count/inline0/query.sql_test.go index 58edb1cf..5c9f5dff 100644 --- a/example/inline_param_count/inline0/query.sql_test.go +++ b/example/inline_param_count/inline0/query.sql_test.go @@ -7,7 +7,7 @@ import ( "github.com/jackc/pgx/v5" "github.com/stretchr/testify/require" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/inline_param_count/inline1/query.sql_test.go b/example/inline_param_count/inline1/query.sql_test.go index 3e8e447d..1e225f1d 100644 --- a/example/inline_param_count/inline1/query.sql_test.go +++ b/example/inline_param_count/inline1/query.sql_test.go @@ -7,7 +7,7 @@ import ( "github.com/jackc/pgx/v5" "github.com/stretchr/testify/require" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/inline_param_count/inline2/query.sql_test.go b/example/inline_param_count/inline2/query.sql_test.go index 1bf7b35d..52753869 100644 --- a/example/inline_param_count/inline2/query.sql_test.go +++ b/example/inline_param_count/inline2/query.sql_test.go @@ -7,7 +7,7 @@ import ( "github.com/jackc/pgx/v5" "github.com/stretchr/testify/require" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/inline_param_count/inline3/query.sql_test.go b/example/inline_param_count/inline3/query.sql_test.go index 99b41fef..31d5276c 100644 --- a/example/inline_param_count/inline3/query.sql_test.go +++ b/example/inline_param_count/inline3/query.sql_test.go @@ -7,7 +7,7 @@ import ( "github.com/jackc/pgx/v5" "github.com/stretchr/testify/require" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/ltree/codegen_test.go b/example/ltree/codegen_test.go index bc62aadf..8c1fe6f9 100644 --- a/example/ltree/codegen_test.go +++ b/example/ltree/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/ltree/query.sql_test.go b/example/ltree/query.sql_test.go index 14041b45..9807d59b 100644 --- a/example/ltree/query.sql_test.go +++ b/example/ltree/query.sql_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/jackc/pgx/v5/pgtype" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/example/nested/codegen_test.go b/example/nested/codegen_test.go index cc944af0..a6c732c3 100644 --- a/example/nested/codegen_test.go +++ b/example/nested/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/nested/query.sql_test.go b/example/nested/query.sql_test.go index 8d3612d0..afebc4a4 100644 --- a/example/nested/query.sql_test.go +++ b/example/nested/query.sql_test.go @@ -3,7 +3,7 @@ package nested import ( "testing" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/example/pgcrypto/codegen_test.go b/example/pgcrypto/codegen_test.go index 8a76f19f..25a55be4 100644 --- a/example/pgcrypto/codegen_test.go +++ b/example/pgcrypto/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/pgcrypto/query.sql_test.go b/example/pgcrypto/query.sql_test.go index bf97979a..e0a904d9 100644 --- a/example/pgcrypto/query.sql_test.go +++ b/example/pgcrypto/query.sql_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/separate_out_dir/out/codegen_test.go b/example/separate_out_dir/out/codegen_test.go index e348e4c0..56d10a8a 100644 --- a/example/separate_out_dir/out/codegen_test.go +++ b/example/separate_out_dir/out/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/separate_out_dir/out/query.sql_test.go b/example/separate_out_dir/out/query.sql_test.go index 2e3db92d..3d7e83c6 100644 --- a/example/separate_out_dir/out/query.sql_test.go +++ b/example/separate_out_dir/out/query.sql_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/slices/codegen_test.go b/example/slices/codegen_test.go index 0591e4cb..ca881e9f 100644 --- a/example/slices/codegen_test.go +++ b/example/slices/codegen_test.go @@ -5,9 +5,9 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/difftest" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/difftest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/slices/query.sql_test.go b/example/slices/query.sql_test.go index 7928ba61..7b5daa4f 100644 --- a/example/slices/query.sql_test.go +++ b/example/slices/query.sql_test.go @@ -4,8 +4,8 @@ import ( "testing" "time" - "github.com/jschaf/pggen/internal/difftest" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/difftest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/require" ) diff --git a/example/syntax/codegen_test.go b/example/syntax/codegen_test.go index e8a5a63d..8ca59544 100644 --- a/example/syntax/codegen_test.go +++ b/example/syntax/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/syntax/query.sql_test.go b/example/syntax/query.sql_test.go index 46676caa..36117001 100644 --- a/example/syntax/query.sql_test.go +++ b/example/syntax/query.sql_test.go @@ -3,7 +3,7 @@ package syntax import ( "testing" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/example/void/codegen_test.go b/example/void/codegen_test.go index 8f3b021e..12c80c26 100644 --- a/example/void/codegen_test.go +++ b/example/void/codegen_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/example/void/query.sql_test.go b/example/void/query.sql_test.go index aea472a0..0e69d6be 100644 --- a/example/void/query.sql_test.go +++ b/example/void/query.sql_test.go @@ -3,7 +3,7 @@ package void import ( "testing" - "github.com/jschaf/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/pgtest" "github.com/stretchr/testify/assert" ) diff --git a/generate.go b/generate.go index 0ffcecba..04003ebf 100644 --- a/generate.go +++ b/generate.go @@ -11,13 +11,13 @@ import ( "time" "github.com/jackc/pgx/v5" - "github.com/jschaf/pggen/internal/ast" - "github.com/jschaf/pggen/internal/codegen" - "github.com/jschaf/pggen/internal/codegen/golang" - "github.com/jschaf/pggen/internal/errs" - "github.com/jschaf/pggen/internal/parser" - "github.com/jschaf/pggen/internal/pgdocker" - "github.com/jschaf/pggen/internal/pginfer" + "github.com/meoyawn/pggen/internal/ast" + "github.com/meoyawn/pggen/internal/codegen" + "github.com/meoyawn/pggen/internal/codegen/golang" + "github.com/meoyawn/pggen/internal/errs" + "github.com/meoyawn/pggen/internal/parser" + "github.com/meoyawn/pggen/internal/pgdocker" + "github.com/meoyawn/pggen/internal/pginfer" ) // Lang is a supported codegen language. diff --git a/generate_test.go b/generate_test.go index f4b5298c..a35acea3 100644 --- a/generate_test.go +++ b/generate_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - "github.com/jschaf/pggen/internal/pgtest" - "github.com/jschaf/pggen/internal/texts" + "github.com/meoyawn/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/texts" "github.com/stretchr/testify/assert" ) diff --git a/go.mod b/go.mod index c0745071..0e770b2a 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/jschaf/pggen +module github.com/meoyawn/pggen go 1.24.1 diff --git a/internal/codegen/common.go b/internal/codegen/common.go index 9a15413c..8f1a91b2 100644 --- a/internal/codegen/common.go +++ b/internal/codegen/common.go @@ -3,7 +3,7 @@ package codegen import ( - "github.com/jschaf/pggen/internal/pginfer" + "github.com/meoyawn/pggen/internal/pginfer" ) // QueryFile represents all SQL queries from a single file. diff --git a/internal/codegen/golang/declarer.go b/internal/codegen/golang/declarer.go index 61ca79fe..2db571c5 100644 --- a/internal/codegen/golang/declarer.go +++ b/internal/codegen/golang/declarer.go @@ -4,7 +4,7 @@ import ( "sort" "strings" - "github.com/jschaf/pggen/internal/codegen/golang/gotype" + "github.com/meoyawn/pggen/internal/codegen/golang/gotype" ) // Declarer is implemented by any value that needs to declare types, data, or diff --git a/internal/codegen/golang/declarer_array.go b/internal/codegen/golang/declarer_array.go index 1aac1ab0..d4a83435 100644 --- a/internal/codegen/golang/declarer_array.go +++ b/internal/codegen/golang/declarer_array.go @@ -4,7 +4,7 @@ import ( "strconv" "strings" - "github.com/jschaf/pggen/internal/codegen/golang/gotype" + "github.com/meoyawn/pggen/internal/codegen/golang/gotype" ) // NameArrayTranscoderFunc returns the legacy array helper name. diff --git a/internal/codegen/golang/declarer_composite.go b/internal/codegen/golang/declarer_composite.go index 409308a2..e6560cf0 100644 --- a/internal/codegen/golang/declarer_composite.go +++ b/internal/codegen/golang/declarer_composite.go @@ -4,7 +4,7 @@ import ( "strconv" "strings" - "github.com/jschaf/pggen/internal/codegen/golang/gotype" + "github.com/meoyawn/pggen/internal/codegen/golang/gotype" ) // NameCompositeTranscoderFunc returns the legacy composite helper name. diff --git a/internal/codegen/golang/declarer_enum.go b/internal/codegen/golang/declarer_enum.go index f1437340..5d33c471 100644 --- a/internal/codegen/golang/declarer_enum.go +++ b/internal/codegen/golang/declarer_enum.go @@ -4,7 +4,7 @@ import ( "strconv" "strings" - "github.com/jschaf/pggen/internal/codegen/golang/gotype" + "github.com/meoyawn/pggen/internal/codegen/golang/gotype" ) func NameEnumTranscoderFunc(typ *gotype.EnumType) string { diff --git a/internal/codegen/golang/declarer_test.go b/internal/codegen/golang/declarer_test.go index 96c4ef3a..1dba62d1 100644 --- a/internal/codegen/golang/declarer_test.go +++ b/internal/codegen/golang/declarer_test.go @@ -5,10 +5,10 @@ import ( "os" "testing" - "github.com/jschaf/pggen/internal/casing" - "github.com/jschaf/pggen/internal/codegen/golang/gotype" - "github.com/jschaf/pggen/internal/difftest" - "github.com/jschaf/pggen/internal/pg" + "github.com/meoyawn/pggen/internal/casing" + "github.com/meoyawn/pggen/internal/codegen/golang/gotype" + "github.com/meoyawn/pggen/internal/difftest" + "github.com/meoyawn/pggen/internal/pg" "github.com/stretchr/testify/require" ) diff --git a/internal/codegen/golang/emitter.go b/internal/codegen/golang/emitter.go index e57436ea..5b831d71 100644 --- a/internal/codegen/golang/emitter.go +++ b/internal/codegen/golang/emitter.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/jschaf/pggen/internal/errs" + "github.com/meoyawn/pggen/internal/errs" ) // Emitter writes a templated query file to a file. diff --git a/internal/codegen/golang/generate.go b/internal/codegen/golang/generate.go index 080f2036..df0fbd18 100644 --- a/internal/codegen/golang/generate.go +++ b/internal/codegen/golang/generate.go @@ -7,8 +7,8 @@ import ( "sort" "text/template" - "github.com/jschaf/pggen/internal/casing" - "github.com/jschaf/pggen/internal/codegen" + "github.com/meoyawn/pggen/internal/casing" + "github.com/meoyawn/pggen/internal/codegen" ) // GenerateOptions are options to control generated Go output. diff --git a/internal/codegen/golang/gotype/known_types.go b/internal/codegen/golang/gotype/known_types.go index 3322a6ba..0f44166e 100644 --- a/internal/codegen/golang/gotype/known_types.go +++ b/internal/codegen/golang/gotype/known_types.go @@ -2,8 +2,8 @@ package gotype import ( "github.com/jackc/pgx/v5/pgtype" - "github.com/jschaf/pggen/internal/pg" - "github.com/jschaf/pggen/internal/pg/pgoid" + "github.com/meoyawn/pggen/internal/pg" + "github.com/meoyawn/pggen/internal/pg/pgoid" ) // FindKnownTypePgx returns the native pgx type, like pgtype.Text, if known, for diff --git a/internal/codegen/golang/gotype/types.go b/internal/codegen/golang/gotype/types.go index e02b0808..8bb80728 100644 --- a/internal/codegen/golang/gotype/types.go +++ b/internal/codegen/golang/gotype/types.go @@ -8,13 +8,13 @@ import ( "strings" "unicode" - "github.com/jschaf/pggen/internal/casing" - "github.com/jschaf/pggen/internal/pg" + "github.com/meoyawn/pggen/internal/casing" + "github.com/meoyawn/pggen/internal/pg" ) // Type is a Go type. type Type interface { - // Import returns the full package path, like "github.com/jschaf/pggen/foo". + // Import returns the full package path, like "github.com/meoyawn/pggen/foo". // Empty for builtin types. Import() string // BaseName returns the unqualified, base name of the type, like "Foo" in: @@ -52,7 +52,7 @@ type ( // ImportType is an imported type. ImportType struct { - PkgPath string // fully qualified package path, like "github.com/jschaf/pggen" + PkgPath string // fully qualified package path, like "github.com/meoyawn/pggen" Type Type // type to import } @@ -282,7 +282,7 @@ func MustParseOpaqueType(qualType string) Type { var majorVersionRegexp = regexp.MustCompile(`^v[0-9]+$`) // ExtractShortPackage gets the last part of a package path like "generate" in -// "github.com/jschaf/pggen/generate". +// "github.com/meoyawn/pggen/generate". func ExtractShortPackage(pkgPath []byte) string { parts := bytes.Split(pkgPath, []byte{'/'}) shortPkg := parts[len(parts)-1] diff --git a/internal/codegen/golang/import_set.go b/internal/codegen/golang/import_set.go index 14cb2a8e..e21534c7 100644 --- a/internal/codegen/golang/import_set.go +++ b/internal/codegen/golang/import_set.go @@ -3,7 +3,7 @@ package golang import ( "sort" - "github.com/jschaf/pggen/internal/codegen/golang/gotype" + "github.com/meoyawn/pggen/internal/codegen/golang/gotype" ) // ImportSet contains a set of imports required by one Go file. @@ -16,7 +16,7 @@ func NewImportSet() *ImportSet { } // AddPackage adds a fully qualified package path to the set, like -// "github.com/jschaf/pggen/foo". +// "github.com/meoyawn/pggen/foo". func (s *ImportSet) AddPackage(p string) { s.imports[p] = struct{}{} } diff --git a/internal/codegen/golang/query.gotemplate b/internal/codegen/golang/query.gotemplate index 0617b8c2..c8112f13 100644 --- a/internal/codegen/golang/query.gotemplate +++ b/internal/codegen/golang/query.gotemplate @@ -1,4 +1,4 @@ -{{- /*gotype: github.com/jschaf/pggen/internal/codegen/golang.TemplatedFile*/ -}} +{{- /*gotype: github.com/meoyawn/pggen/internal/codegen/golang.TemplatedFile*/ -}} {{- define "gen_query" -}} // Code generated by pggen. DO NOT EDIT. diff --git a/internal/codegen/golang/templated_file.go b/internal/codegen/golang/templated_file.go index 1ea064c4..fbe248d3 100644 --- a/internal/codegen/golang/templated_file.go +++ b/internal/codegen/golang/templated_file.go @@ -5,9 +5,9 @@ import ( "strconv" "strings" - "github.com/jschaf/pggen/internal/ast" - "github.com/jschaf/pggen/internal/codegen/golang/gotype" - "github.com/jschaf/pggen/internal/pginfer" + "github.com/meoyawn/pggen/internal/ast" + "github.com/meoyawn/pggen/internal/codegen/golang/gotype" + "github.com/meoyawn/pggen/internal/pginfer" ) // TemplatedPackage is all templated files in a pggen invocation. The templated diff --git a/internal/codegen/golang/templater.go b/internal/codegen/golang/templater.go index 7cef961e..b16570aa 100644 --- a/internal/codegen/golang/templater.go +++ b/internal/codegen/golang/templater.go @@ -6,11 +6,11 @@ import ( "strings" "unicode" - "github.com/jschaf/pggen/internal/ast" - "github.com/jschaf/pggen/internal/casing" - "github.com/jschaf/pggen/internal/codegen" - "github.com/jschaf/pggen/internal/codegen/golang/gotype" - "github.com/jschaf/pggen/internal/gomod" + "github.com/meoyawn/pggen/internal/ast" + "github.com/meoyawn/pggen/internal/casing" + "github.com/meoyawn/pggen/internal/codegen" + "github.com/meoyawn/pggen/internal/codegen/golang/gotype" + "github.com/meoyawn/pggen/internal/gomod" ) // Templater creates query file templates. diff --git a/internal/codegen/golang/type_resolver.go b/internal/codegen/golang/type_resolver.go index 179c5bb7..1cc2c10f 100644 --- a/internal/codegen/golang/type_resolver.go +++ b/internal/codegen/golang/type_resolver.go @@ -5,9 +5,9 @@ import ( "strconv" "strings" - "github.com/jschaf/pggen/internal/casing" - "github.com/jschaf/pggen/internal/codegen/golang/gotype" - "github.com/jschaf/pggen/internal/pg" + "github.com/meoyawn/pggen/internal/casing" + "github.com/meoyawn/pggen/internal/codegen/golang/gotype" + "github.com/meoyawn/pggen/internal/pg" ) // TypeResolver handles the mapping between Postgres and Go types. diff --git a/internal/codegen/golang/type_resolver_test.go b/internal/codegen/golang/type_resolver_test.go index aadb6815..bd6a9026 100644 --- a/internal/codegen/golang/type_resolver_test.go +++ b/internal/codegen/golang/type_resolver_test.go @@ -5,15 +5,15 @@ import ( "github.com/google/go-cmp/cmp" "github.com/jackc/pgx/v5/pgtype" - "github.com/jschaf/pggen/internal/casing" - "github.com/jschaf/pggen/internal/codegen/golang/gotype" - "github.com/jschaf/pggen/internal/difftest" - "github.com/jschaf/pggen/internal/pg" + "github.com/meoyawn/pggen/internal/casing" + "github.com/meoyawn/pggen/internal/codegen/golang/gotype" + "github.com/meoyawn/pggen/internal/difftest" + "github.com/meoyawn/pggen/internal/pg" "github.com/stretchr/testify/assert" ) func TestTypeResolver_Resolve(t *testing.T) { - testPkgPath := "github.com/jschaf/pggen/internal/codegen/golang/test_resolve" + testPkgPath := "github.com/meoyawn/pggen/internal/codegen/golang/test_resolve" caser := casing.NewCaser() caser.AddAcronym("ios", "IOS") caser.AddAcronym("macos", "MacOS") diff --git a/internal/gomod/gomod.go b/internal/gomod/gomod.go index fca17f7c..b67d5411 100644 --- a/internal/gomod/gomod.go +++ b/internal/gomod/gomod.go @@ -8,7 +8,7 @@ import ( "path/filepath" "sync" - "github.com/jschaf/pggen/internal/paths" + "github.com/meoyawn/pggen/internal/paths" "golang.org/x/mod/modfile" ) diff --git a/internal/gomod/gomod_test.go b/internal/gomod/gomod_test.go index a0b9cbfe..802ee428 100644 --- a/internal/gomod/gomod_test.go +++ b/internal/gomod/gomod_test.go @@ -13,19 +13,19 @@ func TestResolvePackage(t *testing.T) { }{ { path: "Foo.go", - want: "github.com/jschaf/pggen/internal/gomod", + want: "github.com/meoyawn/pggen/internal/gomod", }, { path: "../Foo.go", - want: "github.com/jschaf/pggen/internal", + want: "github.com/meoyawn/pggen/internal", }, { path: "./Foo.go", - want: "github.com/jschaf/pggen/internal/gomod", + want: "github.com/meoyawn/pggen/internal/gomod", }, { path: "blah/qux/Foo.go", - want: "github.com/jschaf/pggen/internal/gomod/blah/qux", + want: "github.com/meoyawn/pggen/internal/gomod/blah/qux", }, } for _, tt := range tests { diff --git a/internal/parser/interface.go b/internal/parser/interface.go index e14c4b0c..82c82e67 100644 --- a/internal/parser/interface.go +++ b/internal/parser/interface.go @@ -8,7 +8,7 @@ import ( "io" "os" - "github.com/jschaf/pggen/internal/ast" + "github.com/meoyawn/pggen/internal/ast" ) // If src != nil, readSource converts src to a []byte if possible; otherwise it diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 0475457b..15deb4ca 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -8,9 +8,9 @@ import ( "strconv" "strings" - "github.com/jschaf/pggen/internal/ast" - "github.com/jschaf/pggen/internal/scanner" - "github.com/jschaf/pggen/internal/token" + "github.com/meoyawn/pggen/internal/ast" + "github.com/meoyawn/pggen/internal/scanner" + "github.com/meoyawn/pggen/internal/token" ) type parser struct { diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go index 0226eef9..75a382bd 100644 --- a/internal/parser/parser_test.go +++ b/internal/parser/parser_test.go @@ -6,7 +6,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/jschaf/pggen/internal/ast" + "github.com/meoyawn/pggen/internal/ast" ) func ignoreCommentPos() cmp.Option { diff --git a/internal/pg/column.go b/internal/pg/column.go index 365c50b4..e8c2236e 100644 --- a/internal/pg/column.go +++ b/internal/pg/column.go @@ -9,7 +9,7 @@ import ( "time" "github.com/jackc/pgx/v5" - "github.com/jschaf/pggen/internal/texts" + "github.com/meoyawn/pggen/internal/texts" ) // Column stores information about a column in a TableOID. diff --git a/internal/pg/column_test.go b/internal/pg/column_test.go index 9f789c55..c6033894 100644 --- a/internal/pg/column_test.go +++ b/internal/pg/column_test.go @@ -8,8 +8,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/jackc/pgx/v5" - "github.com/jschaf/pggen/internal/pgtest" - "github.com/jschaf/pggen/internal/texts" + "github.com/meoyawn/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/texts" "github.com/stretchr/testify/assert" ) diff --git a/internal/pg/known_types.go b/internal/pg/known_types.go index 4a13ac06..547ac763 100644 --- a/internal/pg/known_types.go +++ b/internal/pg/known_types.go @@ -2,7 +2,7 @@ package pg import ( "github.com/jackc/pgx/v5/pgtype" - "github.com/jschaf/pggen/internal/pg/pgoid" + "github.com/meoyawn/pggen/internal/pg/pgoid" ) // If you add to this list, also add to defaultKnownTypes below. diff --git a/internal/pg/type_fetcher_test.go b/internal/pg/type_fetcher_test.go index 5f1900b4..a68e59cf 100644 --- a/internal/pg/type_fetcher_test.go +++ b/internal/pg/type_fetcher_test.go @@ -6,9 +6,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/jschaf/pggen/internal/pg/pgoid" - "github.com/jschaf/pggen/internal/pgtest" - "github.com/jschaf/pggen/internal/texts" + "github.com/meoyawn/pggen/internal/pg/pgoid" + "github.com/meoyawn/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/texts" ) func TestNewTypeFetcher(t *testing.T) { diff --git a/internal/pg/types.go b/internal/pg/types.go index c5d36bb5..ed7c06ef 100644 --- a/internal/pg/types.go +++ b/internal/pg/types.go @@ -3,7 +3,7 @@ package pg import ( "strconv" - "github.com/jschaf/pggen/internal/pg/pgoid" + "github.com/meoyawn/pggen/internal/pg/pgoid" ) // Type is a Postgres type. diff --git a/internal/pgdocker/cmd/pgdocker/main.go b/internal/pgdocker/cmd/pgdocker/main.go index 765acecd..278a88ab 100644 --- a/internal/pgdocker/cmd/pgdocker/main.go +++ b/internal/pgdocker/cmd/pgdocker/main.go @@ -11,8 +11,8 @@ import ( "os/exec" "time" - "github.com/jschaf/pggen/internal/errs" - "github.com/jschaf/pggen/internal/pgdocker" + "github.com/meoyawn/pggen/internal/errs" + "github.com/meoyawn/pggen/internal/pgdocker" ) func main() { diff --git a/internal/pgdocker/pgdocker.go b/internal/pgdocker/pgdocker.go index 962edb01..4462a9cc 100644 --- a/internal/pgdocker/pgdocker.go +++ b/internal/pgdocker/pgdocker.go @@ -22,8 +22,8 @@ import ( dockerClient "github.com/docker/docker/client" "github.com/docker/go-connections/nat" "github.com/jackc/pgx/v5" - "github.com/jschaf/pggen/internal/errs" - "github.com/jschaf/pggen/internal/ports" + "github.com/meoyawn/pggen/internal/errs" + "github.com/meoyawn/pggen/internal/ports" ) // Client is a client to control the running Postgres Docker container. diff --git a/internal/pgdocker/template.go b/internal/pgdocker/template.go index 88ec2b51..11d9b756 100644 --- a/internal/pgdocker/template.go +++ b/internal/pgdocker/template.go @@ -6,7 +6,7 @@ type pgTemplate struct { } const dockerfileTemplate = ` -{{- /*gotype: github.com/jschaf/pggen/internal/pgdocker.pgTemplate*/ -}} +{{- /*gotype: github.com/meoyawn/pggen/internal/pgdocker.pgTemplate*/ -}} {{- define "dockerfile" -}} FROM postgres:13 {{ range .InitScripts }} diff --git a/internal/pginfer/explain.go b/internal/pginfer/explain.go index 46a1cddd..125a5593 100644 --- a/internal/pginfer/explain.go +++ b/internal/pginfer/explain.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/jschaf/pggen/internal/ast" + "github.com/meoyawn/pggen/internal/ast" ) // PlanType is the top-level node plan type that Postgres plans for executing diff --git a/internal/pginfer/nullability.go b/internal/pginfer/nullability.go index e1b5d860..a0ab1004 100644 --- a/internal/pginfer/nullability.go +++ b/internal/pginfer/nullability.go @@ -4,8 +4,8 @@ import ( "strings" "unicode" - "github.com/jschaf/pggen/internal/ast" - "github.com/jschaf/pggen/internal/pg" + "github.com/meoyawn/pggen/internal/ast" + "github.com/meoyawn/pggen/internal/pg" ) // isColNullable tries to prove the column is not nullable. Strive for diff --git a/internal/pginfer/pginfer.go b/internal/pginfer/pginfer.go index 35f08758..d88cbf91 100644 --- a/internal/pginfer/pginfer.go +++ b/internal/pginfer/pginfer.go @@ -9,8 +9,8 @@ import ( "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jschaf/pggen/internal/ast" - "github.com/jschaf/pggen/internal/pg" + "github.com/meoyawn/pggen/internal/ast" + "github.com/meoyawn/pggen/internal/pg" ) const defaultTimeout = 3 * time.Second diff --git a/internal/pginfer/pginfer_test.go b/internal/pginfer/pginfer_test.go index 8a1c8e41..10f30408 100644 --- a/internal/pginfer/pginfer_test.go +++ b/internal/pginfer/pginfer_test.go @@ -6,11 +6,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/jschaf/pggen/internal/ast" - "github.com/jschaf/pggen/internal/difftest" - "github.com/jschaf/pggen/internal/pg" - "github.com/jschaf/pggen/internal/pgtest" - "github.com/jschaf/pggen/internal/texts" + "github.com/meoyawn/pggen/internal/ast" + "github.com/meoyawn/pggen/internal/difftest" + "github.com/meoyawn/pggen/internal/pg" + "github.com/meoyawn/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/texts" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/internal/pgplan/pgplan_test.go b/internal/pgplan/pgplan_test.go index 8985bfb4..0947ba70 100644 --- a/internal/pgplan/pgplan_test.go +++ b/internal/pgplan/pgplan_test.go @@ -5,8 +5,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/jschaf/pggen/internal/pgtest" - "github.com/jschaf/pggen/internal/texts" + "github.com/meoyawn/pggen/internal/pgtest" + "github.com/meoyawn/pggen/internal/texts" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/internal/ports/port.go b/internal/ports/port.go index 7e8bb895..59daa3a7 100644 --- a/internal/ports/port.go +++ b/internal/ports/port.go @@ -3,7 +3,7 @@ package ports import ( "net" - "github.com/jschaf/pggen/internal/errs" + "github.com/meoyawn/pggen/internal/errs" ) // Port is a port. diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index fd16bb27..a0cfc9d5 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -7,7 +7,7 @@ import ( "unicode" "unicode/utf8" - "github.com/jschaf/pggen/internal/token" + "github.com/meoyawn/pggen/internal/token" ) const ( diff --git a/internal/scanner/scanner_test.go b/internal/scanner/scanner_test.go index 4ad29bbb..0fc4350f 100644 --- a/internal/scanner/scanner_test.go +++ b/internal/scanner/scanner_test.go @@ -4,7 +4,7 @@ import ( gotok "go/token" "testing" - "github.com/jschaf/pggen/internal/token" + "github.com/meoyawn/pggen/internal/token" "github.com/stretchr/testify/assert" ) From 10ab9427e3ce14fa938c2e5864d2957652463586 Mon Sep 17 00:00:00 2001 From: meoyawn Date: Thu, 7 May 2026 11:33:18 +0000 Subject: [PATCH 18/27] Upgrade Docker dependencies --- go.mod | 8 +++--- go.sum | 47 +++++++++-------------------------- internal/pgdocker/pgdocker.go | 4 +-- 3 files changed, 19 insertions(+), 40 deletions(-) diff --git a/go.mod b/go.mod index 0e770b2a..39b886cc 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.24.1 require ( github.com/bmatcuk/doublestar v1.3.4 - github.com/docker/docker v28.0.1+incompatible - github.com/docker/go-connections v0.5.0 + github.com/docker/docker v28.5.2+incompatible + github.com/docker/go-connections v0.7.0 github.com/google/go-cmp v0.7.0 github.com/jackc/pgx/v5 v5.5.1 github.com/peterbourgon/ff/v3 v3.4.0 @@ -15,6 +15,8 @@ require ( require ( github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect @@ -22,10 +24,10 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect diff --git a/go.sum b/go.sum index 4e40167b..e81ac77d 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,10 @@ github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQ github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,10 +17,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0= -github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= +github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -26,8 +30,6 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -42,14 +44,16 @@ github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI= github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -73,8 +77,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= @@ -95,45 +97,20 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= diff --git a/internal/pgdocker/pgdocker.go b/internal/pgdocker/pgdocker.go index 4462a9cc..2ca854fd 100644 --- a/internal/pgdocker/pgdocker.go +++ b/internal/pgdocker/pgdocker.go @@ -17,7 +17,7 @@ import ( "text/template" "time" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/build" "github.com/docker/docker/api/types/container" dockerClient "github.com/docker/docker/client" "github.com/docker/go-connections/nat" @@ -150,7 +150,7 @@ func (c *Client) buildImage(ctx context.Context, initScripts []string) (id strin slog.DebugContext(ctx, "wrote tar dockerfile into buffer") // Send build request. - opts := types.ImageBuildOptions{Dockerfile: "Dockerfile"} + opts := build.ImageBuildOptions{Dockerfile: "Dockerfile"} resp, err := c.docker.ImageBuild(ctx, tarR, opts) if err != nil { return "", fmt.Errorf("build postgres docker image: %w", err) From 0751c2b0c689716c1822796c759250ca501e72e5 Mon Sep 17 00:00:00 2001 From: adelnizamutdinov Date: Tue, 2 Jun 2026 22:53:55 +0300 Subject: [PATCH 19/27] Add projection interfaces for query rows Closes https://github.com/jschaf/pggen/issues/44 --- Makefile | 2 +- example/author/query.sql.go | 69 ++ example/composite/query.sql.go | 21 + example/custom_types/query.sql.go | 9 + example/device/query.sql.go | 33 + example/enums/query.sql.go | 18 + example/erp/order/customer.sql.go | 69 ++ example/erp/order/price.sql.go | 24 + example/function/query.sql.go | 9 + .../inline_param_count/inline0/query.sql.go | 15 + .../inline_param_count/inline1/query.sql.go | 15 + .../inline_param_count/inline2/query.sql.go | 15 + .../inline_param_count/inline3/query.sql.go | 15 + example/ltree/query.sql.go | 9 + example/pgcrypto/query.sql.go | 9 + example/syntax/query.sql.go | 9 + example/void/query.sql.go | 9 + go.mod | 180 +++- go.sum | 942 +++++++++++++++++- internal/codegen/golang/query.gotemplate | 2 + internal/codegen/golang/templated_file.go | 57 +- .../codegen/golang/templated_file_test.go | 74 ++ internal/codegen/golang/templater.go | 26 + internal/pg/query.sql.go | 78 ++ 24 files changed, 1698 insertions(+), 11 deletions(-) create mode 100644 internal/codegen/golang/templated_file_test.go diff --git a/Makefile b/Makefile index 282c8f72..2fa4e1d5 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ update-acceptance-test: .PHONY: lint lint: - golangci-lint run + go tool golangci-lint run .PHONY: dist-dir dist-dir: diff --git a/example/author/query.sql.go b/example/author/query.sql.go index 8c19d2cf..7a17cff2 100644 --- a/example/author/query.sql.go +++ b/example/author/query.sql.go @@ -103,6 +103,13 @@ func addTypeToRegister(typ string) struct{} { const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` +type FindAuthorByIDProjection interface { + GetAuthorID() int32 + GetFirstName() string + GetLastName() string + GetSuffix() *string +} + type FindAuthorByIDRow struct { AuthorID int32 `json:"author_id" db:"author_id"` FirstName string `json:"first_name" db:"first_name"` @@ -110,6 +117,14 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix" db:"suffix"` } +func (r FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } + +func (r FindAuthorByIDRow) GetFirstName() string { return r.FirstName } + +func (r FindAuthorByIDRow) GetLastName() string { return r.LastName } + +func (r FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } + // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") @@ -129,6 +144,13 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut const findAuthorsSQL = `SELECT * FROM author WHERE first_name = $1;` +type FindAuthorsProjection interface { + GetAuthorID() int32 + GetFirstName() string + GetLastName() string + GetSuffix() *string +} + type FindAuthorsRow struct { AuthorID int32 `json:"author_id" db:"author_id"` FirstName string `json:"first_name" db:"first_name"` @@ -136,6 +158,14 @@ type FindAuthorsRow struct { Suffix *string `json:"suffix" db:"suffix"` } +func (r FindAuthorsRow) GetAuthorID() int32 { return r.AuthorID } + +func (r FindAuthorsRow) GetFirstName() string { return r.FirstName } + +func (r FindAuthorsRow) GetLastName() string { return r.LastName } + +func (r FindAuthorsRow) GetSuffix() *string { return r.Suffix } + // FindAuthors implements Querier.FindAuthors. func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAuthorsRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAuthors") @@ -155,11 +185,20 @@ func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAu const findAuthorNamesSQL = `SELECT first_name, last_name FROM author ORDER BY author_id = $1;` +type FindAuthorNamesProjection interface { + GetFirstName() *string + GetLastName() *string +} + type FindAuthorNamesRow struct { FirstName *string `json:"first_name" db:"first_name"` LastName *string `json:"last_name" db:"last_name"` } +func (r FindAuthorNamesRow) GetFirstName() *string { return r.FirstName } + +func (r FindAuthorNamesRow) GetLastName() *string { return r.LastName } + // FindAuthorNames implements Querier.FindAuthorNames. func (q *DBQuerier) FindAuthorNames(ctx context.Context, authorID int32) ([]FindAuthorNamesRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAuthorNames") @@ -198,6 +237,13 @@ func (q *DBQuerier) FindFirstNames(ctx context.Context, authorID int32) ([]*stri const findFirstAuthorSQL = `SELECT * FROM author ORDER BY author_id;` +type FindFirstAuthorProjection interface { + GetAuthorID() *int32 + GetFirstName() *string + GetLastName() *string + GetSuffix() *string +} + type FindFirstAuthorRow struct { AuthorID *int32 `json:"author_id" db:"author_id"` FirstName *string `json:"first_name" db:"first_name"` @@ -205,6 +251,14 @@ type FindFirstAuthorRow struct { Suffix *string `json:"suffix" db:"suffix"` } +func (r FindFirstAuthorRow) GetAuthorID() *int32 { return r.AuthorID } + +func (r FindFirstAuthorRow) GetFirstName() *string { return r.FirstName } + +func (r FindFirstAuthorRow) GetLastName() *string { return r.LastName } + +func (r FindFirstAuthorRow) GetSuffix() *string { return r.Suffix } + // FindFirstAuthor implements Querier.FindFirstAuthor. func (q *DBQuerier) FindFirstAuthor(ctx context.Context) (FindFirstAuthorRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindFirstAuthor") @@ -299,6 +353,13 @@ type InsertAuthorSuffixParams struct { Suffix string `json:"Suffix"` } +type InsertAuthorSuffixProjection interface { + GetAuthorID() int32 + GetFirstName() string + GetLastName() string + GetSuffix() *string +} + type InsertAuthorSuffixRow struct { AuthorID int32 `json:"author_id" db:"author_id"` FirstName string `json:"first_name" db:"first_name"` @@ -306,6 +367,14 @@ type InsertAuthorSuffixRow struct { Suffix *string `json:"suffix" db:"suffix"` } +func (r InsertAuthorSuffixRow) GetAuthorID() int32 { return r.AuthorID } + +func (r InsertAuthorSuffixRow) GetFirstName() string { return r.FirstName } + +func (r InsertAuthorSuffixRow) GetLastName() string { return r.LastName } + +func (r InsertAuthorSuffixRow) GetSuffix() *string { return r.Suffix } + // InsertAuthorSuffix implements Querier.InsertAuthorSuffix. func (q *DBQuerier) InsertAuthorSuffix(ctx context.Context, params InsertAuthorSuffixParams) (InsertAuthorSuffixRow, error) { ctx = context.WithValue(ctx, QueryName{}, "InsertAuthorSuffix") diff --git a/example/composite/query.sql.go b/example/composite/query.sql.go index 4edda308..90a78b64 100644 --- a/example/composite/query.sql.go +++ b/example/composite/query.sql.go @@ -122,11 +122,20 @@ type SearchScreenshotsParams struct { Offset int `json:"Offset"` } +type SearchScreenshotsProjection interface { + GetID() int + GetBlocks() []Blocks +} + type SearchScreenshotsRow struct { ID int `json:"id" db:"id"` Blocks []Blocks `json:"blocks" db:"blocks"` } +func (r SearchScreenshotsRow) GetID() int { return r.ID } + +func (r SearchScreenshotsRow) GetBlocks() []Blocks { return r.Blocks } + // SearchScreenshots implements Querier.SearchScreenshots. func (q *DBQuerier) SearchScreenshots(ctx context.Context, params SearchScreenshotsParams) ([]SearchScreenshotsRow, error) { ctx = context.WithValue(ctx, QueryName{}, "SearchScreenshots") @@ -185,12 +194,24 @@ INTO blocks (screenshot_id, body) VALUES ($1, $2) RETURNING id, screenshot_id, body;` +type InsertScreenshotBlocksProjection interface { + GetID() int + GetScreenshotID() int + GetBody() string +} + type InsertScreenshotBlocksRow struct { ID int `json:"id" db:"id"` ScreenshotID int `json:"screenshot_id" db:"screenshot_id"` Body string `json:"body" db:"body"` } +func (r InsertScreenshotBlocksRow) GetID() int { return r.ID } + +func (r InsertScreenshotBlocksRow) GetScreenshotID() int { return r.ScreenshotID } + +func (r InsertScreenshotBlocksRow) GetBody() string { return r.Body } + // InsertScreenshotBlocks implements Querier.InsertScreenshotBlocks. func (q *DBQuerier) InsertScreenshotBlocks(ctx context.Context, screenshotID int, body string) (InsertScreenshotBlocksRow, error) { ctx = context.WithValue(ctx, QueryName{}, "InsertScreenshotBlocks") diff --git a/example/custom_types/query.sql.go b/example/custom_types/query.sql.go index 990ddb1a..f5e665d8 100644 --- a/example/custom_types/query.sql.go +++ b/example/custom_types/query.sql.go @@ -77,11 +77,20 @@ func addTypeToRegister(typ string) struct{} { const customTypesSQL = `SELECT 'some_text', 1::bigint;` +type CustomTypesProjection interface { + GetColumn() mytype.String + GetInt8() CustomInt +} + type CustomTypesRow struct { Column mytype.String `json:"?column?" db:"?column?"` Int8 CustomInt `json:"int8" db:"int8"` } +func (r CustomTypesRow) GetColumn() mytype.String { return r.Column } + +func (r CustomTypesRow) GetInt8() CustomInt { return r.Int8 } + // CustomTypes implements Querier.CustomTypes. func (q *DBQuerier) CustomTypes(ctx context.Context) (CustomTypesRow, error) { ctx = context.WithValue(ctx, QueryName{}, "CustomTypes") diff --git a/example/device/query.sql.go b/example/device/query.sql.go index 5fe9dd59..8590e7f4 100644 --- a/example/device/query.sql.go +++ b/example/device/query.sql.go @@ -112,12 +112,24 @@ const findDevicesByUserSQL = `SELECT FROM "user" WHERE id = $1;` +type FindDevicesByUserProjection interface { + GetID() int + GetName() string + GetMacAddrs() []net.HardwareAddr +} + type FindDevicesByUserRow struct { ID int `json:"id" db:"id"` Name string `json:"name" db:"name"` MacAddrs []net.HardwareAddr `json:"mac_addrs" db:"mac_addrs"` } +func (r FindDevicesByUserRow) GetID() int { return r.ID } + +func (r FindDevicesByUserRow) GetName() string { return r.Name } + +func (r FindDevicesByUserRow) GetMacAddrs() []net.HardwareAddr { return r.MacAddrs } + // FindDevicesByUser implements Querier.FindDevicesByUser. func (q *DBQuerier) FindDevicesByUser(ctx context.Context, id int) ([]FindDevicesByUserRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindDevicesByUser") @@ -142,12 +154,24 @@ const compositeUserSQL = `SELECT FROM device d LEFT JOIN "user" u ON u.id = d.owner;` +type CompositeUserProjection interface { + GetMac() net.HardwareAddr + GetType() DeviceType + GetUser() User +} + type CompositeUserRow struct { Mac net.HardwareAddr `json:"mac" db:"mac"` Type DeviceType `json:"type" db:"type"` User User `json:"user" db:"user"` } +func (r CompositeUserRow) GetMac() net.HardwareAddr { return r.Mac } + +func (r CompositeUserRow) GetType() DeviceType { return r.Type } + +func (r CompositeUserRow) GetUser() User { return r.User } + // CompositeUser implements Querier.CompositeUser. func (q *DBQuerier) CompositeUser(ctx context.Context) ([]CompositeUserRow, error) { ctx = context.WithValue(ctx, QueryName{}, "CompositeUser") @@ -186,11 +210,20 @@ func (q *DBQuerier) CompositeUserOne(ctx context.Context) (User, error) { const compositeUserOneTwoColsSQL = `SELECT 1 AS num, ROW (15, 'qux')::"user" AS "user";` +type CompositeUserOneTwoColsProjection interface { + GetNum() int32 + GetUser() User +} + type CompositeUserOneTwoColsRow struct { Num int32 `json:"num" db:"num"` User User `json:"user" db:"user"` } +func (r CompositeUserOneTwoColsRow) GetNum() int32 { return r.Num } + +func (r CompositeUserOneTwoColsRow) GetUser() User { return r.User } + // CompositeUserOneTwoCols implements Querier.CompositeUserOneTwoCols. func (q *DBQuerier) CompositeUserOneTwoCols(ctx context.Context) (CompositeUserOneTwoColsRow, error) { ctx = context.WithValue(ctx, QueryName{}, "CompositeUserOneTwoCols") diff --git a/example/enums/query.sql.go b/example/enums/query.sql.go index ce0e31d4..c7ce05ae 100644 --- a/example/enums/query.sql.go +++ b/example/enums/query.sql.go @@ -112,11 +112,20 @@ var _ = addTypeToRegister("\"_device_type\"") const findAllDevicesSQL = `SELECT mac, type FROM device;` +type FindAllDevicesProjection interface { + GetMac() net.HardwareAddr + GetType() DeviceType +} + type FindAllDevicesRow struct { Mac net.HardwareAddr `json:"mac" db:"mac"` Type DeviceType `json:"type" db:"type"` } +func (r FindAllDevicesRow) GetMac() net.HardwareAddr { return r.Mac } + +func (r FindAllDevicesRow) GetType() DeviceType { return r.Type } + // FindAllDevices implements Querier.FindAllDevices. func (q *DBQuerier) FindAllDevices(ctx context.Context) ([]FindAllDevicesRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAllDevices") @@ -191,11 +200,20 @@ const findManyDeviceArrayWithNumSQL = `SELECT 1 AS num, enum_range('ipad'::devic UNION ALL SELECT 2 as num, enum_range(NULL::device_type) AS device_types;` +type FindManyDeviceArrayWithNumProjection interface { + GetNum() *int32 + GetDeviceTypes() []DeviceType +} + type FindManyDeviceArrayWithNumRow struct { Num *int32 `json:"num" db:"num"` DeviceTypes []DeviceType `json:"device_types" db:"device_types"` } +func (r FindManyDeviceArrayWithNumRow) GetNum() *int32 { return r.Num } + +func (r FindManyDeviceArrayWithNumRow) GetDeviceTypes() []DeviceType { return r.DeviceTypes } + // FindManyDeviceArrayWithNum implements Querier.FindManyDeviceArrayWithNum. func (q *DBQuerier) FindManyDeviceArrayWithNum(ctx context.Context) ([]FindManyDeviceArrayWithNumRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindManyDeviceArrayWithNum") diff --git a/example/erp/order/customer.sql.go b/example/erp/order/customer.sql.go index 17a1a3f8..807f61d4 100644 --- a/example/erp/order/customer.sql.go +++ b/example/erp/order/customer.sql.go @@ -85,12 +85,24 @@ const createTenantSQL = `INSERT INTO tenant (tenant_id, name) VALUES (base36_decode($1::text)::tenant_id, $2::text) RETURNING *;` +type CreateTenantProjection interface { + GetTenantID() int + GetRname() *string + GetName() string +} + type CreateTenantRow struct { TenantID int `json:"tenant_id" db:"tenant_id"` Rname *string `json:"rname" db:"rname"` Name string `json:"name" db:"name"` } +func (r CreateTenantRow) GetTenantID() int { return r.TenantID } + +func (r CreateTenantRow) GetRname() *string { return r.Rname } + +func (r CreateTenantRow) GetName() string { return r.Name } + // CreateTenant implements Querier.CreateTenant. func (q *DBQuerier) CreateTenant(ctx context.Context, key string, name string) (CreateTenantRow, error) { ctx = context.WithValue(ctx, QueryName{}, "CreateTenant") @@ -112,6 +124,13 @@ const findOrdersByCustomerSQL = `SELECT * FROM orders WHERE customer_id = $1;` +type FindOrdersByCustomerProjection interface { + GetOrderID() int32 + GetOrderDate() pgtype.Timestamptz + GetOrderTotal() pgtype.Numeric + GetCustomerID() *int32 +} + type FindOrdersByCustomerRow struct { OrderID int32 `json:"order_id" db:"order_id"` OrderDate pgtype.Timestamptz `json:"order_date" db:"order_date"` @@ -119,6 +138,14 @@ type FindOrdersByCustomerRow struct { CustomerID *int32 `json:"customer_id" db:"customer_id"` } +func (r FindOrdersByCustomerRow) GetOrderID() int32 { return r.OrderID } + +func (r FindOrdersByCustomerRow) GetOrderDate() pgtype.Timestamptz { return r.OrderDate } + +func (r FindOrdersByCustomerRow) GetOrderTotal() pgtype.Numeric { return r.OrderTotal } + +func (r FindOrdersByCustomerRow) GetCustomerID() *int32 { return r.CustomerID } + // FindOrdersByCustomer implements Querier.FindOrdersByCustomer. func (q *DBQuerier) FindOrdersByCustomer(ctx context.Context, customerID int32) ([]FindOrdersByCustomerRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindOrdersByCustomer") @@ -142,12 +169,24 @@ FROM orders o INNER JOIN product p USING (product_id) WHERE o.order_id = $1;` +type FindProductsInOrderProjection interface { + GetOrderID() *int32 + GetProductID() *int32 + GetName() *string +} + type FindProductsInOrderRow struct { OrderID *int32 `json:"order_id" db:"order_id"` ProductID *int32 `json:"product_id" db:"product_id"` Name *string `json:"name" db:"name"` } +func (r FindProductsInOrderRow) GetOrderID() *int32 { return r.OrderID } + +func (r FindProductsInOrderRow) GetProductID() *int32 { return r.ProductID } + +func (r FindProductsInOrderRow) GetName() *string { return r.Name } + // FindProductsInOrder implements Querier.FindProductsInOrder. func (q *DBQuerier) FindProductsInOrder(ctx context.Context, orderID int32) ([]FindProductsInOrderRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindProductsInOrder") @@ -175,6 +214,13 @@ type InsertCustomerParams struct { Email string `json:"email"` } +type InsertCustomerProjection interface { + GetCustomerID() int32 + GetFirstName() string + GetLastName() string + GetEmail() string +} + type InsertCustomerRow struct { CustomerID int32 `json:"customer_id" db:"customer_id"` FirstName string `json:"first_name" db:"first_name"` @@ -182,6 +228,14 @@ type InsertCustomerRow struct { Email string `json:"email" db:"email"` } +func (r InsertCustomerRow) GetCustomerID() int32 { return r.CustomerID } + +func (r InsertCustomerRow) GetFirstName() string { return r.FirstName } + +func (r InsertCustomerRow) GetLastName() string { return r.LastName } + +func (r InsertCustomerRow) GetEmail() string { return r.Email } + // InsertCustomer implements Querier.InsertCustomer. func (q *DBQuerier) InsertCustomer(ctx context.Context, params InsertCustomerParams) (InsertCustomerRow, error) { ctx = context.WithValue(ctx, QueryName{}, "InsertCustomer") @@ -209,6 +263,13 @@ type InsertOrderParams struct { CustID int32 `json:"cust_id"` } +type InsertOrderProjection interface { + GetOrderID() int32 + GetOrderDate() pgtype.Timestamptz + GetOrderTotal() pgtype.Numeric + GetCustomerID() *int32 +} + type InsertOrderRow struct { OrderID int32 `json:"order_id" db:"order_id"` OrderDate pgtype.Timestamptz `json:"order_date" db:"order_date"` @@ -216,6 +277,14 @@ type InsertOrderRow struct { CustomerID *int32 `json:"customer_id" db:"customer_id"` } +func (r InsertOrderRow) GetOrderID() int32 { return r.OrderID } + +func (r InsertOrderRow) GetOrderDate() pgtype.Timestamptz { return r.OrderDate } + +func (r InsertOrderRow) GetOrderTotal() pgtype.Numeric { return r.OrderTotal } + +func (r InsertOrderRow) GetCustomerID() *int32 { return r.CustomerID } + // InsertOrder implements Querier.InsertOrder. func (q *DBQuerier) InsertOrder(ctx context.Context, params InsertOrderParams) (InsertOrderRow, error) { ctx = context.WithValue(ctx, QueryName{}, "InsertOrder") diff --git a/example/erp/order/price.sql.go b/example/erp/order/price.sql.go index cd0d7190..19c32c69 100644 --- a/example/erp/order/price.sql.go +++ b/example/erp/order/price.sql.go @@ -11,6 +11,13 @@ import ( const findOrdersByPriceSQL = `SELECT * FROM orders WHERE order_total > $1;` +type FindOrdersByPriceProjection interface { + GetOrderID() int32 + GetOrderDate() pgtype.Timestamptz + GetOrderTotal() pgtype.Numeric + GetCustomerID() *int32 +} + type FindOrdersByPriceRow struct { OrderID int32 `json:"order_id" db:"order_id"` OrderDate pgtype.Timestamptz `json:"order_date" db:"order_date"` @@ -18,6 +25,14 @@ type FindOrdersByPriceRow struct { CustomerID *int32 `json:"customer_id" db:"customer_id"` } +func (r FindOrdersByPriceRow) GetOrderID() int32 { return r.OrderID } + +func (r FindOrdersByPriceRow) GetOrderDate() pgtype.Timestamptz { return r.OrderDate } + +func (r FindOrdersByPriceRow) GetOrderTotal() pgtype.Numeric { return r.OrderTotal } + +func (r FindOrdersByPriceRow) GetCustomerID() *int32 { return r.CustomerID } + // FindOrdersByPrice implements Querier.FindOrdersByPrice. func (q *DBQuerier) FindOrdersByPrice(ctx context.Context, minTotal pgtype.Numeric) ([]FindOrdersByPriceRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindOrdersByPrice") @@ -39,11 +54,20 @@ const findOrdersMRRSQL = `SELECT date_trunc('month', order_date) AS month, sum(o FROM orders GROUP BY date_trunc('month', order_date);` +type FindOrdersMRRProjection interface { + GetMonth() pgtype.Timestamptz + GetOrderMRR() pgtype.Numeric +} + type FindOrdersMRRRow struct { Month pgtype.Timestamptz `json:"month" db:"month"` OrderMRR pgtype.Numeric `json:"order_mrr" db:"order_mrr"` } +func (r FindOrdersMRRRow) GetMonth() pgtype.Timestamptz { return r.Month } + +func (r FindOrdersMRRRow) GetOrderMRR() pgtype.Numeric { return r.OrderMRR } + // FindOrdersMRR implements Querier.FindOrdersMRR. func (q *DBQuerier) FindOrdersMRR(ctx context.Context) ([]FindOrdersMRRRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindOrdersMRR") diff --git a/example/function/query.sql.go b/example/function/query.sql.go index 4d3b3aee..6f3b17d6 100644 --- a/example/function/query.sql.go +++ b/example/function/query.sql.go @@ -88,11 +88,20 @@ var _ = addTypeToRegister("\"_list_item\"") const outParamsSQL = `SELECT * FROM out_params();` +type OutParamsProjection interface { + GetItems() []ListItem + GetStats() ListStats +} + type OutParamsRow struct { Items []ListItem `json:"_items" db:"_items"` Stats ListStats `json:"_stats" db:"_stats"` } +func (r OutParamsRow) GetItems() []ListItem { return r.Items } + +func (r OutParamsRow) GetStats() ListStats { return r.Stats } + // OutParams implements Querier.OutParams. func (q *DBQuerier) OutParams(ctx context.Context) ([]OutParamsRow, error) { ctx = context.WithValue(ctx, QueryName{}, "OutParams") diff --git a/example/inline_param_count/inline0/query.sql.go b/example/inline_param_count/inline0/query.sql.go index 61529bf7..10af6deb 100644 --- a/example/inline_param_count/inline0/query.sql.go +++ b/example/inline_param_count/inline0/query.sql.go @@ -103,6 +103,13 @@ type FindAuthorByIDParams struct { AuthorID int32 `json:"AuthorID"` } +type FindAuthorByIDProjection interface { + GetAuthorID() int32 + GetFirstName() string + GetLastName() string + GetSuffix() *string +} + type FindAuthorByIDRow struct { AuthorID int32 `json:"author_id" db:"author_id"` FirstName string `json:"first_name" db:"first_name"` @@ -110,6 +117,14 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix" db:"suffix"` } +func (r FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } + +func (r FindAuthorByIDRow) GetFirstName() string { return r.FirstName } + +func (r FindAuthorByIDRow) GetLastName() string { return r.LastName } + +func (r FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } + // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, params FindAuthorByIDParams) (FindAuthorByIDRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") diff --git a/example/inline_param_count/inline1/query.sql.go b/example/inline_param_count/inline1/query.sql.go index 134befbf..ee37add6 100644 --- a/example/inline_param_count/inline1/query.sql.go +++ b/example/inline_param_count/inline1/query.sql.go @@ -99,6 +99,13 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` +type FindAuthorByIDProjection interface { + GetAuthorID() int32 + GetFirstName() string + GetLastName() string + GetSuffix() *string +} + type FindAuthorByIDRow struct { AuthorID int32 `json:"author_id" db:"author_id"` FirstName string `json:"first_name" db:"first_name"` @@ -106,6 +113,14 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix" db:"suffix"` } +func (r FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } + +func (r FindAuthorByIDRow) GetFirstName() string { return r.FirstName } + +func (r FindAuthorByIDRow) GetLastName() string { return r.LastName } + +func (r FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } + // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") diff --git a/example/inline_param_count/inline2/query.sql.go b/example/inline_param_count/inline2/query.sql.go index 96dd4320..a2ea2481 100644 --- a/example/inline_param_count/inline2/query.sql.go +++ b/example/inline_param_count/inline2/query.sql.go @@ -99,6 +99,13 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` +type FindAuthorByIDProjection interface { + GetAuthorID() int32 + GetFirstName() string + GetLastName() string + GetSuffix() *string +} + type FindAuthorByIDRow struct { AuthorID int32 `json:"author_id" db:"author_id"` FirstName string `json:"first_name" db:"first_name"` @@ -106,6 +113,14 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix" db:"suffix"` } +func (r FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } + +func (r FindAuthorByIDRow) GetFirstName() string { return r.FirstName } + +func (r FindAuthorByIDRow) GetLastName() string { return r.LastName } + +func (r FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } + // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") diff --git a/example/inline_param_count/inline3/query.sql.go b/example/inline_param_count/inline3/query.sql.go index 8ef8b669..3f1da2d1 100644 --- a/example/inline_param_count/inline3/query.sql.go +++ b/example/inline_param_count/inline3/query.sql.go @@ -99,6 +99,13 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` +type FindAuthorByIDProjection interface { + GetAuthorID() int32 + GetFirstName() string + GetLastName() string + GetSuffix() *string +} + type FindAuthorByIDRow struct { AuthorID int32 `json:"author_id" db:"author_id"` FirstName string `json:"first_name" db:"first_name"` @@ -106,6 +113,14 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix" db:"suffix"` } +func (r FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } + +func (r FindAuthorByIDRow) GetFirstName() string { return r.FirstName } + +func (r FindAuthorByIDRow) GetLastName() string { return r.LastName } + +func (r FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } + // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") diff --git a/example/ltree/query.sql.go b/example/ltree/query.sql.go index 323cab16..03bbd8ef 100644 --- a/example/ltree/query.sql.go +++ b/example/ltree/query.sql.go @@ -153,11 +153,20 @@ const findLtreeInputSQL = `SELECT -- that we need a text array that Postgres then converts to ltree[]. ($2::text[])::ltree[] AS text_arr;` +type FindLtreeInputProjection interface { + GetLtree() pgtype.Text + GetTextArr() pgtype.Array[pgtype.Text] +} + type FindLtreeInputRow struct { Ltree pgtype.Text `json:"ltree" db:"ltree"` TextArr pgtype.Array[pgtype.Text] `json:"text_arr" db:"text_arr"` } +func (r FindLtreeInputRow) GetLtree() pgtype.Text { return r.Ltree } + +func (r FindLtreeInputRow) GetTextArr() pgtype.Array[pgtype.Text] { return r.TextArr } + // FindLtreeInput implements Querier.FindLtreeInput. func (q *DBQuerier) FindLtreeInput(ctx context.Context, inLtree pgtype.Text, inLtreeArray []string) (FindLtreeInputRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindLtreeInput") diff --git a/example/pgcrypto/query.sql.go b/example/pgcrypto/query.sql.go index 8432a28f..ceb4c1db 100644 --- a/example/pgcrypto/query.sql.go +++ b/example/pgcrypto/query.sql.go @@ -86,11 +86,20 @@ func (q *DBQuerier) CreateUser(ctx context.Context, email string, password strin const findUserSQL = `SELECT email, pass from "user" where email = $1;` +type FindUserProjection interface { + GetEmail() string + GetPass() string +} + type FindUserRow struct { Email string `json:"email" db:"email"` Pass string `json:"pass" db:"pass"` } +func (r FindUserRow) GetEmail() string { return r.Email } + +func (r FindUserRow) GetPass() string { return r.Pass } + // FindUser implements Querier.FindUser. func (q *DBQuerier) FindUser(ctx context.Context, email string) (FindUserRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindUser") diff --git a/example/syntax/query.sql.go b/example/syntax/query.sql.go index 18702757..953eece5 100644 --- a/example/syntax/query.sql.go +++ b/example/syntax/query.sql.go @@ -203,11 +203,20 @@ func (q *DBQuerier) BacktickBackslashN(ctx context.Context) (string, error) { const illegalNameSymbolsSQL = "SELECT '`\\n' as \"$\", $1 as \"foo.bar!@#$%&*()\"\"--+\";" +type IllegalNameSymbolsProjection interface { + GetUnnamedColumn0() string + GetFooBar() string +} + type IllegalNameSymbolsRow struct { UnnamedColumn0 string `json:"$" db:"$"` FooBar string `json:"foo.bar!@#$%&*()\"--+" db:"foo.bar!@#$%&*()\"--+"` } +func (r IllegalNameSymbolsRow) GetUnnamedColumn0() string { return r.UnnamedColumn0 } + +func (r IllegalNameSymbolsRow) GetFooBar() string { return r.FooBar } + // IllegalNameSymbols implements Querier.IllegalNameSymbols. func (q *DBQuerier) IllegalNameSymbols(ctx context.Context, helloWorld string) (IllegalNameSymbolsRow, error) { ctx = context.WithValue(ctx, QueryName{}, "IllegalNameSymbols") diff --git a/example/void/query.sql.go b/example/void/query.sql.go index 05939016..dd0b9aa9 100644 --- a/example/void/query.sql.go +++ b/example/void/query.sql.go @@ -127,11 +127,20 @@ func (q *DBQuerier) VoidTwo(ctx context.Context) (string, error) { const voidThreeSQL = `SELECT void_fn(), 'foo' as foo, 'bar' as bar;` +type VoidThreeProjection interface { + GetFoo() string + GetBar() string +} + type VoidThreeRow struct { Foo string `json:"foo" db:"foo"` Bar string `json:"bar" db:"bar"` } +func (r VoidThreeRow) GetFoo() string { return r.Foo } + +func (r VoidThreeRow) GetBar() string { return r.Bar } + // VoidThree implements Querier.VoidThree. func (q *DBQuerier) VoidThree(ctx context.Context) (VoidThreeRow, error) { ctx = context.WithValue(ctx, QueryName{}, "VoidThree") diff --git a/go.mod b/go.mod index 39b886cc..3cfc37a3 100644 --- a/go.mod +++ b/go.mod @@ -14,37 +14,215 @@ require ( ) require ( + 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect + 4d63.com/gochecknoglobals v0.2.2 // indirect + github.com/4meepo/tagalign v1.4.2 // indirect + github.com/Abirdcfly/dupword v0.1.3 // indirect + github.com/Antonboom/errname v1.0.0 // indirect + github.com/Antonboom/nilnil v1.0.1 // indirect + github.com/Antonboom/testifylint v1.5.2 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect + github.com/Crocmagnon/fatcontext v0.7.1 // indirect + github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect + github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect + github.com/alecthomas/go-check-sumtype v0.3.1 // indirect + github.com/alexkohler/nakedret/v2 v2.0.5 // indirect + github.com/alexkohler/prealloc v1.0.0 // indirect + github.com/alingse/asasalint v0.0.11 // indirect + github.com/alingse/nilnesserr v0.1.2 // indirect + github.com/ashanbrown/forbidigo v1.6.0 // indirect + github.com/ashanbrown/makezero v1.2.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bkielbasa/cyclop v1.2.3 // indirect + github.com/blizzy78/varnamelen v0.8.0 // indirect + github.com/bombsimon/wsl/v4 v4.5.0 // indirect + github.com/breml/bidichk v0.3.2 // indirect + github.com/breml/errchkjson v0.4.0 // indirect + github.com/butuzov/ireturn v0.3.1 // indirect + github.com/butuzov/mirror v1.3.0 // indirect + github.com/catenacyber/perfsprint v0.8.2 // indirect + github.com/ccojocar/zxcvbn-go v1.0.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charithe/durationcheck v0.0.10 // indirect + github.com/chavacava/garif v0.1.0 // indirect + github.com/ckaznocha/intrange v0.3.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect + github.com/curioswitch/go-reassign v0.3.0 // indirect + github.com/daixiang0/gci v0.13.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/ettle/strcase v0.2.0 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/fatih/structtag v1.2.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/firefart/nonamedreturns v1.0.5 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/fzipp/gocyclo v0.6.0 // indirect + github.com/ghostiam/protogetter v0.3.9 // indirect + github.com/go-critic/go-critic v0.12.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-toolsmith/astcast v1.1.0 // indirect + github.com/go-toolsmith/astcopy v1.1.0 // indirect + github.com/go-toolsmith/astequal v1.2.0 // indirect + github.com/go-toolsmith/astfmt v1.1.0 // indirect + github.com/go-toolsmith/astp v1.1.0 // indirect + github.com/go-toolsmith/strparse v1.1.0 // indirect + github.com/go-toolsmith/typep v1.1.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/gofrs/flock v0.12.1 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect + github.com/golangci/go-printf-func-name v0.1.0 // indirect + github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect + github.com/golangci/golangci-lint v1.64.8 // indirect + github.com/golangci/misspell v0.6.0 // indirect + github.com/golangci/plugin-module-register v0.1.1 // indirect + github.com/golangci/revgrep v0.8.0 // indirect + github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect + github.com/gordonklaus/ineffassign v0.1.0 // indirect + github.com/gostaticanalysis/analysisutil v0.7.1 // indirect + github.com/gostaticanalysis/comment v1.5.0 // indirect + github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect + github.com/gostaticanalysis/nilerr v0.1.1 // indirect + github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hexops/gotextdiff v1.0.3 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jgautheron/goconst v1.7.1 // indirect + github.com/jingyugao/rowserrcheck v1.1.1 // indirect + github.com/jjti/go-spancheck v0.6.4 // indirect + github.com/julz/importas v0.2.0 // indirect + github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect + github.com/kisielk/errcheck v1.9.0 // indirect + github.com/kkHAIKE/contextcheck v1.1.6 // indirect + github.com/kulti/thelper v0.6.3 // indirect + github.com/kunwardeep/paralleltest v1.0.10 // indirect + github.com/lasiar/canonicalheader v1.1.2 // indirect + github.com/ldez/exptostd v0.4.2 // indirect + github.com/ldez/gomoddirectives v0.6.1 // indirect + github.com/ldez/grignotin v0.9.0 // indirect + github.com/ldez/tagliatelle v0.7.1 // indirect + github.com/ldez/usetesting v0.4.2 // indirect + github.com/leonklingele/grouper v1.1.2 // indirect + github.com/macabu/inamedparam v0.1.3 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/maratori/testableexamples v1.0.0 // indirect + github.com/maratori/testpackage v1.1.1 // indirect + github.com/matoous/godox v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mgechev/revive v1.7.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect + github.com/moricho/tparallel v0.3.2 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/nakabonne/nestif v0.3.1 // indirect + github.com/nishanths/exhaustive v0.12.0 // indirect + github.com/nishanths/predeclared v0.2.2 // indirect + github.com/nunnatsa/ginkgolinter v0.19.1 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/polyfloyd/go-errorlint v1.7.1 // indirect + github.com/prometheus/client_golang v1.12.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect + github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect + github.com/quasilyte/gogrep v0.5.0 // indirect + github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect + github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect + github.com/raeperd/recvcheck v0.2.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/ryancurrah/gomodguard v1.3.5 // indirect + github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect + github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect + github.com/sashamelentyev/interfacebloat v1.1.0 // indirect + github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect + github.com/securego/gosec/v2 v2.22.2 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sivchari/containedctx v1.0.3 // indirect + github.com/sivchari/tenv v1.12.1 // indirect + github.com/sonatard/noctx v0.1.0 // indirect + github.com/sourcegraph/go-diff v0.7.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/spf13/viper v1.12.0 // indirect + github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect + github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/subosito/gotenv v1.4.1 // indirect + github.com/tdakkota/asciicheck v0.4.1 // indirect + github.com/tetafro/godot v1.5.0 // indirect + github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect + github.com/timonwong/loggercheck v0.10.1 // indirect + github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect + github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect + github.com/ultraware/funlen v0.2.0 // indirect + github.com/ultraware/whitespace v0.2.0 // indirect + github.com/uudashr/gocognit v1.2.0 // indirect + github.com/uudashr/iface v1.3.1 // indirect + github.com/xen0n/gosmopolitan v1.2.2 // indirect + github.com/yagipy/maintidx v1.0.0 // indirect + github.com/yeya24/promlinter v0.3.0 // indirect + github.com/ykadowak/zerologlint v0.1.5 // indirect + gitlab.com/bosi/decorder v0.4.2 // indirect + go-simpler.org/musttag v0.13.0 // indirect + go-simpler.org/sloglint v0.9.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.24.0 // indirect golang.org/x/crypto v0.36.0 // indirect - golang.org/x/net v0.37.0 // indirect + golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect + golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.31.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.2 // indirect + honnef.co/go/tools v0.6.1 // indirect + mvdan.cc/gofumpt v0.7.0 // indirect + mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect ) + +tool github.com/golangci/golangci-lint/cmd/golangci-lint diff --git a/go.sum b/go.sum index e81ac77d..fea64d8c 100644 --- a/go.sum +++ b/go.sum @@ -1,41 +1,345 @@ +4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A= +4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= +4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= +4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E= +github.com/4meepo/tagalign v1.4.2/go.mod h1:+p4aMyFM+ra7nb41CnFG6aSDXqRxU/w1VQqScKqDARI= +github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= +github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= +github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoTBA= +github.com/Antonboom/errname v1.0.0/go.mod h1:gMOBFzK/vrTiXN9Oh+HFs+e6Ndl0eTFbtsRTSRdXyGI= +github.com/Antonboom/nilnil v1.0.1 h1:C3Tkm0KUxgfO4Duk3PM+ztPncTFlOf0b2qadmS0s4xs= +github.com/Antonboom/nilnil v1.0.1/go.mod h1:CH7pW2JsRNFgEh8B2UaPZTEPhCMuFowP/e8Udp9Nnb0= +github.com/Antonboom/testifylint v1.5.2 h1:4s3Xhuv5AvdIgbd8wOOEeo0uZG7PbDKQyKY5lGoQazk= +github.com/Antonboom/testifylint v1.5.2/go.mod h1:vxy8VJ0bc6NavlYqjZfmp6EfqXMtBgQ4+mhCojwC1P8= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Crocmagnon/fatcontext v0.7.1 h1:SC/VIbRRZQeQWj/TcQBS6JmrXcfA+BU4OGSVUt54PjM= +github.com/Crocmagnon/fatcontext v0.7.1/go.mod h1:1wMvv3NXEBJucFGfwOJBxSVWcoIO6emV215SMkW9MFU= +github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= +github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnhpIE0thVASYjvosApmHuD2k= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= +github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= +github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU= +github.com/alexkohler/nakedret/v2 v2.0.5/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= +github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= +github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= +github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= +github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= +github.com/alingse/nilnesserr v0.1.2 h1:Yf8Iwm3z2hUUrP4muWfW83DF4nE3r1xZ26fGWUKCZlo= +github.com/alingse/nilnesserr v0.1.2/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= +github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= +github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= +github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU= +github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= +github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= +github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= +github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= +github.com/bombsimon/wsl/v4 v4.5.0 h1:iZRsEvDdyhd2La0FVi5k6tYehpOR/R7qIUjmKk7N74A= +github.com/bombsimon/wsl/v4 v4.5.0/go.mod h1:NOQ3aLF4nD7N5YPXMruR6ZXDOAqLoM0GEpLwTdvmOSc= +github.com/breml/bidichk v0.3.2 h1:xV4flJ9V5xWTqxL+/PMFF6dtJPvZLPsyixAoPe8BGJs= +github.com/breml/bidichk v0.3.2/go.mod h1:VzFLBxuYtT23z5+iVkamXO386OB+/sVwZOpIj6zXGos= +github.com/breml/errchkjson v0.4.0 h1:gftf6uWZMtIa/Is3XJgibewBm2ksAQSY/kABDNFTAdk= +github.com/breml/errchkjson v0.4.0/go.mod h1:AuBOSTHyLSaaAFlWsRSuRBIroCh3eh7ZHh5YeelDIk8= +github.com/butuzov/ireturn v0.3.1 h1:mFgbEI6m+9W8oP/oDdfA34dLisRFCj2G6o/yiI1yZrY= +github.com/butuzov/ireturn v0.3.1/go.mod h1:ZfRp+E7eJLC0NQmk1Nrm1LOrn/gQlOykv+cVPdiXH5M= +github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= +github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= +github.com/catenacyber/perfsprint v0.8.2 h1:+o9zVmCSVa7M4MvabsWvESEhpsMkhfE7k0sHNGL95yw= +github.com/catenacyber/perfsprint v0.8.2/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM= +github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= +github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= +github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= +github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= +github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/ckaznocha/intrange v0.3.0 h1:VqnxtK32pxgkhJgYQEeOArVidIPg+ahLP7WBOXZd5ZY= +github.com/ckaznocha/intrange v0.3.0/go.mod h1:+I/o2d2A1FBHgGELbGxzIcyd3/9l9DuwjM8FsbSS3Lo= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= +github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= +github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= +github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= +github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= +github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= +github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= +github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= +github.com/ghostiam/protogetter v0.3.9 h1:j+zlLLWzqLay22Cz/aYwTHKQ88GE2DQ6GkWSYFOI4lQ= +github.com/ghostiam/protogetter v0.3.9/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= +github.com/go-critic/go-critic v0.12.0 h1:iLosHZuye812wnkEz1Xu3aBwn5ocCPfc9yqmFG9pa6w= +github.com/go-critic/go-critic v0.12.0/go.mod h1:DpE0P6OVc6JzVYzmM5gq5jMU31zLr4am5mB/VfFK64w= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= +github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= +github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= +github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= +github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= +github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= +github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= +github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= +github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= +github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= +github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= +github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= +github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= +github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= +github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= +github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= +github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= +github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= +github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= +github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= +github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= +github.com/golangci/golangci-lint v1.64.8 h1:y5TdeVidMtBGG32zgSC7ZXTFNHrsJkDnpO4ItB3Am+I= +github.com/golangci/golangci-lint v1.64.8/go.mod h1:5cEsUQBSr6zi8XI8OjmcY2Xmliqc4iYL7YoPrL+zLJ4= +github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= +github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= +github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= +github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= +github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= +github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= +github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= +github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= +github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= +github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= +github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= +github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= +github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= +github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= +github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= +github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= +github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= +github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8= +github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= +github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= +github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -44,10 +348,85 @@ github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI= github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= +github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= +github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= +github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc= +github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= +github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= +github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI= +github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM= +github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= +github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= +github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= +github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= +github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= +github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= +github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= +github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= +github.com/ldez/exptostd v0.4.2 h1:l5pOzHBz8mFOlbcifTxzfyYbgEmoUqjxLFHZkjlbHXs= +github.com/ldez/exptostd v0.4.2/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= +github.com/ldez/gomoddirectives v0.6.1 h1:Z+PxGAY+217f/bSGjNZr/b2KTXcyYLgiWI6geMBN2Qc= +github.com/ldez/gomoddirectives v0.6.1/go.mod h1:cVBiu3AHR9V31em9u2kwfMKD43ayN5/XDgr+cdaFaKs= +github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= +github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= +github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk= +github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I= +github.com/ldez/usetesting v0.4.2 h1:J2WwbrFGk3wx4cZwSMiCQQ00kjGR0+tuuyW0Lqm4lwA= +github.com/ldez/usetesting v0.4.2/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ= +github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= +github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= +github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= +github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= +github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= +github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= +github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= +github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= +github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgechev/revive v1.7.0 h1:JyeQ4yO5K8aZhIKf5rec56u0376h8AlKNQEmjfkjKlY= +github.com/mgechev/revive v1.7.0/go.mod h1:qZnwcNhoguE58dfi96IJeSTPeZQejNeoMQLUZGi4SW4= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= @@ -56,27 +435,215 @@ github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7z github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= +github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= +github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= +github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= +github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= +github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= +github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= +github.com/nunnatsa/ginkgolinter v0.19.1 h1:mjwbOlDQxZi9Cal+KfbEJTCz327OLNfwNvoZ70NJ+c4= +github.com/nunnatsa/ginkgolinter v0.19.1/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/polyfloyd/go-errorlint v1.7.1 h1:RyLVXIbosq1gBdk/pChWA8zWYLsq9UEw7a1L5TVMCnA= +github.com/polyfloyd/go-errorlint v1.7.1/go.mod h1:aXjNb1x2TNhoLsk26iv1yl7a+zTnXPhwEMtEXukiLR8= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= +github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= +github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= +github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= +github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= +github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= +github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= +github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= +github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= +github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= +github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= +github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= +github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= +github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= +github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= +github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= +github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= +github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ= +github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/securego/gosec/v2 v2.22.2 h1:IXbuI7cJninj0nRpZSLCUlotsj8jGusohfONMrHoF6g= +github.com/securego/gosec/v2 v2.22.2/go.mod h1:UEBGA+dSKb+VqM6TdehR7lnQtIIMorYJ4/9CW1KVQBE= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= +github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= +github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= +github.com/sivchari/tenv v1.12.1/go.mod h1:1LjSOUCc25snIr5n3DtGGrENhX3LuWefcplwVGC24mw= +github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= +github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= +github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= +github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= +github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= +github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= +github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= +github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8= +github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8= +github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= +github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= +github.com/tetafro/godot v1.5.0 h1:aNwfVI4I3+gdxjMgYPus9eHmoBeJIbnajOyqZYStzuw= +github.com/tetafro/godot v1.5.0/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 h1:y4mJRFlM6fUyPhoXuFg/Yu02fg/nIPFMOY8tOqppoFg= +github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= +github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= +github.com/timonwong/loggercheck v0.10.1/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= +github.com/tomarrell/wrapcheck/v2 v2.10.0 h1:SzRCryzy4IrAH7bVGG4cK40tNUhmVmMDuJujy4XwYDg= +github.com/tomarrell/wrapcheck/v2 v2.10.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= +github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= +github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= +github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI= +github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= +github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= +github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= +github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= +github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= +github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U= +github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= +github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= +github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= +github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= +github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= +github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= +github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= +github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= +github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= +gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= +go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= +go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= +go-simpler.org/musttag v0.13.0 h1:Q/YAW0AHvaoaIbsPj3bvEI5/QFP7w696IMUpnKXQfCE= +go-simpler.org/musttag v0.13.0/go.mod h1:FTzIGeK6OkKlUDVpj0iQUXZLUO1Js9+mvykDQy9C5yM= +go-simpler.org/sloglint v0.9.0 h1:/40NQtjRx9txvsB/RN022KsUJU+zaaSb/9q9BSefSrE= +go-simpler.org/sloglint v0.9.0/go.mod h1:G/OrAF6uxj48sHahCzrbarVMptL2kjWTaUeC8+fOGww= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= @@ -97,33 +664,400 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 h1:DMTIbak9GhdaSxEjvVzAeNZvyc03I61duqNbnm3SU0M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= +honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= +mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= +mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= +mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= +mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/codegen/golang/query.gotemplate b/internal/codegen/golang/query.gotemplate index c8112f13..7ff478c2 100644 --- a/internal/codegen/golang/query.gotemplate +++ b/internal/codegen/golang/query.gotemplate @@ -51,7 +51,9 @@ func NewQuerier(conn genericConn) *DBQuerier { {{- "\n\n" -}} const {{ $q.SQLVarName }} = {{ $q.EmitPreparedSQL }} {{- $q.EmitParamStruct -}} +{{- $q.EmitProjectionInterface -}} {{- $q.EmitRowStruct -}} +{{- $q.EmitRowGetterMethods -}} {{- "\n\n" -}} // {{ $q.Name }} implements Querier.{{ $q.Name }}. func (q *DBQuerier) {{ $q.Name }}(ctx context.Context {{- $q.EmitParams }}) ({{ $q.EmitResultType }}, error) { diff --git a/internal/codegen/golang/templated_file.go b/internal/codegen/golang/templated_file.go index fbe248d3..33a361d7 100644 --- a/internal/codegen/golang/templated_file.go +++ b/internal/codegen/golang/templated_file.go @@ -55,11 +55,12 @@ type TemplatedParam struct { } type TemplatedColumn struct { - PgName string // original name of the Postgres column - UpperName string // name in Go-style (UpperCamelCase) to use for the column - LowerName string // name in Go-style (lowerCamelCase) - Type gotype.Type - QualType string // package qualified Go type to use for the column, like "pgtype.Text" + PgName string // original name of the Postgres column + UpperName string // name in Go-style (UpperCamelCase) to use for the column + LowerName string // name in Go-style (lowerCamelCase) + GetterName string // name of the projection getter method + Type gotype.Type + QualType string // package qualified Go type to use for the column, like "pgtype.Text" } func (tf TemplatedFile) needsPgconnImport() bool { @@ -502,6 +503,31 @@ func getLongestOutput(outs []TemplatedColumn) (int, int) { return nameLen, typeLen } +func (tq TemplatedQuery) hasProjection() bool { + return tq.ResultKind != ast.ResultKindExec && len(tq.Outputs) > 1 +} + +// EmitProjectionInterface writes the interface implemented by a multi-column +// output row. +func (tq TemplatedQuery) EmitProjectionInterface() string { + if !tq.hasProjection() { + return "" + } + sb := &strings.Builder{} + sb.WriteString("\n\ntype ") + sb.WriteString(tq.Name) + sb.WriteString("Projection interface {\n") + for _, out := range tq.Outputs { + sb.WriteString("\t") + sb.WriteString(out.GetterName) + sb.WriteString("() ") + sb.WriteString(out.QualType) + sb.WriteRune('\n') + } + sb.WriteString("}") + return sb.String() +} + // EmitRowStruct writes the struct definition for query output row if one is // needed. func (tq TemplatedQuery) EmitRowStruct() string { @@ -539,3 +565,24 @@ func (tq TemplatedQuery) EmitRowStruct() string { panic("unhandled result type: " + tq.ResultKind) } } + +// EmitRowGetterMethods writes projection getter methods for a multi-column +// output row. +func (tq TemplatedQuery) EmitRowGetterMethods() string { + if !tq.hasProjection() { + return "" + } + sb := &strings.Builder{} + for _, out := range tq.Outputs { + sb.WriteString("\n\nfunc (r ") + sb.WriteString(tq.Name) + sb.WriteString("Row) ") + sb.WriteString(out.GetterName) + sb.WriteString("() ") + sb.WriteString(out.QualType) + sb.WriteString(" { return r.") + sb.WriteString(out.UpperName) + sb.WriteString(" }") + } + return sb.String() +} diff --git a/internal/codegen/golang/templated_file_test.go b/internal/codegen/golang/templated_file_test.go new file mode 100644 index 00000000..2b076165 --- /dev/null +++ b/internal/codegen/golang/templated_file_test.go @@ -0,0 +1,74 @@ +package golang + +import ( + "testing" + + "github.com/meoyawn/pggen/internal/ast" + "github.com/stretchr/testify/assert" +) + +func TestResolveProjectionGetterNames(t *testing.T) { + cols := resolveProjectionGetterNames([]TemplatedColumn{ + {UpperName: "ID"}, + {UpperName: "GetID"}, + {UpperName: "Foo"}, + {UpperName: "FooValue"}, + {UpperName: "GetFoo"}, + {UpperName: "GetFooValue"}, + {UpperName: "Bar"}, + {UpperName: "Bar"}, + }) + + got := make([]string, 0, len(cols)) + for _, col := range cols { + got = append(got, col.GetterName) + } + + assert.Equal(t, []string{ + "GetIDValue", + "GetGetID", + "GetFooValue2", + "GetFooValueValue", + "GetGetFoo", + "GetGetFooValue", + "GetBar", + "GetBarValue", + }, got) +} + +func TestTemplatedQuery_EmitProjection(t *testing.T) { + query := TemplatedQuery{ + Name: "FindAuthors", + ResultKind: ast.ResultKindMany, + Outputs: []TemplatedColumn{ + {UpperName: "AuthorID", GetterName: "GetAuthorID", QualType: "int"}, + {UpperName: "Name", GetterName: "GetName", QualType: "string"}, + }, + } + + assert.Equal(t, ` + +type FindAuthorsProjection interface { + GetAuthorID() int + GetName() string +}`, query.EmitProjectionInterface()) + + assert.Equal(t, ` + +func (r FindAuthorsRow) GetAuthorID() int { return r.AuthorID } + +func (r FindAuthorsRow) GetName() string { return r.Name }`, query.EmitRowGetterMethods()) +} + +func TestTemplatedQuery_EmitProjectionSkipsSingleColumn(t *testing.T) { + query := TemplatedQuery{ + Name: "FindAuthorID", + ResultKind: ast.ResultKindMany, + Outputs: []TemplatedColumn{ + {UpperName: "AuthorID", GetterName: "GetAuthorID", QualType: "int"}, + }, + } + + assert.Empty(t, query.EmitProjectionInterface()) + assert.Empty(t, query.EmitRowGetterMethods()) +} diff --git a/internal/codegen/golang/templater.go b/internal/codegen/golang/templater.go index b16570aa..674b9587 100644 --- a/internal/codegen/golang/templater.go +++ b/internal/codegen/golang/templater.go @@ -185,6 +185,7 @@ func (tm Templater) templateFile(file codegen.QueryFile, isLeader bool) (Templat } nonVoidCols := removeVoidColumns(outputs) + nonVoidCols = resolveProjectionGetterNames(nonVoidCols) resultKind := query.ResultKind if len(nonVoidCols) == 0 { resultKind = ast.ResultKindExec @@ -256,3 +257,28 @@ func removeVoidColumns(cols []TemplatedColumn) []TemplatedColumn { } return outs } + +func resolveProjectionGetterNames(cols []TemplatedColumn) []TemplatedColumn { + used := make(map[string]struct{}, len(cols)*2) + for _, col := range cols { + used[col.UpperName] = struct{}{} + } + for i, col := range cols { + name := "Get" + col.UpperName + if _, ok := used[name]; ok { + base := name + "Value" + name = base + for suffix := 2; hasIdentifier(used, name); suffix++ { + name = base + strconv.Itoa(suffix) + } + } + cols[i].GetterName = name + used[name] = struct{}{} + } + return cols +} + +func hasIdentifier(used map[string]struct{}, name string) bool { + _, ok := used[name] + return ok +} diff --git a/internal/pg/query.sql.go b/internal/pg/query.sql.go index 8eaa9f19..e634ffda 100644 --- a/internal/pg/query.sql.go +++ b/internal/pg/query.sql.go @@ -129,6 +129,16 @@ WHERE typ.typisdefined AND typ.typtype = 'e' AND typ.oid = ANY ($1::oid[]);` +type FindEnumTypesProjection interface { + GetOID() uint32 + GetTypeName() string + GetChildOIDs() []int + GetOrders() []float32 + GetLabels() []string + GetTypeKind() byte + GetDefaultExpr() string +} + type FindEnumTypesRow struct { OID uint32 `json:"oid" db:"oid"` TypeName string `json:"type_name" db:"type_name"` @@ -139,6 +149,20 @@ type FindEnumTypesRow struct { DefaultExpr string `json:"default_expr" db:"default_expr"` } +func (r FindEnumTypesRow) GetOID() uint32 { return r.OID } + +func (r FindEnumTypesRow) GetTypeName() string { return r.TypeName } + +func (r FindEnumTypesRow) GetChildOIDs() []int { return r.ChildOIDs } + +func (r FindEnumTypesRow) GetOrders() []float32 { return r.Orders } + +func (r FindEnumTypesRow) GetLabels() []string { return r.Labels } + +func (r FindEnumTypesRow) GetTypeKind() byte { return r.TypeKind } + +func (r FindEnumTypesRow) GetDefaultExpr() string { return r.DefaultExpr } + // FindEnumTypes implements Querier.FindEnumTypes. func (q *DBQuerier) FindEnumTypes(ctx context.Context, oids []uint32) ([]FindEnumTypesRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindEnumTypes") @@ -185,6 +209,13 @@ WHERE arr_typ.typisdefined AND arr_typ.typlen = -1 AND arr_typ.oid = ANY ($1::oid[]);` +type FindArrayTypesProjection interface { + GetOID() uint32 + GetTypeName() string + GetElemOID() uint32 + GetTypeKind() byte +} + type FindArrayTypesRow struct { OID uint32 `json:"oid" db:"oid"` TypeName string `json:"type_name" db:"type_name"` @@ -192,6 +223,14 @@ type FindArrayTypesRow struct { TypeKind byte `json:"type_kind" db:"type_kind"` } +func (r FindArrayTypesRow) GetOID() uint32 { return r.OID } + +func (r FindArrayTypesRow) GetTypeName() string { return r.TypeName } + +func (r FindArrayTypesRow) GetElemOID() uint32 { return r.ElemOID } + +func (r FindArrayTypesRow) GetTypeKind() byte { return r.TypeKind } + // FindArrayTypes implements Querier.FindArrayTypes. func (q *DBQuerier) FindArrayTypes(ctx context.Context, oids []uint32) ([]FindArrayTypesRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindArrayTypes") @@ -239,6 +278,17 @@ FROM pg_type typ WHERE typ.oid = ANY ($1::oid[]) AND typ.typtype = 'c';` +type FindCompositeTypesProjection interface { + GetTableTypeName() string + GetTableTypeOID() uint32 + GetTableName() string + GetColNames() []string + GetColOIDs() []int + GetColOrders() []int + GetColNotNulls() []bool + GetColTypeNames() []string +} + type FindCompositeTypesRow struct { TableTypeName string `json:"table_type_name" db:"table_type_name"` TableTypeOID uint32 `json:"table_type_oid" db:"table_type_oid"` @@ -250,6 +300,22 @@ type FindCompositeTypesRow struct { ColTypeNames []string `json:"col_type_names" db:"col_type_names"` } +func (r FindCompositeTypesRow) GetTableTypeName() string { return r.TableTypeName } + +func (r FindCompositeTypesRow) GetTableTypeOID() uint32 { return r.TableTypeOID } + +func (r FindCompositeTypesRow) GetTableName() string { return r.TableName } + +func (r FindCompositeTypesRow) GetColNames() []string { return r.ColNames } + +func (r FindCompositeTypesRow) GetColOIDs() []int { return r.ColOIDs } + +func (r FindCompositeTypesRow) GetColOrders() []int { return r.ColOrders } + +func (r FindCompositeTypesRow) GetColNotNulls() []bool { return r.ColNotNulls } + +func (r FindCompositeTypesRow) GetColTypeNames() []string { return r.ColTypeNames } + // FindCompositeTypes implements Querier.FindCompositeTypes. func (q *DBQuerier) FindCompositeTypes(ctx context.Context, oids []uint32) ([]FindCompositeTypesRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindCompositeTypes") @@ -360,12 +426,24 @@ const findOIDNamesSQL = `SELECT oid, typname AS name, typtype AS kind FROM pg_type WHERE oid = ANY ($1::oid[]);` +type FindOIDNamesProjection interface { + GetOID() uint32 + GetName() string + GetKind() byte +} + type FindOIDNamesRow struct { OID uint32 `json:"oid" db:"oid"` Name string `json:"name" db:"name"` Kind byte `json:"kind" db:"kind"` } +func (r FindOIDNamesRow) GetOID() uint32 { return r.OID } + +func (r FindOIDNamesRow) GetName() string { return r.Name } + +func (r FindOIDNamesRow) GetKind() byte { return r.Kind } + // FindOIDNames implements Querier.FindOIDNames. func (q *DBQuerier) FindOIDNames(ctx context.Context, oid []uint32) ([]FindOIDNamesRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindOIDNames") From 29ec1980c735b718353a3a0641f0ed51bb33b510 Mon Sep 17 00:00:00 2001 From: adelnizamutdinov Date: Tue, 2 Jun 2026 23:17:21 +0300 Subject: [PATCH 20/27] Use pointer receivers for projection getters --- example/author/query.sql.go | 36 +++++++-------- example/composite/query.sql.go | 10 ++--- example/custom_types/query.sql.go | 4 +- example/device/query.sql.go | 16 +++---- example/enums/query.sql.go | 8 ++-- example/erp/order/customer.sql.go | 36 +++++++-------- example/erp/order/price.sql.go | 12 ++--- example/function/query.sql.go | 4 +- .../inline_param_count/inline0/query.sql.go | 8 ++-- .../inline_param_count/inline1/query.sql.go | 8 ++-- .../inline_param_count/inline2/query.sql.go | 8 ++-- .../inline_param_count/inline3/query.sql.go | 8 ++-- example/ltree/query.sql.go | 4 +- example/pgcrypto/query.sql.go | 4 +- example/syntax/query.sql.go | 4 +- example/void/query.sql.go | 4 +- internal/codegen/golang/templated_file.go | 2 +- .../codegen/golang/templated_file_test.go | 4 +- internal/pg/query.sql.go | 44 +++++++++---------- 19 files changed, 112 insertions(+), 112 deletions(-) diff --git a/example/author/query.sql.go b/example/author/query.sql.go index 7a17cff2..898cb19f 100644 --- a/example/author/query.sql.go +++ b/example/author/query.sql.go @@ -117,13 +117,13 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix" db:"suffix"` } -func (r FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } +func (r *FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } -func (r FindAuthorByIDRow) GetFirstName() string { return r.FirstName } +func (r *FindAuthorByIDRow) GetFirstName() string { return r.FirstName } -func (r FindAuthorByIDRow) GetLastName() string { return r.LastName } +func (r *FindAuthorByIDRow) GetLastName() string { return r.LastName } -func (r FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } +func (r *FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { @@ -158,13 +158,13 @@ type FindAuthorsRow struct { Suffix *string `json:"suffix" db:"suffix"` } -func (r FindAuthorsRow) GetAuthorID() int32 { return r.AuthorID } +func (r *FindAuthorsRow) GetAuthorID() int32 { return r.AuthorID } -func (r FindAuthorsRow) GetFirstName() string { return r.FirstName } +func (r *FindAuthorsRow) GetFirstName() string { return r.FirstName } -func (r FindAuthorsRow) GetLastName() string { return r.LastName } +func (r *FindAuthorsRow) GetLastName() string { return r.LastName } -func (r FindAuthorsRow) GetSuffix() *string { return r.Suffix } +func (r *FindAuthorsRow) GetSuffix() *string { return r.Suffix } // FindAuthors implements Querier.FindAuthors. func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAuthorsRow, error) { @@ -195,9 +195,9 @@ type FindAuthorNamesRow struct { LastName *string `json:"last_name" db:"last_name"` } -func (r FindAuthorNamesRow) GetFirstName() *string { return r.FirstName } +func (r *FindAuthorNamesRow) GetFirstName() *string { return r.FirstName } -func (r FindAuthorNamesRow) GetLastName() *string { return r.LastName } +func (r *FindAuthorNamesRow) GetLastName() *string { return r.LastName } // FindAuthorNames implements Querier.FindAuthorNames. func (q *DBQuerier) FindAuthorNames(ctx context.Context, authorID int32) ([]FindAuthorNamesRow, error) { @@ -251,13 +251,13 @@ type FindFirstAuthorRow struct { Suffix *string `json:"suffix" db:"suffix"` } -func (r FindFirstAuthorRow) GetAuthorID() *int32 { return r.AuthorID } +func (r *FindFirstAuthorRow) GetAuthorID() *int32 { return r.AuthorID } -func (r FindFirstAuthorRow) GetFirstName() *string { return r.FirstName } +func (r *FindFirstAuthorRow) GetFirstName() *string { return r.FirstName } -func (r FindFirstAuthorRow) GetLastName() *string { return r.LastName } +func (r *FindFirstAuthorRow) GetLastName() *string { return r.LastName } -func (r FindFirstAuthorRow) GetSuffix() *string { return r.Suffix } +func (r *FindFirstAuthorRow) GetSuffix() *string { return r.Suffix } // FindFirstAuthor implements Querier.FindFirstAuthor. func (q *DBQuerier) FindFirstAuthor(ctx context.Context) (FindFirstAuthorRow, error) { @@ -367,13 +367,13 @@ type InsertAuthorSuffixRow struct { Suffix *string `json:"suffix" db:"suffix"` } -func (r InsertAuthorSuffixRow) GetAuthorID() int32 { return r.AuthorID } +func (r *InsertAuthorSuffixRow) GetAuthorID() int32 { return r.AuthorID } -func (r InsertAuthorSuffixRow) GetFirstName() string { return r.FirstName } +func (r *InsertAuthorSuffixRow) GetFirstName() string { return r.FirstName } -func (r InsertAuthorSuffixRow) GetLastName() string { return r.LastName } +func (r *InsertAuthorSuffixRow) GetLastName() string { return r.LastName } -func (r InsertAuthorSuffixRow) GetSuffix() *string { return r.Suffix } +func (r *InsertAuthorSuffixRow) GetSuffix() *string { return r.Suffix } // InsertAuthorSuffix implements Querier.InsertAuthorSuffix. func (q *DBQuerier) InsertAuthorSuffix(ctx context.Context, params InsertAuthorSuffixParams) (InsertAuthorSuffixRow, error) { diff --git a/example/composite/query.sql.go b/example/composite/query.sql.go index 90a78b64..a51ccb35 100644 --- a/example/composite/query.sql.go +++ b/example/composite/query.sql.go @@ -132,9 +132,9 @@ type SearchScreenshotsRow struct { Blocks []Blocks `json:"blocks" db:"blocks"` } -func (r SearchScreenshotsRow) GetID() int { return r.ID } +func (r *SearchScreenshotsRow) GetID() int { return r.ID } -func (r SearchScreenshotsRow) GetBlocks() []Blocks { return r.Blocks } +func (r *SearchScreenshotsRow) GetBlocks() []Blocks { return r.Blocks } // SearchScreenshots implements Querier.SearchScreenshots. func (q *DBQuerier) SearchScreenshots(ctx context.Context, params SearchScreenshotsParams) ([]SearchScreenshotsRow, error) { @@ -206,11 +206,11 @@ type InsertScreenshotBlocksRow struct { Body string `json:"body" db:"body"` } -func (r InsertScreenshotBlocksRow) GetID() int { return r.ID } +func (r *InsertScreenshotBlocksRow) GetID() int { return r.ID } -func (r InsertScreenshotBlocksRow) GetScreenshotID() int { return r.ScreenshotID } +func (r *InsertScreenshotBlocksRow) GetScreenshotID() int { return r.ScreenshotID } -func (r InsertScreenshotBlocksRow) GetBody() string { return r.Body } +func (r *InsertScreenshotBlocksRow) GetBody() string { return r.Body } // InsertScreenshotBlocks implements Querier.InsertScreenshotBlocks. func (q *DBQuerier) InsertScreenshotBlocks(ctx context.Context, screenshotID int, body string) (InsertScreenshotBlocksRow, error) { diff --git a/example/custom_types/query.sql.go b/example/custom_types/query.sql.go index f5e665d8..bf9c9d4f 100644 --- a/example/custom_types/query.sql.go +++ b/example/custom_types/query.sql.go @@ -87,9 +87,9 @@ type CustomTypesRow struct { Int8 CustomInt `json:"int8" db:"int8"` } -func (r CustomTypesRow) GetColumn() mytype.String { return r.Column } +func (r *CustomTypesRow) GetColumn() mytype.String { return r.Column } -func (r CustomTypesRow) GetInt8() CustomInt { return r.Int8 } +func (r *CustomTypesRow) GetInt8() CustomInt { return r.Int8 } // CustomTypes implements Querier.CustomTypes. func (q *DBQuerier) CustomTypes(ctx context.Context) (CustomTypesRow, error) { diff --git a/example/device/query.sql.go b/example/device/query.sql.go index 8590e7f4..ef0a4a6b 100644 --- a/example/device/query.sql.go +++ b/example/device/query.sql.go @@ -124,11 +124,11 @@ type FindDevicesByUserRow struct { MacAddrs []net.HardwareAddr `json:"mac_addrs" db:"mac_addrs"` } -func (r FindDevicesByUserRow) GetID() int { return r.ID } +func (r *FindDevicesByUserRow) GetID() int { return r.ID } -func (r FindDevicesByUserRow) GetName() string { return r.Name } +func (r *FindDevicesByUserRow) GetName() string { return r.Name } -func (r FindDevicesByUserRow) GetMacAddrs() []net.HardwareAddr { return r.MacAddrs } +func (r *FindDevicesByUserRow) GetMacAddrs() []net.HardwareAddr { return r.MacAddrs } // FindDevicesByUser implements Querier.FindDevicesByUser. func (q *DBQuerier) FindDevicesByUser(ctx context.Context, id int) ([]FindDevicesByUserRow, error) { @@ -166,11 +166,11 @@ type CompositeUserRow struct { User User `json:"user" db:"user"` } -func (r CompositeUserRow) GetMac() net.HardwareAddr { return r.Mac } +func (r *CompositeUserRow) GetMac() net.HardwareAddr { return r.Mac } -func (r CompositeUserRow) GetType() DeviceType { return r.Type } +func (r *CompositeUserRow) GetType() DeviceType { return r.Type } -func (r CompositeUserRow) GetUser() User { return r.User } +func (r *CompositeUserRow) GetUser() User { return r.User } // CompositeUser implements Querier.CompositeUser. func (q *DBQuerier) CompositeUser(ctx context.Context) ([]CompositeUserRow, error) { @@ -220,9 +220,9 @@ type CompositeUserOneTwoColsRow struct { User User `json:"user" db:"user"` } -func (r CompositeUserOneTwoColsRow) GetNum() int32 { return r.Num } +func (r *CompositeUserOneTwoColsRow) GetNum() int32 { return r.Num } -func (r CompositeUserOneTwoColsRow) GetUser() User { return r.User } +func (r *CompositeUserOneTwoColsRow) GetUser() User { return r.User } // CompositeUserOneTwoCols implements Querier.CompositeUserOneTwoCols. func (q *DBQuerier) CompositeUserOneTwoCols(ctx context.Context) (CompositeUserOneTwoColsRow, error) { diff --git a/example/enums/query.sql.go b/example/enums/query.sql.go index c7ce05ae..af2fb7e2 100644 --- a/example/enums/query.sql.go +++ b/example/enums/query.sql.go @@ -122,9 +122,9 @@ type FindAllDevicesRow struct { Type DeviceType `json:"type" db:"type"` } -func (r FindAllDevicesRow) GetMac() net.HardwareAddr { return r.Mac } +func (r *FindAllDevicesRow) GetMac() net.HardwareAddr { return r.Mac } -func (r FindAllDevicesRow) GetType() DeviceType { return r.Type } +func (r *FindAllDevicesRow) GetType() DeviceType { return r.Type } // FindAllDevices implements Querier.FindAllDevices. func (q *DBQuerier) FindAllDevices(ctx context.Context) ([]FindAllDevicesRow, error) { @@ -210,9 +210,9 @@ type FindManyDeviceArrayWithNumRow struct { DeviceTypes []DeviceType `json:"device_types" db:"device_types"` } -func (r FindManyDeviceArrayWithNumRow) GetNum() *int32 { return r.Num } +func (r *FindManyDeviceArrayWithNumRow) GetNum() *int32 { return r.Num } -func (r FindManyDeviceArrayWithNumRow) GetDeviceTypes() []DeviceType { return r.DeviceTypes } +func (r *FindManyDeviceArrayWithNumRow) GetDeviceTypes() []DeviceType { return r.DeviceTypes } // FindManyDeviceArrayWithNum implements Querier.FindManyDeviceArrayWithNum. func (q *DBQuerier) FindManyDeviceArrayWithNum(ctx context.Context) ([]FindManyDeviceArrayWithNumRow, error) { diff --git a/example/erp/order/customer.sql.go b/example/erp/order/customer.sql.go index 807f61d4..db1bd31a 100644 --- a/example/erp/order/customer.sql.go +++ b/example/erp/order/customer.sql.go @@ -97,11 +97,11 @@ type CreateTenantRow struct { Name string `json:"name" db:"name"` } -func (r CreateTenantRow) GetTenantID() int { return r.TenantID } +func (r *CreateTenantRow) GetTenantID() int { return r.TenantID } -func (r CreateTenantRow) GetRname() *string { return r.Rname } +func (r *CreateTenantRow) GetRname() *string { return r.Rname } -func (r CreateTenantRow) GetName() string { return r.Name } +func (r *CreateTenantRow) GetName() string { return r.Name } // CreateTenant implements Querier.CreateTenant. func (q *DBQuerier) CreateTenant(ctx context.Context, key string, name string) (CreateTenantRow, error) { @@ -138,13 +138,13 @@ type FindOrdersByCustomerRow struct { CustomerID *int32 `json:"customer_id" db:"customer_id"` } -func (r FindOrdersByCustomerRow) GetOrderID() int32 { return r.OrderID } +func (r *FindOrdersByCustomerRow) GetOrderID() int32 { return r.OrderID } -func (r FindOrdersByCustomerRow) GetOrderDate() pgtype.Timestamptz { return r.OrderDate } +func (r *FindOrdersByCustomerRow) GetOrderDate() pgtype.Timestamptz { return r.OrderDate } -func (r FindOrdersByCustomerRow) GetOrderTotal() pgtype.Numeric { return r.OrderTotal } +func (r *FindOrdersByCustomerRow) GetOrderTotal() pgtype.Numeric { return r.OrderTotal } -func (r FindOrdersByCustomerRow) GetCustomerID() *int32 { return r.CustomerID } +func (r *FindOrdersByCustomerRow) GetCustomerID() *int32 { return r.CustomerID } // FindOrdersByCustomer implements Querier.FindOrdersByCustomer. func (q *DBQuerier) FindOrdersByCustomer(ctx context.Context, customerID int32) ([]FindOrdersByCustomerRow, error) { @@ -181,11 +181,11 @@ type FindProductsInOrderRow struct { Name *string `json:"name" db:"name"` } -func (r FindProductsInOrderRow) GetOrderID() *int32 { return r.OrderID } +func (r *FindProductsInOrderRow) GetOrderID() *int32 { return r.OrderID } -func (r FindProductsInOrderRow) GetProductID() *int32 { return r.ProductID } +func (r *FindProductsInOrderRow) GetProductID() *int32 { return r.ProductID } -func (r FindProductsInOrderRow) GetName() *string { return r.Name } +func (r *FindProductsInOrderRow) GetName() *string { return r.Name } // FindProductsInOrder implements Querier.FindProductsInOrder. func (q *DBQuerier) FindProductsInOrder(ctx context.Context, orderID int32) ([]FindProductsInOrderRow, error) { @@ -228,13 +228,13 @@ type InsertCustomerRow struct { Email string `json:"email" db:"email"` } -func (r InsertCustomerRow) GetCustomerID() int32 { return r.CustomerID } +func (r *InsertCustomerRow) GetCustomerID() int32 { return r.CustomerID } -func (r InsertCustomerRow) GetFirstName() string { return r.FirstName } +func (r *InsertCustomerRow) GetFirstName() string { return r.FirstName } -func (r InsertCustomerRow) GetLastName() string { return r.LastName } +func (r *InsertCustomerRow) GetLastName() string { return r.LastName } -func (r InsertCustomerRow) GetEmail() string { return r.Email } +func (r *InsertCustomerRow) GetEmail() string { return r.Email } // InsertCustomer implements Querier.InsertCustomer. func (q *DBQuerier) InsertCustomer(ctx context.Context, params InsertCustomerParams) (InsertCustomerRow, error) { @@ -277,13 +277,13 @@ type InsertOrderRow struct { CustomerID *int32 `json:"customer_id" db:"customer_id"` } -func (r InsertOrderRow) GetOrderID() int32 { return r.OrderID } +func (r *InsertOrderRow) GetOrderID() int32 { return r.OrderID } -func (r InsertOrderRow) GetOrderDate() pgtype.Timestamptz { return r.OrderDate } +func (r *InsertOrderRow) GetOrderDate() pgtype.Timestamptz { return r.OrderDate } -func (r InsertOrderRow) GetOrderTotal() pgtype.Numeric { return r.OrderTotal } +func (r *InsertOrderRow) GetOrderTotal() pgtype.Numeric { return r.OrderTotal } -func (r InsertOrderRow) GetCustomerID() *int32 { return r.CustomerID } +func (r *InsertOrderRow) GetCustomerID() *int32 { return r.CustomerID } // InsertOrder implements Querier.InsertOrder. func (q *DBQuerier) InsertOrder(ctx context.Context, params InsertOrderParams) (InsertOrderRow, error) { diff --git a/example/erp/order/price.sql.go b/example/erp/order/price.sql.go index 19c32c69..1355598f 100644 --- a/example/erp/order/price.sql.go +++ b/example/erp/order/price.sql.go @@ -25,13 +25,13 @@ type FindOrdersByPriceRow struct { CustomerID *int32 `json:"customer_id" db:"customer_id"` } -func (r FindOrdersByPriceRow) GetOrderID() int32 { return r.OrderID } +func (r *FindOrdersByPriceRow) GetOrderID() int32 { return r.OrderID } -func (r FindOrdersByPriceRow) GetOrderDate() pgtype.Timestamptz { return r.OrderDate } +func (r *FindOrdersByPriceRow) GetOrderDate() pgtype.Timestamptz { return r.OrderDate } -func (r FindOrdersByPriceRow) GetOrderTotal() pgtype.Numeric { return r.OrderTotal } +func (r *FindOrdersByPriceRow) GetOrderTotal() pgtype.Numeric { return r.OrderTotal } -func (r FindOrdersByPriceRow) GetCustomerID() *int32 { return r.CustomerID } +func (r *FindOrdersByPriceRow) GetCustomerID() *int32 { return r.CustomerID } // FindOrdersByPrice implements Querier.FindOrdersByPrice. func (q *DBQuerier) FindOrdersByPrice(ctx context.Context, minTotal pgtype.Numeric) ([]FindOrdersByPriceRow, error) { @@ -64,9 +64,9 @@ type FindOrdersMRRRow struct { OrderMRR pgtype.Numeric `json:"order_mrr" db:"order_mrr"` } -func (r FindOrdersMRRRow) GetMonth() pgtype.Timestamptz { return r.Month } +func (r *FindOrdersMRRRow) GetMonth() pgtype.Timestamptz { return r.Month } -func (r FindOrdersMRRRow) GetOrderMRR() pgtype.Numeric { return r.OrderMRR } +func (r *FindOrdersMRRRow) GetOrderMRR() pgtype.Numeric { return r.OrderMRR } // FindOrdersMRR implements Querier.FindOrdersMRR. func (q *DBQuerier) FindOrdersMRR(ctx context.Context) ([]FindOrdersMRRRow, error) { diff --git a/example/function/query.sql.go b/example/function/query.sql.go index 6f3b17d6..1d121729 100644 --- a/example/function/query.sql.go +++ b/example/function/query.sql.go @@ -98,9 +98,9 @@ type OutParamsRow struct { Stats ListStats `json:"_stats" db:"_stats"` } -func (r OutParamsRow) GetItems() []ListItem { return r.Items } +func (r *OutParamsRow) GetItems() []ListItem { return r.Items } -func (r OutParamsRow) GetStats() ListStats { return r.Stats } +func (r *OutParamsRow) GetStats() ListStats { return r.Stats } // OutParams implements Querier.OutParams. func (q *DBQuerier) OutParams(ctx context.Context) ([]OutParamsRow, error) { diff --git a/example/inline_param_count/inline0/query.sql.go b/example/inline_param_count/inline0/query.sql.go index 10af6deb..e5ea4c6b 100644 --- a/example/inline_param_count/inline0/query.sql.go +++ b/example/inline_param_count/inline0/query.sql.go @@ -117,13 +117,13 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix" db:"suffix"` } -func (r FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } +func (r *FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } -func (r FindAuthorByIDRow) GetFirstName() string { return r.FirstName } +func (r *FindAuthorByIDRow) GetFirstName() string { return r.FirstName } -func (r FindAuthorByIDRow) GetLastName() string { return r.LastName } +func (r *FindAuthorByIDRow) GetLastName() string { return r.LastName } -func (r FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } +func (r *FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, params FindAuthorByIDParams) (FindAuthorByIDRow, error) { diff --git a/example/inline_param_count/inline1/query.sql.go b/example/inline_param_count/inline1/query.sql.go index ee37add6..a947c93f 100644 --- a/example/inline_param_count/inline1/query.sql.go +++ b/example/inline_param_count/inline1/query.sql.go @@ -113,13 +113,13 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix" db:"suffix"` } -func (r FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } +func (r *FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } -func (r FindAuthorByIDRow) GetFirstName() string { return r.FirstName } +func (r *FindAuthorByIDRow) GetFirstName() string { return r.FirstName } -func (r FindAuthorByIDRow) GetLastName() string { return r.LastName } +func (r *FindAuthorByIDRow) GetLastName() string { return r.LastName } -func (r FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } +func (r *FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { diff --git a/example/inline_param_count/inline2/query.sql.go b/example/inline_param_count/inline2/query.sql.go index a2ea2481..a95a4277 100644 --- a/example/inline_param_count/inline2/query.sql.go +++ b/example/inline_param_count/inline2/query.sql.go @@ -113,13 +113,13 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix" db:"suffix"` } -func (r FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } +func (r *FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } -func (r FindAuthorByIDRow) GetFirstName() string { return r.FirstName } +func (r *FindAuthorByIDRow) GetFirstName() string { return r.FirstName } -func (r FindAuthorByIDRow) GetLastName() string { return r.LastName } +func (r *FindAuthorByIDRow) GetLastName() string { return r.LastName } -func (r FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } +func (r *FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { diff --git a/example/inline_param_count/inline3/query.sql.go b/example/inline_param_count/inline3/query.sql.go index 3f1da2d1..c45f24e8 100644 --- a/example/inline_param_count/inline3/query.sql.go +++ b/example/inline_param_count/inline3/query.sql.go @@ -113,13 +113,13 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix" db:"suffix"` } -func (r FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } +func (r *FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } -func (r FindAuthorByIDRow) GetFirstName() string { return r.FirstName } +func (r *FindAuthorByIDRow) GetFirstName() string { return r.FirstName } -func (r FindAuthorByIDRow) GetLastName() string { return r.LastName } +func (r *FindAuthorByIDRow) GetLastName() string { return r.LastName } -func (r FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } +func (r *FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { diff --git a/example/ltree/query.sql.go b/example/ltree/query.sql.go index 03bbd8ef..82be8213 100644 --- a/example/ltree/query.sql.go +++ b/example/ltree/query.sql.go @@ -163,9 +163,9 @@ type FindLtreeInputRow struct { TextArr pgtype.Array[pgtype.Text] `json:"text_arr" db:"text_arr"` } -func (r FindLtreeInputRow) GetLtree() pgtype.Text { return r.Ltree } +func (r *FindLtreeInputRow) GetLtree() pgtype.Text { return r.Ltree } -func (r FindLtreeInputRow) GetTextArr() pgtype.Array[pgtype.Text] { return r.TextArr } +func (r *FindLtreeInputRow) GetTextArr() pgtype.Array[pgtype.Text] { return r.TextArr } // FindLtreeInput implements Querier.FindLtreeInput. func (q *DBQuerier) FindLtreeInput(ctx context.Context, inLtree pgtype.Text, inLtreeArray []string) (FindLtreeInputRow, error) { diff --git a/example/pgcrypto/query.sql.go b/example/pgcrypto/query.sql.go index ceb4c1db..70ecf64e 100644 --- a/example/pgcrypto/query.sql.go +++ b/example/pgcrypto/query.sql.go @@ -96,9 +96,9 @@ type FindUserRow struct { Pass string `json:"pass" db:"pass"` } -func (r FindUserRow) GetEmail() string { return r.Email } +func (r *FindUserRow) GetEmail() string { return r.Email } -func (r FindUserRow) GetPass() string { return r.Pass } +func (r *FindUserRow) GetPass() string { return r.Pass } // FindUser implements Querier.FindUser. func (q *DBQuerier) FindUser(ctx context.Context, email string) (FindUserRow, error) { diff --git a/example/syntax/query.sql.go b/example/syntax/query.sql.go index 953eece5..2b26e20c 100644 --- a/example/syntax/query.sql.go +++ b/example/syntax/query.sql.go @@ -213,9 +213,9 @@ type IllegalNameSymbolsRow struct { FooBar string `json:"foo.bar!@#$%&*()\"--+" db:"foo.bar!@#$%&*()\"--+"` } -func (r IllegalNameSymbolsRow) GetUnnamedColumn0() string { return r.UnnamedColumn0 } +func (r *IllegalNameSymbolsRow) GetUnnamedColumn0() string { return r.UnnamedColumn0 } -func (r IllegalNameSymbolsRow) GetFooBar() string { return r.FooBar } +func (r *IllegalNameSymbolsRow) GetFooBar() string { return r.FooBar } // IllegalNameSymbols implements Querier.IllegalNameSymbols. func (q *DBQuerier) IllegalNameSymbols(ctx context.Context, helloWorld string) (IllegalNameSymbolsRow, error) { diff --git a/example/void/query.sql.go b/example/void/query.sql.go index dd0b9aa9..c1ed8636 100644 --- a/example/void/query.sql.go +++ b/example/void/query.sql.go @@ -137,9 +137,9 @@ type VoidThreeRow struct { Bar string `json:"bar" db:"bar"` } -func (r VoidThreeRow) GetFoo() string { return r.Foo } +func (r *VoidThreeRow) GetFoo() string { return r.Foo } -func (r VoidThreeRow) GetBar() string { return r.Bar } +func (r *VoidThreeRow) GetBar() string { return r.Bar } // VoidThree implements Querier.VoidThree. func (q *DBQuerier) VoidThree(ctx context.Context) (VoidThreeRow, error) { diff --git a/internal/codegen/golang/templated_file.go b/internal/codegen/golang/templated_file.go index 33a361d7..b8b8329c 100644 --- a/internal/codegen/golang/templated_file.go +++ b/internal/codegen/golang/templated_file.go @@ -574,7 +574,7 @@ func (tq TemplatedQuery) EmitRowGetterMethods() string { } sb := &strings.Builder{} for _, out := range tq.Outputs { - sb.WriteString("\n\nfunc (r ") + sb.WriteString("\n\nfunc (r *") sb.WriteString(tq.Name) sb.WriteString("Row) ") sb.WriteString(out.GetterName) diff --git a/internal/codegen/golang/templated_file_test.go b/internal/codegen/golang/templated_file_test.go index 2b076165..f4185798 100644 --- a/internal/codegen/golang/templated_file_test.go +++ b/internal/codegen/golang/templated_file_test.go @@ -55,9 +55,9 @@ type FindAuthorsProjection interface { assert.Equal(t, ` -func (r FindAuthorsRow) GetAuthorID() int { return r.AuthorID } +func (r *FindAuthorsRow) GetAuthorID() int { return r.AuthorID } -func (r FindAuthorsRow) GetName() string { return r.Name }`, query.EmitRowGetterMethods()) +func (r *FindAuthorsRow) GetName() string { return r.Name }`, query.EmitRowGetterMethods()) } func TestTemplatedQuery_EmitProjectionSkipsSingleColumn(t *testing.T) { diff --git a/internal/pg/query.sql.go b/internal/pg/query.sql.go index e634ffda..f1729009 100644 --- a/internal/pg/query.sql.go +++ b/internal/pg/query.sql.go @@ -149,19 +149,19 @@ type FindEnumTypesRow struct { DefaultExpr string `json:"default_expr" db:"default_expr"` } -func (r FindEnumTypesRow) GetOID() uint32 { return r.OID } +func (r *FindEnumTypesRow) GetOID() uint32 { return r.OID } -func (r FindEnumTypesRow) GetTypeName() string { return r.TypeName } +func (r *FindEnumTypesRow) GetTypeName() string { return r.TypeName } -func (r FindEnumTypesRow) GetChildOIDs() []int { return r.ChildOIDs } +func (r *FindEnumTypesRow) GetChildOIDs() []int { return r.ChildOIDs } -func (r FindEnumTypesRow) GetOrders() []float32 { return r.Orders } +func (r *FindEnumTypesRow) GetOrders() []float32 { return r.Orders } -func (r FindEnumTypesRow) GetLabels() []string { return r.Labels } +func (r *FindEnumTypesRow) GetLabels() []string { return r.Labels } -func (r FindEnumTypesRow) GetTypeKind() byte { return r.TypeKind } +func (r *FindEnumTypesRow) GetTypeKind() byte { return r.TypeKind } -func (r FindEnumTypesRow) GetDefaultExpr() string { return r.DefaultExpr } +func (r *FindEnumTypesRow) GetDefaultExpr() string { return r.DefaultExpr } // FindEnumTypes implements Querier.FindEnumTypes. func (q *DBQuerier) FindEnumTypes(ctx context.Context, oids []uint32) ([]FindEnumTypesRow, error) { @@ -223,13 +223,13 @@ type FindArrayTypesRow struct { TypeKind byte `json:"type_kind" db:"type_kind"` } -func (r FindArrayTypesRow) GetOID() uint32 { return r.OID } +func (r *FindArrayTypesRow) GetOID() uint32 { return r.OID } -func (r FindArrayTypesRow) GetTypeName() string { return r.TypeName } +func (r *FindArrayTypesRow) GetTypeName() string { return r.TypeName } -func (r FindArrayTypesRow) GetElemOID() uint32 { return r.ElemOID } +func (r *FindArrayTypesRow) GetElemOID() uint32 { return r.ElemOID } -func (r FindArrayTypesRow) GetTypeKind() byte { return r.TypeKind } +func (r *FindArrayTypesRow) GetTypeKind() byte { return r.TypeKind } // FindArrayTypes implements Querier.FindArrayTypes. func (q *DBQuerier) FindArrayTypes(ctx context.Context, oids []uint32) ([]FindArrayTypesRow, error) { @@ -300,21 +300,21 @@ type FindCompositeTypesRow struct { ColTypeNames []string `json:"col_type_names" db:"col_type_names"` } -func (r FindCompositeTypesRow) GetTableTypeName() string { return r.TableTypeName } +func (r *FindCompositeTypesRow) GetTableTypeName() string { return r.TableTypeName } -func (r FindCompositeTypesRow) GetTableTypeOID() uint32 { return r.TableTypeOID } +func (r *FindCompositeTypesRow) GetTableTypeOID() uint32 { return r.TableTypeOID } -func (r FindCompositeTypesRow) GetTableName() string { return r.TableName } +func (r *FindCompositeTypesRow) GetTableName() string { return r.TableName } -func (r FindCompositeTypesRow) GetColNames() []string { return r.ColNames } +func (r *FindCompositeTypesRow) GetColNames() []string { return r.ColNames } -func (r FindCompositeTypesRow) GetColOIDs() []int { return r.ColOIDs } +func (r *FindCompositeTypesRow) GetColOIDs() []int { return r.ColOIDs } -func (r FindCompositeTypesRow) GetColOrders() []int { return r.ColOrders } +func (r *FindCompositeTypesRow) GetColOrders() []int { return r.ColOrders } -func (r FindCompositeTypesRow) GetColNotNulls() []bool { return r.ColNotNulls } +func (r *FindCompositeTypesRow) GetColNotNulls() []bool { return r.ColNotNulls } -func (r FindCompositeTypesRow) GetColTypeNames() []string { return r.ColTypeNames } +func (r *FindCompositeTypesRow) GetColTypeNames() []string { return r.ColTypeNames } // FindCompositeTypes implements Querier.FindCompositeTypes. func (q *DBQuerier) FindCompositeTypes(ctx context.Context, oids []uint32) ([]FindCompositeTypesRow, error) { @@ -438,11 +438,11 @@ type FindOIDNamesRow struct { Kind byte `json:"kind" db:"kind"` } -func (r FindOIDNamesRow) GetOID() uint32 { return r.OID } +func (r *FindOIDNamesRow) GetOID() uint32 { return r.OID } -func (r FindOIDNamesRow) GetName() string { return r.Name } +func (r *FindOIDNamesRow) GetName() string { return r.Name } -func (r FindOIDNamesRow) GetKind() byte { return r.Kind } +func (r *FindOIDNamesRow) GetKind() byte { return r.Kind } // FindOIDNames implements Querier.FindOIDNames. func (q *DBQuerier) FindOIDNames(ctx context.Context, oid []uint32) ([]FindOIDNamesRow, error) { From 3a56366e8057af5462bdb1c5e1d806a0ac391831 Mon Sep 17 00:00:00 2001 From: adelnizamutdinov Date: Thu, 18 Jun 2026 16:08:47 +0300 Subject: [PATCH 21/27] taskfile --- .gitignore | 7 ++-- CONTRIBUTING.md | 28 ++++++------- Makefile | 55 ------------------------- Taskfile.yaml | 106 ++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 19 +++++---- go.sum | 42 ++++++++++--------- 6 files changed, 156 insertions(+), 101 deletions(-) delete mode 100644 Makefile create mode 100644 Taskfile.yaml diff --git a/.gitignore b/.gitignore index 4af7aa0c..ebb89c72 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -/*.iml -/.idea/ -/dist/ +*.iml +.idea/ +dist/ .DS_Store +.task/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c0dead6..727d0672 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,21 +10,21 @@ First, read [ARCHITECTURE.md](ARCHITECTURE.md) to get a lay of the land. # Dependencies - see Setup below # Start a long-lived Postgres server in Docker for integration tests. -# Connect with "make psql" -make start +# Connect with "task psql" +task start # Hack # Commit changes # Validate changes -make lint && make test && make acceptance-test -# make all - equivalent -# make - equivalent +task lint && task test && task acceptance-test +# task all - equivalent +# task - equivalent # Send PR to GitHub. Check that tests and lints passed. # Stop Postgres server running in Docker. -make stop +task stop ``` ## Design goals of pggen @@ -72,8 +72,8 @@ tests from one another. Creating a new schema is much faster than spinning up a new Dockerized Postgres instance. ```shell -make start -make test # all unit tests +task start +task test # all unit tests ``` To run the acceptance tests to validate that pggen produces the same code as @@ -83,13 +83,13 @@ the checked-in example code: # Acceptance tests check that there's no Git diffs so commit code first. git commit -m "some message" -make acceptance-test +task acceptance-test ``` To update the acceptance tests after changing the code generator: ```shell -make update-acceptance-test +task update-acceptance-test ``` ### Testing hierarchy @@ -97,15 +97,15 @@ make update-acceptance-test pggen has tests at most parts of the testing hierarchy. - Unit tests to test the logic of small, independent components, like - [casing_test.go]. Run with `make test`. + [casing_test.go]. Run with `task test`. - Integration tests like the [pginfer_test.go] to test that the code works (integrates) with different subsystems like Postgres, Docker, or other Go - packages. As with unit tests, run with `make test`. + packages. As with unit tests, run with `task test`. - Acceptance tests like [example/nested/codegen_test.go] to test that pggen produces the exact same output as the checked-in examples. Run with - `make acceptance-test`. + `task acceptance-test`. [casing_test.go]: internal/casing/casing_test.go [pginfer_test.go]: internal/pginfer/pginfer_test.go @@ -131,4 +131,4 @@ in the test output. You can connect to that schema with: PGPASSWORD=hunter2 psql --host=127.0.0.1 --port=5555 --username=postgres pggen postgres> set search_path to 'pggen_test_' -``` \ No newline at end of file +``` diff --git a/Makefile b/Makefile deleted file mode 100644 index 2fa4e1d5..00000000 --- a/Makefile +++ /dev/null @@ -1,55 +0,0 @@ -SHELL := bash -.SHELLFLAGS := -euo pipefail -c -.ONESHELL: # use a single shell for commands instead a new shell per line -.DELETE_ON_ERROR: # delete output files when make rule fails -MAKEFLAGS += --warn-undefined-variables -MAKEFLAGS += --no-builtin-rules - -version := $(shell date '+%Y-%m-%d') -commit := $(shell git rev-parse --short HEAD) -ldflags := -ldflags "-X 'main.version=${version}' -X 'main.commit=${commit}'" - -.PHONY: all -all: lint test acceptance-test - -.PHONY: check -check: lint test update-acceptance-test - -.PHONY: start -start: - docker-compose up -d - -.PHONY: stop -stop: - docker-compose down - -.PHONY: restart -restart: stop start - -.PHONY: psql -psql: - PGPASSWORD=hunter2 psql --host=127.0.0.1 --port=5555 --username=postgres pggen - -.PHONY: test -test: - go run ./internal/pgdocker/cmd/pgdocker -- go test ./... - -.PHONY: acceptance-test -acceptance-test: - DOCKER_API_VERSION=1.39 go test ./example/acceptance_test.go - -.PHONY: update-acceptance-test -update-acceptance-test: - go test ./example/acceptance_test.go -update - -.PHONY: lint -lint: - go tool golangci-lint run - -.PHONY: dist-dir -dist-dir: - mkdir -p dist - -.PHONY: release -release: - ./script/release.sh diff --git a/Taskfile.yaml b/Taskfile.yaml new file mode 100644 index 00000000..f304a20b --- /dev/null +++ b/Taskfile.yaml @@ -0,0 +1,106 @@ +version: '3' + +vars: + VERSION: + sh: date '+%Y-%m-%d' + COMMIT: + sh: git rev-parse --short HEAD + LDFLAGS: -ldflags "-X 'main.version={{.VERSION}}' -X 'main.commit={{.COMMIT}}'" + +tasks: + default: + desc: Run lint, tests, and acceptance tests + deps: + - all + + all: + desc: Run lint, tests, and acceptance tests + run: once + deps: + - lint + - test + - acceptance-test + + check: + desc: Run lint, tests, and update acceptance tests + run: once + deps: + - lint + - test + - update-acceptance-test + + outdated: + desc: Print modules with available updates + run: once + cmd: > + go list -m -u -mod=readonly -f + '{{`{{if .Update}}{{.Path}} {{.Version}} -> {{.Update.Version}}{{end}}`}}' + all + + start: + desc: Start the Postgres Docker service + run: once + cmd: docker-compose up -d + + stop: + desc: Stop the Postgres Docker service + run: once + cmd: docker-compose down + + restart: + desc: Restart the Postgres Docker service + run: once + cmds: + - docker-compose down + - docker-compose up -d + + psql: + desc: Connect to the local Postgres service + run: once + env: + PGPASSWORD: hunter2 + cmd: psql --host=127.0.0.1 --port=5555 --username=postgres pggen + + test: + desc: Run the test suite through pgdocker + run: once + cmd: go run ./internal/pgdocker/cmd/pgdocker -- go test ./... + sources: + - "**/*.go" + - "**/*.sql" + - go.sum + + acceptance-test: + desc: Run acceptance tests + run: once + env: + DOCKER_API_VERSION: '1.39' + cmd: go test ./example/acceptance_test.go + + update-acceptance-test: + desc: Update acceptance test output + run: once + cmd: go test ./example/acceptance_test.go -update + sources: + - "**/*.sql" + - "**/*.go" + - go.sum + + lint: + desc: Run golangci-lint + run: once + cmd: go tool golangci-lint run + sources: + - .golangci.yaml + - "**/*.go" + - go.sum + + dist-dir: + desc: Create the dist directory + run: once + cmd: mkdir -p dist + + release: + desc: Build a release + run: once + cmd: ./script/release.sh diff --git a/go.mod b/go.mod index 3cfc37a3..8b1d969a 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,16 @@ module github.com/meoyawn/pggen -go 1.24.1 +go 1.25.0 require ( github.com/bmatcuk/doublestar v1.3.4 github.com/docker/docker v28.5.2+incompatible github.com/docker/go-connections v0.7.0 github.com/google/go-cmp v0.7.0 - github.com/jackc/pgx/v5 v5.5.1 + github.com/jackc/pgx/v5 v5.10.0 github.com/peterbourgon/ff/v3 v3.4.0 - github.com/stretchr/testify v1.10.0 - golang.org/x/mod v0.24.0 + github.com/stretchr/testify v1.11.1 + golang.org/x/mod v0.37.0 ) require ( @@ -208,13 +208,14 @@ require ( go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.36.0 // indirect golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/text v0.29.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.31.0 // indirect + golang.org/x/tools v0.45.0 // indirect + golang.org/x/tools/go/expect v0.1.1-deprecated // indirect + golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index fea64d8c..fb103e21 100644 --- a/go.sum +++ b/go.sum @@ -344,10 +344,10 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI= -github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= @@ -588,8 +588,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8= @@ -683,8 +683,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -730,8 +728,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -772,8 +770,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -795,8 +793,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -849,8 +847,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -871,8 +869,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -934,8 +932,12 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= -golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 8055dbf62bce4523b5b1e6e23edbaa1c08aedcb8 Mon Sep 17 00:00:00 2001 From: adelnizamutdinov Date: Thu, 18 Jun 2026 16:11:09 +0300 Subject: [PATCH 22/27] gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ebb89c72..abb81d9b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ dist/ .DS_Store .task/ +.code-review-graph/ From db035d4328d65aa407725de61371298859eb3108 Mon Sep 17 00:00:00 2001 From: adelnizamutdinov Date: Thu, 18 Jun 2026 16:43:27 +0300 Subject: [PATCH 23/27] Add shared row annotations Fixes https://github.com/meoyawn/pggen/issues/1 --- example/author/codegen_test.go | 49 +++++++ example/author/query.sql | 4 +- example/author/query.sql.go | 98 ++------------ example/author/query.sql_test.go | 12 +- example/composite/query.sql.go | 21 --- example/custom_types/query.sql.go | 9 -- example/device/query.sql.go | 33 ----- example/enums/query.sql.go | 18 --- example/erp/order/customer.sql | 2 +- example/erp/order/customer.sql.go | 83 +----------- example/erp/order/customer.sql_test.go | 2 +- example/erp/order/price.sql | 2 +- example/erp/order/price.sql.go | 39 +----- example/function/query.sql.go | 9 -- .../inline_param_count/inline0/query.sql.go | 15 --- .../inline_param_count/inline1/query.sql.go | 15 --- .../inline_param_count/inline2/query.sql.go | 15 --- .../inline_param_count/inline3/query.sql.go | 15 --- example/ltree/query.sql.go | 9 -- example/pgcrypto/query.sql.go | 9 -- example/syntax/query.sql.go | 9 -- example/void/query.sql.go | 9 -- generate_test.go | 11 ++ internal/ast/ast.go | 1 + internal/codegen/golang/query.gotemplate | 2 - internal/codegen/golang/templated_file.go | 79 ++++-------- .../codegen/golang/templated_file_test.go | 120 ++++++++++-------- internal/codegen/golang/templater.go | 104 ++++++++++++--- internal/parser/parser.go | 7 +- internal/parser/parser_test.go | 12 ++ internal/pg/query.sql.go | 78 ------------ internal/pginfer/pginfer.go | 4 + 32 files changed, 291 insertions(+), 604 deletions(-) diff --git a/example/author/codegen_test.go b/example/author/codegen_test.go index b29133d9..4a2f8205 100644 --- a/example/author/codegen_test.go +++ b/example/author/codegen_test.go @@ -3,6 +3,7 @@ package author import ( "os" "path/filepath" + "strings" "testing" "github.com/meoyawn/pggen" @@ -44,3 +45,51 @@ func TestGenerate_Go_Example_Author(t *testing.T) { "Got file %s; does not match contents of %s", gotQueryFile, wantQueryFile) } + +func TestGenerate_Go_Example_Author_SharedRowAnnotation(t *testing.T) { + conn, cleanupFunc := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) + defer cleanupFunc() + + tmpDir := t.TempDir() + queryFile := filepath.Join(tmpDir, "query.sql") + err := os.WriteFile(queryFile, []byte(`-- FindAuthorById finds one author by ID. +-- name: FindAuthorByID :one row=Author +SELECT * FROM author WHERE author_id = pggen.arg('AuthorID'); + +-- FindAuthors finds authors by first name. +-- name: FindAuthors :many row=Author +SELECT * FROM author WHERE first_name = pggen.arg('FirstName'); +`), 0o600) + if err != nil { + t.Fatalf("write query file: %s", err) + } + + outDir := t.TempDir() + err = pggen.Generate( + pggen.GenerateOptions{ + ConnString: conn.Config().ConnString(), + QueryFiles: []string{queryFile}, + OutputDir: outDir, + GoPackage: "author", + Language: pggen.LangGo, + InlineParamCount: 2, + }) + if err != nil { + t.Fatalf("Generate() shared row annotation: %s", err) + } + + gotQueryFile := filepath.Join(outDir, "query.sql.go") + gotQueries, err := os.ReadFile(gotQueryFile) + if err != nil { + t.Fatalf("read generated query.go.sql: %s", err) + } + got := string(gotQueries) + + assert.Contains(t, got, "type AuthorRow struct {") + assert.Contains(t, got, "FindAuthorByID(ctx context.Context, authorID int32) (AuthorRow, error)") + assert.Contains(t, got, "FindAuthors(ctx context.Context, firstName string) ([]AuthorRow, error)") + assert.Contains(t, got, "pgx.RowToStructByName[AuthorRow]") + assert.Equal(t, 1, strings.Count(got, "type AuthorRow struct {")) + assert.NotContains(t, got, "type FindAuthorByIDRow struct {") + assert.NotContains(t, got, "type FindAuthorsRow struct {") +} diff --git a/example/author/query.sql b/example/author/query.sql index 963cd109..4657e46e 100644 --- a/example/author/query.sql +++ b/example/author/query.sql @@ -1,9 +1,9 @@ -- FindAuthorById finds one (or zero) authors by ID. --- name: FindAuthorByID :one +-- name: FindAuthorByID :one row=Author SELECT * FROM author WHERE author_id = pggen.arg('AuthorID'); -- FindAuthors finds authors by first name. --- name: FindAuthors :many +-- name: FindAuthors :many row=Author SELECT * FROM author WHERE first_name = pggen.arg('FirstName'); -- FindAuthorNames finds one (or zero) authors by ID. diff --git a/example/author/query.sql.go b/example/author/query.sql.go index 898cb19f..4937924c 100644 --- a/example/author/query.sql.go +++ b/example/author/query.sql.go @@ -14,10 +14,10 @@ type QueryName struct{} // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { // FindAuthorById finds one (or zero) authors by ID. - FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) + FindAuthorByID(ctx context.Context, authorID int32) (AuthorRow, error) // FindAuthors finds authors by first name. - FindAuthors(ctx context.Context, firstName string) ([]FindAuthorsRow, error) + FindAuthors(ctx context.Context, firstName string) ([]AuthorRow, error) // FindAuthorNames finds one (or zero) authors by ID. FindAuthorNames(ctx context.Context, authorID int32) ([]FindAuthorNamesRow, error) @@ -103,40 +103,25 @@ func addTypeToRegister(typ string) struct{} { const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` -type FindAuthorByIDProjection interface { - GetAuthorID() int32 - GetFirstName() string - GetLastName() string - GetSuffix() *string -} - -type FindAuthorByIDRow struct { +type AuthorRow struct { AuthorID int32 `json:"author_id" db:"author_id"` FirstName string `json:"first_name" db:"first_name"` LastName string `json:"last_name" db:"last_name"` Suffix *string `json:"suffix" db:"suffix"` } -func (r *FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } - -func (r *FindAuthorByIDRow) GetFirstName() string { return r.FirstName } - -func (r *FindAuthorByIDRow) GetLastName() string { return r.LastName } - -func (r *FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } - // FindAuthorByID implements Querier.FindAuthorByID. -func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { +func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (AuthorRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") rows, err := q.conn.Query(ctx, findAuthorByIDSQL, authorID) if err != nil { - var zero FindAuthorByIDRow + var zero AuthorRow return zero, fmt.Errorf("query FindAuthorByID: %w", err) } - result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[FindAuthorByIDRow]) + result, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[AuthorRow]) if err != nil { - var zero FindAuthorByIDRow + var zero AuthorRow return zero, fmt.Errorf("query FindAuthorByID: %w", err) } return result, nil @@ -144,40 +129,18 @@ func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAut const findAuthorsSQL = `SELECT * FROM author WHERE first_name = $1;` -type FindAuthorsProjection interface { - GetAuthorID() int32 - GetFirstName() string - GetLastName() string - GetSuffix() *string -} - -type FindAuthorsRow struct { - AuthorID int32 `json:"author_id" db:"author_id"` - FirstName string `json:"first_name" db:"first_name"` - LastName string `json:"last_name" db:"last_name"` - Suffix *string `json:"suffix" db:"suffix"` -} - -func (r *FindAuthorsRow) GetAuthorID() int32 { return r.AuthorID } - -func (r *FindAuthorsRow) GetFirstName() string { return r.FirstName } - -func (r *FindAuthorsRow) GetLastName() string { return r.LastName } - -func (r *FindAuthorsRow) GetSuffix() *string { return r.Suffix } - // FindAuthors implements Querier.FindAuthors. -func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAuthorsRow, error) { +func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]AuthorRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAuthors") rows, err := q.conn.Query(ctx, findAuthorsSQL, firstName) if err != nil { - var zero []FindAuthorsRow + var zero []AuthorRow return zero, fmt.Errorf("query FindAuthors: %w", err) } - result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindAuthorsRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[AuthorRow]) if err != nil { - var zero []FindAuthorsRow + var zero []AuthorRow return zero, fmt.Errorf("scan FindAuthors row: %w", err) } return result, nil @@ -185,20 +148,11 @@ func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]FindAu const findAuthorNamesSQL = `SELECT first_name, last_name FROM author ORDER BY author_id = $1;` -type FindAuthorNamesProjection interface { - GetFirstName() *string - GetLastName() *string -} - type FindAuthorNamesRow struct { FirstName *string `json:"first_name" db:"first_name"` LastName *string `json:"last_name" db:"last_name"` } -func (r *FindAuthorNamesRow) GetFirstName() *string { return r.FirstName } - -func (r *FindAuthorNamesRow) GetLastName() *string { return r.LastName } - // FindAuthorNames implements Querier.FindAuthorNames. func (q *DBQuerier) FindAuthorNames(ctx context.Context, authorID int32) ([]FindAuthorNamesRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAuthorNames") @@ -237,13 +191,6 @@ func (q *DBQuerier) FindFirstNames(ctx context.Context, authorID int32) ([]*stri const findFirstAuthorSQL = `SELECT * FROM author ORDER BY author_id;` -type FindFirstAuthorProjection interface { - GetAuthorID() *int32 - GetFirstName() *string - GetLastName() *string - GetSuffix() *string -} - type FindFirstAuthorRow struct { AuthorID *int32 `json:"author_id" db:"author_id"` FirstName *string `json:"first_name" db:"first_name"` @@ -251,14 +198,6 @@ type FindFirstAuthorRow struct { Suffix *string `json:"suffix" db:"suffix"` } -func (r *FindFirstAuthorRow) GetAuthorID() *int32 { return r.AuthorID } - -func (r *FindFirstAuthorRow) GetFirstName() *string { return r.FirstName } - -func (r *FindFirstAuthorRow) GetLastName() *string { return r.LastName } - -func (r *FindFirstAuthorRow) GetSuffix() *string { return r.Suffix } - // FindFirstAuthor implements Querier.FindFirstAuthor. func (q *DBQuerier) FindFirstAuthor(ctx context.Context) (FindFirstAuthorRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindFirstAuthor") @@ -353,13 +292,6 @@ type InsertAuthorSuffixParams struct { Suffix string `json:"Suffix"` } -type InsertAuthorSuffixProjection interface { - GetAuthorID() int32 - GetFirstName() string - GetLastName() string - GetSuffix() *string -} - type InsertAuthorSuffixRow struct { AuthorID int32 `json:"author_id" db:"author_id"` FirstName string `json:"first_name" db:"first_name"` @@ -367,14 +299,6 @@ type InsertAuthorSuffixRow struct { Suffix *string `json:"suffix" db:"suffix"` } -func (r *InsertAuthorSuffixRow) GetAuthorID() int32 { return r.AuthorID } - -func (r *InsertAuthorSuffixRow) GetFirstName() string { return r.FirstName } - -func (r *InsertAuthorSuffixRow) GetLastName() string { return r.LastName } - -func (r *InsertAuthorSuffixRow) GetSuffix() *string { return r.Suffix } - // InsertAuthorSuffix implements Querier.InsertAuthorSuffix. func (q *DBQuerier) InsertAuthorSuffix(ctx context.Context, params InsertAuthorSuffixParams) (InsertAuthorSuffixRow, error) { ctx = context.WithValue(ctx, QueryName{}, "InsertAuthorSuffix") diff --git a/example/author/query.sql_test.go b/example/author/query.sql_test.go index 50805022..770467f7 100644 --- a/example/author/query.sql_test.go +++ b/example/author/query.sql_test.go @@ -23,7 +23,7 @@ func TestNewQuerier_FindAuthorByID(t *testing.T) { t.Run("FindAuthorByID", func(t *testing.T) { authorByID, err := q.FindAuthorByID(t.Context(), adamsID) require.NoError(t, err) - assert.Equal(t, FindAuthorByIDRow{ + assert.Equal(t, AuthorRow{ AuthorID: adamsID, FirstName: "john", LastName: "adams", @@ -53,7 +53,7 @@ func TestNewQuerier_FindAuthors(t *testing.T) { t.Run("FindAuthors - 1 row - john", func(t *testing.T) { authors, err := q.FindAuthors(t.Context(), "john") require.NoError(t, err) - want := []FindAuthorsRow{ + want := []AuthorRow{ { AuthorID: adamsID, FirstName: "john", @@ -73,7 +73,7 @@ func TestNewQuerier_FindAuthors(t *testing.T) { require.NoError(t, err) authors, err := q.FindAuthors(t.Context(), "bill") require.NoError(t, err) - want := []FindAuthorsRow{ + want := []AuthorRow{ { AuthorID: insRow.AuthorID, FirstName: "bill", @@ -87,7 +87,7 @@ func TestNewQuerier_FindAuthors(t *testing.T) { t.Run("FindAuthors - 2 rows - george", func(t *testing.T) { authors, err := q.FindAuthors(t.Context(), "george") require.NoError(t, err) - want := []FindAuthorsRow{ + want := []AuthorRow{ {AuthorID: washingtonID, FirstName: "george", LastName: "washington", Suffix: nil}, {AuthorID: carverID, FirstName: "george", LastName: "carver", Suffix: nil}, } @@ -97,7 +97,7 @@ func TestNewQuerier_FindAuthors(t *testing.T) { t.Run("FindAuthors - 0 rows - joe", func(t *testing.T) { authors, err := q.FindAuthors(t.Context(), "joe") require.NoError(t, err) - assert.Equal(t, []FindAuthorsRow{}, authors) + assert.Equal(t, []AuthorRow{}, authors) }) } @@ -201,7 +201,7 @@ func TestNewQuerier_DeleteAuthorsByFullName(t *testing.T) { authors, err := q.FindAuthors(t.Context(), "george") require.NoError(t, err) - want := []FindAuthorsRow{ + want := []AuthorRow{ { AuthorID: washingtonID, FirstName: "george", diff --git a/example/composite/query.sql.go b/example/composite/query.sql.go index a51ccb35..4edda308 100644 --- a/example/composite/query.sql.go +++ b/example/composite/query.sql.go @@ -122,20 +122,11 @@ type SearchScreenshotsParams struct { Offset int `json:"Offset"` } -type SearchScreenshotsProjection interface { - GetID() int - GetBlocks() []Blocks -} - type SearchScreenshotsRow struct { ID int `json:"id" db:"id"` Blocks []Blocks `json:"blocks" db:"blocks"` } -func (r *SearchScreenshotsRow) GetID() int { return r.ID } - -func (r *SearchScreenshotsRow) GetBlocks() []Blocks { return r.Blocks } - // SearchScreenshots implements Querier.SearchScreenshots. func (q *DBQuerier) SearchScreenshots(ctx context.Context, params SearchScreenshotsParams) ([]SearchScreenshotsRow, error) { ctx = context.WithValue(ctx, QueryName{}, "SearchScreenshots") @@ -194,24 +185,12 @@ INTO blocks (screenshot_id, body) VALUES ($1, $2) RETURNING id, screenshot_id, body;` -type InsertScreenshotBlocksProjection interface { - GetID() int - GetScreenshotID() int - GetBody() string -} - type InsertScreenshotBlocksRow struct { ID int `json:"id" db:"id"` ScreenshotID int `json:"screenshot_id" db:"screenshot_id"` Body string `json:"body" db:"body"` } -func (r *InsertScreenshotBlocksRow) GetID() int { return r.ID } - -func (r *InsertScreenshotBlocksRow) GetScreenshotID() int { return r.ScreenshotID } - -func (r *InsertScreenshotBlocksRow) GetBody() string { return r.Body } - // InsertScreenshotBlocks implements Querier.InsertScreenshotBlocks. func (q *DBQuerier) InsertScreenshotBlocks(ctx context.Context, screenshotID int, body string) (InsertScreenshotBlocksRow, error) { ctx = context.WithValue(ctx, QueryName{}, "InsertScreenshotBlocks") diff --git a/example/custom_types/query.sql.go b/example/custom_types/query.sql.go index bf9c9d4f..990ddb1a 100644 --- a/example/custom_types/query.sql.go +++ b/example/custom_types/query.sql.go @@ -77,20 +77,11 @@ func addTypeToRegister(typ string) struct{} { const customTypesSQL = `SELECT 'some_text', 1::bigint;` -type CustomTypesProjection interface { - GetColumn() mytype.String - GetInt8() CustomInt -} - type CustomTypesRow struct { Column mytype.String `json:"?column?" db:"?column?"` Int8 CustomInt `json:"int8" db:"int8"` } -func (r *CustomTypesRow) GetColumn() mytype.String { return r.Column } - -func (r *CustomTypesRow) GetInt8() CustomInt { return r.Int8 } - // CustomTypes implements Querier.CustomTypes. func (q *DBQuerier) CustomTypes(ctx context.Context) (CustomTypesRow, error) { ctx = context.WithValue(ctx, QueryName{}, "CustomTypes") diff --git a/example/device/query.sql.go b/example/device/query.sql.go index ef0a4a6b..5fe9dd59 100644 --- a/example/device/query.sql.go +++ b/example/device/query.sql.go @@ -112,24 +112,12 @@ const findDevicesByUserSQL = `SELECT FROM "user" WHERE id = $1;` -type FindDevicesByUserProjection interface { - GetID() int - GetName() string - GetMacAddrs() []net.HardwareAddr -} - type FindDevicesByUserRow struct { ID int `json:"id" db:"id"` Name string `json:"name" db:"name"` MacAddrs []net.HardwareAddr `json:"mac_addrs" db:"mac_addrs"` } -func (r *FindDevicesByUserRow) GetID() int { return r.ID } - -func (r *FindDevicesByUserRow) GetName() string { return r.Name } - -func (r *FindDevicesByUserRow) GetMacAddrs() []net.HardwareAddr { return r.MacAddrs } - // FindDevicesByUser implements Querier.FindDevicesByUser. func (q *DBQuerier) FindDevicesByUser(ctx context.Context, id int) ([]FindDevicesByUserRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindDevicesByUser") @@ -154,24 +142,12 @@ const compositeUserSQL = `SELECT FROM device d LEFT JOIN "user" u ON u.id = d.owner;` -type CompositeUserProjection interface { - GetMac() net.HardwareAddr - GetType() DeviceType - GetUser() User -} - type CompositeUserRow struct { Mac net.HardwareAddr `json:"mac" db:"mac"` Type DeviceType `json:"type" db:"type"` User User `json:"user" db:"user"` } -func (r *CompositeUserRow) GetMac() net.HardwareAddr { return r.Mac } - -func (r *CompositeUserRow) GetType() DeviceType { return r.Type } - -func (r *CompositeUserRow) GetUser() User { return r.User } - // CompositeUser implements Querier.CompositeUser. func (q *DBQuerier) CompositeUser(ctx context.Context) ([]CompositeUserRow, error) { ctx = context.WithValue(ctx, QueryName{}, "CompositeUser") @@ -210,20 +186,11 @@ func (q *DBQuerier) CompositeUserOne(ctx context.Context) (User, error) { const compositeUserOneTwoColsSQL = `SELECT 1 AS num, ROW (15, 'qux')::"user" AS "user";` -type CompositeUserOneTwoColsProjection interface { - GetNum() int32 - GetUser() User -} - type CompositeUserOneTwoColsRow struct { Num int32 `json:"num" db:"num"` User User `json:"user" db:"user"` } -func (r *CompositeUserOneTwoColsRow) GetNum() int32 { return r.Num } - -func (r *CompositeUserOneTwoColsRow) GetUser() User { return r.User } - // CompositeUserOneTwoCols implements Querier.CompositeUserOneTwoCols. func (q *DBQuerier) CompositeUserOneTwoCols(ctx context.Context) (CompositeUserOneTwoColsRow, error) { ctx = context.WithValue(ctx, QueryName{}, "CompositeUserOneTwoCols") diff --git a/example/enums/query.sql.go b/example/enums/query.sql.go index af2fb7e2..ce0e31d4 100644 --- a/example/enums/query.sql.go +++ b/example/enums/query.sql.go @@ -112,20 +112,11 @@ var _ = addTypeToRegister("\"_device_type\"") const findAllDevicesSQL = `SELECT mac, type FROM device;` -type FindAllDevicesProjection interface { - GetMac() net.HardwareAddr - GetType() DeviceType -} - type FindAllDevicesRow struct { Mac net.HardwareAddr `json:"mac" db:"mac"` Type DeviceType `json:"type" db:"type"` } -func (r *FindAllDevicesRow) GetMac() net.HardwareAddr { return r.Mac } - -func (r *FindAllDevicesRow) GetType() DeviceType { return r.Type } - // FindAllDevices implements Querier.FindAllDevices. func (q *DBQuerier) FindAllDevices(ctx context.Context) ([]FindAllDevicesRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAllDevices") @@ -200,20 +191,11 @@ const findManyDeviceArrayWithNumSQL = `SELECT 1 AS num, enum_range('ipad'::devic UNION ALL SELECT 2 as num, enum_range(NULL::device_type) AS device_types;` -type FindManyDeviceArrayWithNumProjection interface { - GetNum() *int32 - GetDeviceTypes() []DeviceType -} - type FindManyDeviceArrayWithNumRow struct { Num *int32 `json:"num" db:"num"` DeviceTypes []DeviceType `json:"device_types" db:"device_types"` } -func (r *FindManyDeviceArrayWithNumRow) GetNum() *int32 { return r.Num } - -func (r *FindManyDeviceArrayWithNumRow) GetDeviceTypes() []DeviceType { return r.DeviceTypes } - // FindManyDeviceArrayWithNum implements Querier.FindManyDeviceArrayWithNum. func (q *DBQuerier) FindManyDeviceArrayWithNum(ctx context.Context) ([]FindManyDeviceArrayWithNumRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindManyDeviceArrayWithNum") diff --git a/example/erp/order/customer.sql b/example/erp/order/customer.sql index d46d0e52..97d34cc5 100644 --- a/example/erp/order/customer.sql +++ b/example/erp/order/customer.sql @@ -3,7 +3,7 @@ INSERT INTO tenant (tenant_id, name) VALUES (base36_decode(pggen.arg('key')::text)::tenant_id, pggen.arg('name')::text) RETURNING *; --- name: FindOrdersByCustomer :many +-- name: FindOrdersByCustomer :many row=Order SELECT * FROM orders WHERE customer_id = pggen.arg('CustomerID'); diff --git a/example/erp/order/customer.sql.go b/example/erp/order/customer.sql.go index db1bd31a..086607d8 100644 --- a/example/erp/order/customer.sql.go +++ b/example/erp/order/customer.sql.go @@ -16,7 +16,7 @@ type QueryName struct{} type Querier interface { CreateTenant(ctx context.Context, key string, name string) (CreateTenantRow, error) - FindOrdersByCustomer(ctx context.Context, customerID int32) ([]FindOrdersByCustomerRow, error) + FindOrdersByCustomer(ctx context.Context, customerID int32) ([]OrderRow, error) FindProductsInOrder(ctx context.Context, orderID int32) ([]FindProductsInOrderRow, error) @@ -24,7 +24,7 @@ type Querier interface { InsertOrder(ctx context.Context, params InsertOrderParams) (InsertOrderRow, error) - FindOrdersByPrice(ctx context.Context, minTotal pgtype.Numeric) ([]FindOrdersByPriceRow, error) + FindOrdersByPrice(ctx context.Context, minTotal pgtype.Numeric) ([]OrderRow, error) FindOrdersMRR(ctx context.Context) ([]FindOrdersMRRRow, error) } @@ -85,24 +85,12 @@ const createTenantSQL = `INSERT INTO tenant (tenant_id, name) VALUES (base36_decode($1::text)::tenant_id, $2::text) RETURNING *;` -type CreateTenantProjection interface { - GetTenantID() int - GetRname() *string - GetName() string -} - type CreateTenantRow struct { TenantID int `json:"tenant_id" db:"tenant_id"` Rname *string `json:"rname" db:"rname"` Name string `json:"name" db:"name"` } -func (r *CreateTenantRow) GetTenantID() int { return r.TenantID } - -func (r *CreateTenantRow) GetRname() *string { return r.Rname } - -func (r *CreateTenantRow) GetName() string { return r.Name } - // CreateTenant implements Querier.CreateTenant. func (q *DBQuerier) CreateTenant(ctx context.Context, key string, name string) (CreateTenantRow, error) { ctx = context.WithValue(ctx, QueryName{}, "CreateTenant") @@ -124,40 +112,25 @@ const findOrdersByCustomerSQL = `SELECT * FROM orders WHERE customer_id = $1;` -type FindOrdersByCustomerProjection interface { - GetOrderID() int32 - GetOrderDate() pgtype.Timestamptz - GetOrderTotal() pgtype.Numeric - GetCustomerID() *int32 -} - -type FindOrdersByCustomerRow struct { +type OrderRow struct { OrderID int32 `json:"order_id" db:"order_id"` OrderDate pgtype.Timestamptz `json:"order_date" db:"order_date"` OrderTotal pgtype.Numeric `json:"order_total" db:"order_total"` CustomerID *int32 `json:"customer_id" db:"customer_id"` } -func (r *FindOrdersByCustomerRow) GetOrderID() int32 { return r.OrderID } - -func (r *FindOrdersByCustomerRow) GetOrderDate() pgtype.Timestamptz { return r.OrderDate } - -func (r *FindOrdersByCustomerRow) GetOrderTotal() pgtype.Numeric { return r.OrderTotal } - -func (r *FindOrdersByCustomerRow) GetCustomerID() *int32 { return r.CustomerID } - // FindOrdersByCustomer implements Querier.FindOrdersByCustomer. -func (q *DBQuerier) FindOrdersByCustomer(ctx context.Context, customerID int32) ([]FindOrdersByCustomerRow, error) { +func (q *DBQuerier) FindOrdersByCustomer(ctx context.Context, customerID int32) ([]OrderRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindOrdersByCustomer") rows, err := q.conn.Query(ctx, findOrdersByCustomerSQL, customerID) if err != nil { - var zero []FindOrdersByCustomerRow + var zero []OrderRow return zero, fmt.Errorf("query FindOrdersByCustomer: %w", err) } - result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindOrdersByCustomerRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[OrderRow]) if err != nil { - var zero []FindOrdersByCustomerRow + var zero []OrderRow return zero, fmt.Errorf("scan FindOrdersByCustomer row: %w", err) } return result, nil @@ -169,24 +142,12 @@ FROM orders o INNER JOIN product p USING (product_id) WHERE o.order_id = $1;` -type FindProductsInOrderProjection interface { - GetOrderID() *int32 - GetProductID() *int32 - GetName() *string -} - type FindProductsInOrderRow struct { OrderID *int32 `json:"order_id" db:"order_id"` ProductID *int32 `json:"product_id" db:"product_id"` Name *string `json:"name" db:"name"` } -func (r *FindProductsInOrderRow) GetOrderID() *int32 { return r.OrderID } - -func (r *FindProductsInOrderRow) GetProductID() *int32 { return r.ProductID } - -func (r *FindProductsInOrderRow) GetName() *string { return r.Name } - // FindProductsInOrder implements Querier.FindProductsInOrder. func (q *DBQuerier) FindProductsInOrder(ctx context.Context, orderID int32) ([]FindProductsInOrderRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindProductsInOrder") @@ -214,13 +175,6 @@ type InsertCustomerParams struct { Email string `json:"email"` } -type InsertCustomerProjection interface { - GetCustomerID() int32 - GetFirstName() string - GetLastName() string - GetEmail() string -} - type InsertCustomerRow struct { CustomerID int32 `json:"customer_id" db:"customer_id"` FirstName string `json:"first_name" db:"first_name"` @@ -228,14 +182,6 @@ type InsertCustomerRow struct { Email string `json:"email" db:"email"` } -func (r *InsertCustomerRow) GetCustomerID() int32 { return r.CustomerID } - -func (r *InsertCustomerRow) GetFirstName() string { return r.FirstName } - -func (r *InsertCustomerRow) GetLastName() string { return r.LastName } - -func (r *InsertCustomerRow) GetEmail() string { return r.Email } - // InsertCustomer implements Querier.InsertCustomer. func (q *DBQuerier) InsertCustomer(ctx context.Context, params InsertCustomerParams) (InsertCustomerRow, error) { ctx = context.WithValue(ctx, QueryName{}, "InsertCustomer") @@ -263,13 +209,6 @@ type InsertOrderParams struct { CustID int32 `json:"cust_id"` } -type InsertOrderProjection interface { - GetOrderID() int32 - GetOrderDate() pgtype.Timestamptz - GetOrderTotal() pgtype.Numeric - GetCustomerID() *int32 -} - type InsertOrderRow struct { OrderID int32 `json:"order_id" db:"order_id"` OrderDate pgtype.Timestamptz `json:"order_date" db:"order_date"` @@ -277,14 +216,6 @@ type InsertOrderRow struct { CustomerID *int32 `json:"customer_id" db:"customer_id"` } -func (r *InsertOrderRow) GetOrderID() int32 { return r.OrderID } - -func (r *InsertOrderRow) GetOrderDate() pgtype.Timestamptz { return r.OrderDate } - -func (r *InsertOrderRow) GetOrderTotal() pgtype.Numeric { return r.OrderTotal } - -func (r *InsertOrderRow) GetCustomerID() *int32 { return r.CustomerID } - // InsertOrder implements Querier.InsertOrder. func (q *DBQuerier) InsertOrder(ctx context.Context, params InsertOrderParams) (InsertOrderRow, error) { ctx = context.WithValue(ctx, QueryName{}, "InsertOrder") diff --git a/example/erp/order/customer.sql_test.go b/example/erp/order/customer.sql_test.go index 4f46e708..82da532d 100644 --- a/example/erp/order/customer.sql_test.go +++ b/example/erp/order/customer.sql_test.go @@ -39,7 +39,7 @@ func TestNewQuerier_FindOrdersByCustomer(t *testing.T) { t.Run("FindOrdersByCustomer", func(t *testing.T) { orders, err := q.FindOrdersByCustomer(t.Context(), cust1.CustomerID) require.NoError(t, err) - assert.Equal(t, []FindOrdersByCustomerRow{ + assert.Equal(t, []OrderRow{ { OrderID: order1.OrderID, OrderDate: order1.OrderDate, diff --git a/example/erp/order/price.sql b/example/erp/order/price.sql index 24f5d6ce..337cc9a8 100644 --- a/example/erp/order/price.sql +++ b/example/erp/order/price.sql @@ -1,4 +1,4 @@ --- name: FindOrdersByPrice :many +-- name: FindOrdersByPrice :many row=Order SELECT * FROM orders WHERE order_total > pggen.arg('MinTotal'); -- name: FindOrdersMRR :many diff --git a/example/erp/order/price.sql.go b/example/erp/order/price.sql.go index 1355598f..88f46350 100644 --- a/example/erp/order/price.sql.go +++ b/example/erp/order/price.sql.go @@ -11,40 +11,18 @@ import ( const findOrdersByPriceSQL = `SELECT * FROM orders WHERE order_total > $1;` -type FindOrdersByPriceProjection interface { - GetOrderID() int32 - GetOrderDate() pgtype.Timestamptz - GetOrderTotal() pgtype.Numeric - GetCustomerID() *int32 -} - -type FindOrdersByPriceRow struct { - OrderID int32 `json:"order_id" db:"order_id"` - OrderDate pgtype.Timestamptz `json:"order_date" db:"order_date"` - OrderTotal pgtype.Numeric `json:"order_total" db:"order_total"` - CustomerID *int32 `json:"customer_id" db:"customer_id"` -} - -func (r *FindOrdersByPriceRow) GetOrderID() int32 { return r.OrderID } - -func (r *FindOrdersByPriceRow) GetOrderDate() pgtype.Timestamptz { return r.OrderDate } - -func (r *FindOrdersByPriceRow) GetOrderTotal() pgtype.Numeric { return r.OrderTotal } - -func (r *FindOrdersByPriceRow) GetCustomerID() *int32 { return r.CustomerID } - // FindOrdersByPrice implements Querier.FindOrdersByPrice. -func (q *DBQuerier) FindOrdersByPrice(ctx context.Context, minTotal pgtype.Numeric) ([]FindOrdersByPriceRow, error) { +func (q *DBQuerier) FindOrdersByPrice(ctx context.Context, minTotal pgtype.Numeric) ([]OrderRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindOrdersByPrice") rows, err := q.conn.Query(ctx, findOrdersByPriceSQL, minTotal) if err != nil { - var zero []FindOrdersByPriceRow + var zero []OrderRow return zero, fmt.Errorf("query FindOrdersByPrice: %w", err) } - result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindOrdersByPriceRow]) + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[OrderRow]) if err != nil { - var zero []FindOrdersByPriceRow + var zero []OrderRow return zero, fmt.Errorf("scan FindOrdersByPrice row: %w", err) } return result, nil @@ -54,20 +32,11 @@ const findOrdersMRRSQL = `SELECT date_trunc('month', order_date) AS month, sum(o FROM orders GROUP BY date_trunc('month', order_date);` -type FindOrdersMRRProjection interface { - GetMonth() pgtype.Timestamptz - GetOrderMRR() pgtype.Numeric -} - type FindOrdersMRRRow struct { Month pgtype.Timestamptz `json:"month" db:"month"` OrderMRR pgtype.Numeric `json:"order_mrr" db:"order_mrr"` } -func (r *FindOrdersMRRRow) GetMonth() pgtype.Timestamptz { return r.Month } - -func (r *FindOrdersMRRRow) GetOrderMRR() pgtype.Numeric { return r.OrderMRR } - // FindOrdersMRR implements Querier.FindOrdersMRR. func (q *DBQuerier) FindOrdersMRR(ctx context.Context) ([]FindOrdersMRRRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindOrdersMRR") diff --git a/example/function/query.sql.go b/example/function/query.sql.go index 1d121729..4d3b3aee 100644 --- a/example/function/query.sql.go +++ b/example/function/query.sql.go @@ -88,20 +88,11 @@ var _ = addTypeToRegister("\"_list_item\"") const outParamsSQL = `SELECT * FROM out_params();` -type OutParamsProjection interface { - GetItems() []ListItem - GetStats() ListStats -} - type OutParamsRow struct { Items []ListItem `json:"_items" db:"_items"` Stats ListStats `json:"_stats" db:"_stats"` } -func (r *OutParamsRow) GetItems() []ListItem { return r.Items } - -func (r *OutParamsRow) GetStats() ListStats { return r.Stats } - // OutParams implements Querier.OutParams. func (q *DBQuerier) OutParams(ctx context.Context) ([]OutParamsRow, error) { ctx = context.WithValue(ctx, QueryName{}, "OutParams") diff --git a/example/inline_param_count/inline0/query.sql.go b/example/inline_param_count/inline0/query.sql.go index e5ea4c6b..61529bf7 100644 --- a/example/inline_param_count/inline0/query.sql.go +++ b/example/inline_param_count/inline0/query.sql.go @@ -103,13 +103,6 @@ type FindAuthorByIDParams struct { AuthorID int32 `json:"AuthorID"` } -type FindAuthorByIDProjection interface { - GetAuthorID() int32 - GetFirstName() string - GetLastName() string - GetSuffix() *string -} - type FindAuthorByIDRow struct { AuthorID int32 `json:"author_id" db:"author_id"` FirstName string `json:"first_name" db:"first_name"` @@ -117,14 +110,6 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix" db:"suffix"` } -func (r *FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } - -func (r *FindAuthorByIDRow) GetFirstName() string { return r.FirstName } - -func (r *FindAuthorByIDRow) GetLastName() string { return r.LastName } - -func (r *FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } - // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, params FindAuthorByIDParams) (FindAuthorByIDRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") diff --git a/example/inline_param_count/inline1/query.sql.go b/example/inline_param_count/inline1/query.sql.go index a947c93f..134befbf 100644 --- a/example/inline_param_count/inline1/query.sql.go +++ b/example/inline_param_count/inline1/query.sql.go @@ -99,13 +99,6 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` -type FindAuthorByIDProjection interface { - GetAuthorID() int32 - GetFirstName() string - GetLastName() string - GetSuffix() *string -} - type FindAuthorByIDRow struct { AuthorID int32 `json:"author_id" db:"author_id"` FirstName string `json:"first_name" db:"first_name"` @@ -113,14 +106,6 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix" db:"suffix"` } -func (r *FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } - -func (r *FindAuthorByIDRow) GetFirstName() string { return r.FirstName } - -func (r *FindAuthorByIDRow) GetLastName() string { return r.LastName } - -func (r *FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } - // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") diff --git a/example/inline_param_count/inline2/query.sql.go b/example/inline_param_count/inline2/query.sql.go index a95a4277..96dd4320 100644 --- a/example/inline_param_count/inline2/query.sql.go +++ b/example/inline_param_count/inline2/query.sql.go @@ -99,13 +99,6 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` -type FindAuthorByIDProjection interface { - GetAuthorID() int32 - GetFirstName() string - GetLastName() string - GetSuffix() *string -} - type FindAuthorByIDRow struct { AuthorID int32 `json:"author_id" db:"author_id"` FirstName string `json:"first_name" db:"first_name"` @@ -113,14 +106,6 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix" db:"suffix"` } -func (r *FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } - -func (r *FindAuthorByIDRow) GetFirstName() string { return r.FirstName } - -func (r *FindAuthorByIDRow) GetLastName() string { return r.LastName } - -func (r *FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } - // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") diff --git a/example/inline_param_count/inline3/query.sql.go b/example/inline_param_count/inline3/query.sql.go index c45f24e8..8ef8b669 100644 --- a/example/inline_param_count/inline3/query.sql.go +++ b/example/inline_param_count/inline3/query.sql.go @@ -99,13 +99,6 @@ func (q *DBQuerier) CountAuthors(ctx context.Context) (*int, error) { const findAuthorByIDSQL = `SELECT * FROM author WHERE author_id = $1;` -type FindAuthorByIDProjection interface { - GetAuthorID() int32 - GetFirstName() string - GetLastName() string - GetSuffix() *string -} - type FindAuthorByIDRow struct { AuthorID int32 `json:"author_id" db:"author_id"` FirstName string `json:"first_name" db:"first_name"` @@ -113,14 +106,6 @@ type FindAuthorByIDRow struct { Suffix *string `json:"suffix" db:"suffix"` } -func (r *FindAuthorByIDRow) GetAuthorID() int32 { return r.AuthorID } - -func (r *FindAuthorByIDRow) GetFirstName() string { return r.FirstName } - -func (r *FindAuthorByIDRow) GetLastName() string { return r.LastName } - -func (r *FindAuthorByIDRow) GetSuffix() *string { return r.Suffix } - // FindAuthorByID implements Querier.FindAuthorByID. func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (FindAuthorByIDRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindAuthorByID") diff --git a/example/ltree/query.sql.go b/example/ltree/query.sql.go index 82be8213..323cab16 100644 --- a/example/ltree/query.sql.go +++ b/example/ltree/query.sql.go @@ -153,20 +153,11 @@ const findLtreeInputSQL = `SELECT -- that we need a text array that Postgres then converts to ltree[]. ($2::text[])::ltree[] AS text_arr;` -type FindLtreeInputProjection interface { - GetLtree() pgtype.Text - GetTextArr() pgtype.Array[pgtype.Text] -} - type FindLtreeInputRow struct { Ltree pgtype.Text `json:"ltree" db:"ltree"` TextArr pgtype.Array[pgtype.Text] `json:"text_arr" db:"text_arr"` } -func (r *FindLtreeInputRow) GetLtree() pgtype.Text { return r.Ltree } - -func (r *FindLtreeInputRow) GetTextArr() pgtype.Array[pgtype.Text] { return r.TextArr } - // FindLtreeInput implements Querier.FindLtreeInput. func (q *DBQuerier) FindLtreeInput(ctx context.Context, inLtree pgtype.Text, inLtreeArray []string) (FindLtreeInputRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindLtreeInput") diff --git a/example/pgcrypto/query.sql.go b/example/pgcrypto/query.sql.go index 70ecf64e..8432a28f 100644 --- a/example/pgcrypto/query.sql.go +++ b/example/pgcrypto/query.sql.go @@ -86,20 +86,11 @@ func (q *DBQuerier) CreateUser(ctx context.Context, email string, password strin const findUserSQL = `SELECT email, pass from "user" where email = $1;` -type FindUserProjection interface { - GetEmail() string - GetPass() string -} - type FindUserRow struct { Email string `json:"email" db:"email"` Pass string `json:"pass" db:"pass"` } -func (r *FindUserRow) GetEmail() string { return r.Email } - -func (r *FindUserRow) GetPass() string { return r.Pass } - // FindUser implements Querier.FindUser. func (q *DBQuerier) FindUser(ctx context.Context, email string) (FindUserRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindUser") diff --git a/example/syntax/query.sql.go b/example/syntax/query.sql.go index 2b26e20c..18702757 100644 --- a/example/syntax/query.sql.go +++ b/example/syntax/query.sql.go @@ -203,20 +203,11 @@ func (q *DBQuerier) BacktickBackslashN(ctx context.Context) (string, error) { const illegalNameSymbolsSQL = "SELECT '`\\n' as \"$\", $1 as \"foo.bar!@#$%&*()\"\"--+\";" -type IllegalNameSymbolsProjection interface { - GetUnnamedColumn0() string - GetFooBar() string -} - type IllegalNameSymbolsRow struct { UnnamedColumn0 string `json:"$" db:"$"` FooBar string `json:"foo.bar!@#$%&*()\"--+" db:"foo.bar!@#$%&*()\"--+"` } -func (r *IllegalNameSymbolsRow) GetUnnamedColumn0() string { return r.UnnamedColumn0 } - -func (r *IllegalNameSymbolsRow) GetFooBar() string { return r.FooBar } - // IllegalNameSymbols implements Querier.IllegalNameSymbols. func (q *DBQuerier) IllegalNameSymbols(ctx context.Context, helloWorld string) (IllegalNameSymbolsRow, error) { ctx = context.WithValue(ctx, QueryName{}, "IllegalNameSymbols") diff --git a/example/void/query.sql.go b/example/void/query.sql.go index c1ed8636..05939016 100644 --- a/example/void/query.sql.go +++ b/example/void/query.sql.go @@ -127,20 +127,11 @@ func (q *DBQuerier) VoidTwo(ctx context.Context) (string, error) { const voidThreeSQL = `SELECT void_fn(), 'foo' as foo, 'bar' as bar;` -type VoidThreeProjection interface { - GetFoo() string - GetBar() string -} - type VoidThreeRow struct { Foo string `json:"foo" db:"foo"` Bar string `json:"bar" db:"bar"` } -func (r *VoidThreeRow) GetFoo() string { return r.Foo } - -func (r *VoidThreeRow) GetBar() string { return r.Bar } - // VoidThree implements Querier.VoidThree. func (q *DBQuerier) VoidThree(ctx context.Context) (VoidThreeRow, error) { ctx = context.WithValue(ctx, QueryName{}, "VoidThree") diff --git a/generate_test.go b/generate_test.go index a35acea3..709a3266 100644 --- a/generate_test.go +++ b/generate_test.go @@ -37,6 +37,17 @@ func TestGenerate_Golang_Error(t *testing.T) { `), wantErrMsg: `function encode(integer, text) does not exist`, }, + { + name: "incompatible shared row shape", + schema: "", + queries: texts.Dedent(` + -- name: Foo :many row=Shared + SELECT 1::int4 AS id, 'foo'::text AS name; + -- name: Bar :many row=Shared + SELECT 1::int4 AS id, 2::int4 AS name; + `), + wantErrMsg: `row=Shared used by incompatible queries Foo and Bar`, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 8d596ce3..351f3c2c 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -77,6 +77,7 @@ const ( // Pragmas are options to control generated code for a single query. type Pragmas struct { ProtobufType string // package qualified protocol buffer message type to use for output rows + RowType string // shared output row struct name prefix, emitted as Row } // An query is represented by one of the following query nodes. diff --git a/internal/codegen/golang/query.gotemplate b/internal/codegen/golang/query.gotemplate index 7ff478c2..c8112f13 100644 --- a/internal/codegen/golang/query.gotemplate +++ b/internal/codegen/golang/query.gotemplate @@ -51,9 +51,7 @@ func NewQuerier(conn genericConn) *DBQuerier { {{- "\n\n" -}} const {{ $q.SQLVarName }} = {{ $q.EmitPreparedSQL }} {{- $q.EmitParamStruct -}} -{{- $q.EmitProjectionInterface -}} {{- $q.EmitRowStruct -}} -{{- $q.EmitRowGetterMethods -}} {{- "\n\n" -}} // {{ $q.Name }} implements Querier.{{ $q.Name }}. func (q *DBQuerier) {{ $q.Name }}(ctx context.Context {{- $q.EmitParams }}) ({{ $q.EmitResultType }}, error) { diff --git a/internal/codegen/golang/templated_file.go b/internal/codegen/golang/templated_file.go index b8b8329c..d77a9b71 100644 --- a/internal/codegen/golang/templated_file.go +++ b/internal/codegen/golang/templated_file.go @@ -36,6 +36,9 @@ type TemplatedFile struct { // codegen template. type TemplatedQuery struct { Name string // name of the query, from the comment preceding the query + RowType string // shared output row name prefix, without the Row suffix + RowStructType string // output row struct type for multi-column results + ShouldEmitRow bool // true if this query owns its row struct declaration SQLVarName string // name of the string variable containing the SQL ResultKind ast.ResultKind // kind of result: :one, :many, or :exec Doc string // doc from the source query file, formatted for Go @@ -55,12 +58,12 @@ type TemplatedParam struct { } type TemplatedColumn struct { - PgName string // original name of the Postgres column - UpperName string // name in Go-style (UpperCamelCase) to use for the column - LowerName string // name in Go-style (lowerCamelCase) - GetterName string // name of the projection getter method - Type gotype.Type - QualType string // package qualified Go type to use for the column, like "pgtype.Text" + PgName string // original name of the Postgres column + UpperName string // name in Go-style (UpperCamelCase) to use for the column + LowerName string // name in Go-style (lowerCamelCase) + Type gotype.Type + QualType string // package qualified Go type to use for the column, like "pgtype.Text" + Nullable bool } func (tf TemplatedFile) needsPgconnImport() bool { @@ -349,7 +352,10 @@ func (tq TemplatedQuery) EmitSingularResultType() string { if len(tq.Outputs) == 1 { return tq.Outputs[0].QualType } - return tq.Name + "Row" + if tq.RowStructType == "" { + return tq.Name + "Row" + } + return tq.RowStructType } // EmitResultType returns the string representing the overall query result type, @@ -424,7 +430,7 @@ func (tq TemplatedQuery) EmitZeroResult() (string, error) { return "nil", nil // empty slice case ast.ResultKindOne: if len(tq.Outputs) > 1 { - return tq.Name + "Row{}", nil + return tq.EmitSingularResultType() + "{}", nil } typ := tq.Outputs[0].Type.BaseName() switch { @@ -503,31 +509,6 @@ func getLongestOutput(outs []TemplatedColumn) (int, int) { return nameLen, typeLen } -func (tq TemplatedQuery) hasProjection() bool { - return tq.ResultKind != ast.ResultKindExec && len(tq.Outputs) > 1 -} - -// EmitProjectionInterface writes the interface implemented by a multi-column -// output row. -func (tq TemplatedQuery) EmitProjectionInterface() string { - if !tq.hasProjection() { - return "" - } - sb := &strings.Builder{} - sb.WriteString("\n\ntype ") - sb.WriteString(tq.Name) - sb.WriteString("Projection interface {\n") - for _, out := range tq.Outputs { - sb.WriteString("\t") - sb.WriteString(out.GetterName) - sb.WriteString("() ") - sb.WriteString(out.QualType) - sb.WriteRune('\n') - } - sb.WriteString("}") - return sb.String() -} - // EmitRowStruct writes the struct definition for query output row if one is // needed. func (tq TemplatedQuery) EmitRowStruct() string { @@ -538,10 +519,17 @@ func (tq TemplatedQuery) EmitRowStruct() string { if len(tq.Outputs) == 1 { return "" // if there's only 1 output column, return it directly } + if tq.RowStructType != "" && !tq.ShouldEmitRow { + return "" + } + rowStructType := tq.RowStructType + if rowStructType == "" { + rowStructType = tq.Name + "Row" + } sb := &strings.Builder{} sb.WriteString("\n\ntype ") - sb.WriteString(tq.Name) - sb.WriteString("Row struct {\n") + sb.WriteString(rowStructType) + sb.WriteString(" struct {\n") maxNameLen, maxTypeLen := getLongestOutput(tq.Outputs) for _, out := range tq.Outputs { // Name @@ -565,24 +553,3 @@ func (tq TemplatedQuery) EmitRowStruct() string { panic("unhandled result type: " + tq.ResultKind) } } - -// EmitRowGetterMethods writes projection getter methods for a multi-column -// output row. -func (tq TemplatedQuery) EmitRowGetterMethods() string { - if !tq.hasProjection() { - return "" - } - sb := &strings.Builder{} - for _, out := range tq.Outputs { - sb.WriteString("\n\nfunc (r *") - sb.WriteString(tq.Name) - sb.WriteString("Row) ") - sb.WriteString(out.GetterName) - sb.WriteString("() ") - sb.WriteString(out.QualType) - sb.WriteString(" { return r.") - sb.WriteString(out.UpperName) - sb.WriteString(" }") - } - return sb.String() -} diff --git a/internal/codegen/golang/templated_file_test.go b/internal/codegen/golang/templated_file_test.go index f4185798..f88db4cf 100644 --- a/internal/codegen/golang/templated_file_test.go +++ b/internal/codegen/golang/templated_file_test.go @@ -5,70 +5,86 @@ import ( "github.com/meoyawn/pggen/internal/ast" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestResolveProjectionGetterNames(t *testing.T) { - cols := resolveProjectionGetterNames([]TemplatedColumn{ - {UpperName: "ID"}, - {UpperName: "GetID"}, - {UpperName: "Foo"}, - {UpperName: "FooValue"}, - {UpperName: "GetFoo"}, - {UpperName: "GetFooValue"}, - {UpperName: "Bar"}, - {UpperName: "Bar"}, - }) - - got := make([]string, 0, len(cols)) - for _, col := range cols { - got = append(got, col.GetterName) - } - - assert.Equal(t, []string{ - "GetIDValue", - "GetGetID", - "GetFooValue2", - "GetFooValueValue", - "GetGetFoo", - "GetGetFooValue", - "GetBar", - "GetBarValue", - }, got) -} - -func TestTemplatedQuery_EmitProjection(t *testing.T) { +func TestTemplatedQuery_EmitRowStructUsesSharedRowType(t *testing.T) { query := TemplatedQuery{ - Name: "FindAuthors", - ResultKind: ast.ResultKindMany, + Name: "FindAuthors", + RowStructType: "AuthorRow", + ShouldEmitRow: true, + ResultKind: ast.ResultKindMany, Outputs: []TemplatedColumn{ - {UpperName: "AuthorID", GetterName: "GetAuthorID", QualType: "int"}, - {UpperName: "Name", GetterName: "GetName", QualType: "string"}, + {PgName: "author_id", UpperName: "AuthorID", QualType: "int"}, + {PgName: "name", UpperName: "Name", QualType: "string"}, }, } - assert.Equal(t, ` - -type FindAuthorsProjection interface { - GetAuthorID() int - GetName() string -}`, query.EmitProjectionInterface()) - - assert.Equal(t, ` + assert.Contains(t, query.EmitRowStruct(), "type AuthorRow struct {") +} -func (r *FindAuthorsRow) GetAuthorID() int { return r.AuthorID } +func TestAssignSharedRowStructs(t *testing.T) { + files := []TemplatedFile{ + { + Queries: []TemplatedQuery{ + { + Name: "FindAuthorByID", + RowType: "Author", + RowStructType: "FindAuthorByIDRow", + ShouldEmitRow: true, + Outputs: []TemplatedColumn{ + {PgName: "author_id", UpperName: "AuthorID", QualType: "int32"}, + {PgName: "name", UpperName: "Name", QualType: "string"}, + }, + }, + { + Name: "FindAuthors", + RowType: "Author", + RowStructType: "FindAuthorsRow", + ShouldEmitRow: true, + Outputs: []TemplatedColumn{ + {PgName: "author_id", UpperName: "AuthorID", QualType: "int32"}, + {PgName: "name", UpperName: "Name", QualType: "string"}, + }, + }, + }, + }, + } -func (r *FindAuthorsRow) GetName() string { return r.Name }`, query.EmitRowGetterMethods()) + require.NoError(t, assignSharedRowStructs(files)) + assert.Equal(t, "AuthorRow", files[0].Queries[0].RowStructType) + assert.True(t, files[0].Queries[0].ShouldEmitRow) + assert.Equal(t, "AuthorRow", files[0].Queries[1].RowStructType) + assert.False(t, files[0].Queries[1].ShouldEmitRow) } -func TestTemplatedQuery_EmitProjectionSkipsSingleColumn(t *testing.T) { - query := TemplatedQuery{ - Name: "FindAuthorID", - ResultKind: ast.ResultKindMany, - Outputs: []TemplatedColumn{ - {UpperName: "AuthorID", GetterName: "GetAuthorID", QualType: "int"}, +func TestAssignSharedRowStructsRejectsIncompatibleShapes(t *testing.T) { + files := []TemplatedFile{ + { + Queries: []TemplatedQuery{ + { + Name: "FindAuthorByID", + RowType: "Author", + Outputs: []TemplatedColumn{ + {PgName: "author_id", UpperName: "AuthorID", QualType: "int32"}, + {PgName: "name", UpperName: "Name", QualType: "string"}, + }, + }, + { + Name: "FindAuthors", + RowType: "Author", + Outputs: []TemplatedColumn{ + {PgName: "author_id", UpperName: "AuthorID", QualType: "int32"}, + {PgName: "name", UpperName: "Name", QualType: "*string", Nullable: true}, + }, + }, + }, }, } - assert.Empty(t, query.EmitProjectionInterface()) - assert.Empty(t, query.EmitRowGetterMethods()) + err := assignSharedRowStructs(files) + require.Error(t, err) + assert.Contains(t, err.Error(), "row=Author used by incompatible queries FindAuthorByID and FindAuthors") + assert.Contains(t, err.Error(), `pg="name" field=Name type=string nullable=false`) + assert.Contains(t, err.Error(), `pg="name" field=Name type=*string nullable=true`) } diff --git a/internal/codegen/golang/templater.go b/internal/codegen/golang/templater.go index 674b9587..5657f436 100644 --- a/internal/codegen/golang/templater.go +++ b/internal/codegen/golang/templater.go @@ -63,6 +63,9 @@ func (tm Templater) TemplateAll(files []codegen.QueryFile) ([]TemplatedFile, err goQueryFiles = append(goQueryFiles, goFile) allDeclarers.AddAll(decls.ListAll()...) } + if err := assignSharedRowStructs(goQueryFiles); err != nil { + return nil, err + } // Add declarers to leader file. goQueryFiles[firstIndex].Declarers = allDeclarers.ListAll() @@ -179,22 +182,36 @@ func (tm Templater) templateFile(file codegen.QueryFile, isLeader bool) (Templat LowerName: tm.chooseLowerName(out.PgName, "UnnamedColumn", i, len(query.Outputs)), Type: goType, QualType: gotype.QualifyType(goType, pkgPath), + Nullable: out.Nullable, } ds := FindOutputDeclarers(goType).ListAll() declarers.AddAll(ds...) } nonVoidCols := removeVoidColumns(outputs) - nonVoidCols = resolveProjectionGetterNames(nonVoidCols) resultKind := query.ResultKind if len(nonVoidCols) == 0 { resultKind = ast.ResultKindExec } + rowType := "" + if query.RowType != "" { + rowType = tm.caser.ToUpperGoIdent(query.RowType) + if rowType == "" { + return TemplatedFile{}, nil, fmt.Errorf("query %s has invalid row pragma %q", query.Name, query.RowType) + } + if resultKind == ast.ResultKindExec { + return TemplatedFile{}, nil, fmt.Errorf("query %s uses row=%s with %s; row is only supported for result queries", query.Name, query.RowType, query.ResultKind) + } + } if resultKind != ast.ResultKindExec { imports.AddPackage("github.com/jackc/pgx/v5") } + name := tm.caser.ToUpperGoIdent(query.Name) queries = append(queries, TemplatedQuery{ - Name: tm.caser.ToUpperGoIdent(query.Name), + Name: name, + RowType: rowType, + RowStructType: name + "Row", + ShouldEmitRow: true, SQLVarName: tm.caser.ToLowerGoIdent(query.Name) + "SQL", ResultKind: resultKind, Doc: docs.String(), @@ -258,27 +275,74 @@ func removeVoidColumns(cols []TemplatedColumn) []TemplatedColumn { return outs } -func resolveProjectionGetterNames(cols []TemplatedColumn) []TemplatedColumn { - used := make(map[string]struct{}, len(cols)*2) - for _, col := range cols { - used[col.UpperName] = struct{}{} - } - for i, col := range cols { - name := "Get" + col.UpperName - if _, ok := used[name]; ok { - base := name + "Value" - name = base - for suffix := 2; hasIdentifier(used, name); suffix++ { - name = base + strconv.Itoa(suffix) +type sharedRowShape struct { + QueryName string + Columns []sharedRowColumn +} + +type sharedRowColumn struct { + PgName string + UpperName string + QualType string + Nullable bool +} + +func assignSharedRowStructs(files []TemplatedFile) error { + seen := make(map[string]sharedRowShape) + for fileIdx := range files { + for queryIdx := range files[fileIdx].Queries { + query := &files[fileIdx].Queries[queryIdx] + if query.RowType == "" || len(query.Outputs) <= 1 { + continue + } + + query.RowStructType = query.RowType + "Row" + shape := makeSharedRowShape(*query) + prev, ok := seen[query.RowType] + if !ok { + seen[query.RowType] = shape + continue } + if diff := compareSharedRowShape(prev, shape); diff != "" { + return fmt.Errorf("row=%s used by incompatible queries %s and %s: %s", query.RowType, prev.QueryName, query.Name, diff) + } + query.ShouldEmitRow = false + } + } + return nil +} + +func makeSharedRowShape(query TemplatedQuery) sharedRowShape { + cols := make([]sharedRowColumn, len(query.Outputs)) + for i, col := range query.Outputs { + cols[i] = sharedRowColumn{ + PgName: col.PgName, + UpperName: col.UpperName, + QualType: col.QualType, + Nullable: col.Nullable, + } + } + return sharedRowShape{ + QueryName: query.Name, + Columns: cols, + } +} + +func compareSharedRowShape(want sharedRowShape, got sharedRowShape) string { + if len(want.Columns) != len(got.Columns) { + return fmt.Sprintf("column count differs: %s has %d columns, %s has %d columns", + want.QueryName, len(want.Columns), got.QueryName, len(got.Columns)) + } + for i := range want.Columns { + if want.Columns[i] == got.Columns[i] { + continue } - cols[i].GetterName = name - used[name] = struct{}{} + return fmt.Sprintf("column %d differs: %s has %s, %s has %s", + i+1, want.QueryName, describeSharedRowColumn(want.Columns[i]), got.QueryName, describeSharedRowColumn(got.Columns[i])) } - return cols + return "" } -func hasIdentifier(used map[string]struct{}, name string) bool { - _, ok := used[name] - return ok +func describeSharedRowColumn(col sharedRowColumn) string { + return fmt.Sprintf("pg=%q field=%s type=%s nullable=%t", col.PgName, col.UpperName, col.QualType, col.Nullable) } diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 15deb4ca..6cf03a37 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -296,8 +296,13 @@ func parsePragmas(allPragmas string) (ast.Pragmas, error) { return ast.Pragmas{}, err } qp.ProtobufType = p + case "row": + if val == "" { + return ast.Pragmas{}, fmt.Errorf("row pragma must not be empty") + } + qp.RowType = val default: - return ast.Pragmas{}, fmt.Errorf("unsupported pramga %q", key) + return ast.Pragmas{}, fmt.Errorf("unsupported pragma %q", key) } } return qp, nil diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go index 75a382bd..e2520aba 100644 --- a/internal/parser/parser_test.go +++ b/internal/parser/parser_test.go @@ -121,6 +121,18 @@ func TestParseFile_Queries(t *testing.T) { Pragmas: ast.Pragmas{ProtobufType: "Bar"}, }, }, + { + "-- name: Qux :many row=Shared\nSELECT 1;", + &ast.SourceQuery{ + Name: "Qux", + Doc: &ast.CommentGroup{List: []*ast.LineComment{{Text: "-- name: Qux :many row=Shared"}}}, + SourceSQL: "SELECT 1;", + PreparedSQL: "SELECT 1;", + ParamNames: nil, + ResultKind: ast.ResultKindMany, + Pragmas: ast.Pragmas{RowType: "Shared"}, + }, + }, } for _, tt := range tests { diff --git a/internal/pg/query.sql.go b/internal/pg/query.sql.go index f1729009..8eaa9f19 100644 --- a/internal/pg/query.sql.go +++ b/internal/pg/query.sql.go @@ -129,16 +129,6 @@ WHERE typ.typisdefined AND typ.typtype = 'e' AND typ.oid = ANY ($1::oid[]);` -type FindEnumTypesProjection interface { - GetOID() uint32 - GetTypeName() string - GetChildOIDs() []int - GetOrders() []float32 - GetLabels() []string - GetTypeKind() byte - GetDefaultExpr() string -} - type FindEnumTypesRow struct { OID uint32 `json:"oid" db:"oid"` TypeName string `json:"type_name" db:"type_name"` @@ -149,20 +139,6 @@ type FindEnumTypesRow struct { DefaultExpr string `json:"default_expr" db:"default_expr"` } -func (r *FindEnumTypesRow) GetOID() uint32 { return r.OID } - -func (r *FindEnumTypesRow) GetTypeName() string { return r.TypeName } - -func (r *FindEnumTypesRow) GetChildOIDs() []int { return r.ChildOIDs } - -func (r *FindEnumTypesRow) GetOrders() []float32 { return r.Orders } - -func (r *FindEnumTypesRow) GetLabels() []string { return r.Labels } - -func (r *FindEnumTypesRow) GetTypeKind() byte { return r.TypeKind } - -func (r *FindEnumTypesRow) GetDefaultExpr() string { return r.DefaultExpr } - // FindEnumTypes implements Querier.FindEnumTypes. func (q *DBQuerier) FindEnumTypes(ctx context.Context, oids []uint32) ([]FindEnumTypesRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindEnumTypes") @@ -209,13 +185,6 @@ WHERE arr_typ.typisdefined AND arr_typ.typlen = -1 AND arr_typ.oid = ANY ($1::oid[]);` -type FindArrayTypesProjection interface { - GetOID() uint32 - GetTypeName() string - GetElemOID() uint32 - GetTypeKind() byte -} - type FindArrayTypesRow struct { OID uint32 `json:"oid" db:"oid"` TypeName string `json:"type_name" db:"type_name"` @@ -223,14 +192,6 @@ type FindArrayTypesRow struct { TypeKind byte `json:"type_kind" db:"type_kind"` } -func (r *FindArrayTypesRow) GetOID() uint32 { return r.OID } - -func (r *FindArrayTypesRow) GetTypeName() string { return r.TypeName } - -func (r *FindArrayTypesRow) GetElemOID() uint32 { return r.ElemOID } - -func (r *FindArrayTypesRow) GetTypeKind() byte { return r.TypeKind } - // FindArrayTypes implements Querier.FindArrayTypes. func (q *DBQuerier) FindArrayTypes(ctx context.Context, oids []uint32) ([]FindArrayTypesRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindArrayTypes") @@ -278,17 +239,6 @@ FROM pg_type typ WHERE typ.oid = ANY ($1::oid[]) AND typ.typtype = 'c';` -type FindCompositeTypesProjection interface { - GetTableTypeName() string - GetTableTypeOID() uint32 - GetTableName() string - GetColNames() []string - GetColOIDs() []int - GetColOrders() []int - GetColNotNulls() []bool - GetColTypeNames() []string -} - type FindCompositeTypesRow struct { TableTypeName string `json:"table_type_name" db:"table_type_name"` TableTypeOID uint32 `json:"table_type_oid" db:"table_type_oid"` @@ -300,22 +250,6 @@ type FindCompositeTypesRow struct { ColTypeNames []string `json:"col_type_names" db:"col_type_names"` } -func (r *FindCompositeTypesRow) GetTableTypeName() string { return r.TableTypeName } - -func (r *FindCompositeTypesRow) GetTableTypeOID() uint32 { return r.TableTypeOID } - -func (r *FindCompositeTypesRow) GetTableName() string { return r.TableName } - -func (r *FindCompositeTypesRow) GetColNames() []string { return r.ColNames } - -func (r *FindCompositeTypesRow) GetColOIDs() []int { return r.ColOIDs } - -func (r *FindCompositeTypesRow) GetColOrders() []int { return r.ColOrders } - -func (r *FindCompositeTypesRow) GetColNotNulls() []bool { return r.ColNotNulls } - -func (r *FindCompositeTypesRow) GetColTypeNames() []string { return r.ColTypeNames } - // FindCompositeTypes implements Querier.FindCompositeTypes. func (q *DBQuerier) FindCompositeTypes(ctx context.Context, oids []uint32) ([]FindCompositeTypesRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindCompositeTypes") @@ -426,24 +360,12 @@ const findOIDNamesSQL = `SELECT oid, typname AS name, typtype AS kind FROM pg_type WHERE oid = ANY ($1::oid[]);` -type FindOIDNamesProjection interface { - GetOID() uint32 - GetName() string - GetKind() byte -} - type FindOIDNamesRow struct { OID uint32 `json:"oid" db:"oid"` Name string `json:"name" db:"name"` Kind byte `json:"kind" db:"kind"` } -func (r *FindOIDNamesRow) GetOID() uint32 { return r.OID } - -func (r *FindOIDNamesRow) GetName() string { return r.Name } - -func (r *FindOIDNamesRow) GetKind() byte { return r.Kind } - // FindOIDNames implements Querier.FindOIDNames. func (q *DBQuerier) FindOIDNames(ctx context.Context, oid []uint32) ([]FindOIDNamesRow, error) { ctx = context.WithValue(ctx, QueryName{}, "FindOIDNames") diff --git a/internal/pginfer/pginfer.go b/internal/pginfer/pginfer.go index d88cbf91..42d9c050 100644 --- a/internal/pginfer/pginfer.go +++ b/internal/pginfer/pginfer.go @@ -36,6 +36,9 @@ type TypedQuery struct { // Qualified protocol buffer message type to use for each output row, like // "erp.api.Product". If empty, generate our own Row type. ProtobufType string + // Shared row struct name prefix, emitted as Row. If empty, generate + // query-specific row types when needed. + RowType string } // InputParam is an input parameter for a prepared query. @@ -99,6 +102,7 @@ func (inf *Inferrer) InferTypes(query *ast.SourceQuery) (TypedQuery, error) { Inputs: inputs, Outputs: outputs, ProtobufType: query.Pragmas.ProtobufType, + RowType: query.Pragmas.RowType, }, nil } From d7606d8b1b273ef3d2dd113b6fc59c52e03b53d6 Mon Sep 17 00:00:00 2001 From: adelnizamutdinov Date: Thu, 18 Jun 2026 17:27:02 +0300 Subject: [PATCH 24/27] Add stream query support Fixes https://github.com/meoyawn/pggen/issues/2 --- README.md | 40 ++++++++++++++-- example/author/codegen_test.go | 9 ++++ example/author/query.sql | 4 ++ example/author/query.sql.go | 29 ++++++++++++ example/author/query.sql_test.go | 49 +++++++++++++++++++- example/go_pointer_types/query.sql | 4 ++ example/go_pointer_types/query.sql.go | 29 ++++++++++++ example/go_pointer_types/query.sql_test.go | 14 ++++++ example/void/query.sql | 3 ++ example/void/query.sql.go | 28 ++++++++++++ example/void/query.sql_test.go | 12 +++++ internal/ast/ast.go | 7 +-- internal/codegen/golang/query.gotemplate | 44 +++++++++++++++--- internal/codegen/golang/templated_file.go | 53 ++++++++++++++++++++-- internal/parser/parser.go | 2 +- internal/parser/parser_test.go | 12 +++++ internal/pginfer/pginfer.go | 2 +- 17 files changed, 320 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 6b444dd8..1a1ccb32 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,9 @@ have to worry about SQL injection attacks. How to use pggen in three steps: -1. Write arbitrarily complex SQL queries with a name and a `:one`, `:many`, or - `:exec` annotation. Declare inputs with `pggen.arg('input_name')`. +1. Write arbitrarily complex SQL queries with a name and a `:one`, `:many`, + `:stream`, or `:exec` annotation. Declare inputs with + `pggen.arg('input_name')`. ```sql -- name: SearchScreenshots :many @@ -64,6 +65,14 @@ How to use pggen in three steps: ) ([]SearchScreenshotsRow, error) { /* omitted */ } + + func (q *DBQuerier) StreamScreenshots( + ctx context.Context, + params StreamScreenshotsParams, + yield func(StreamScreenshotsRow) error, + ) error { + /* omitted */ + } ``` 3. Use the generated code. @@ -474,13 +483,18 @@ CREATE TABLE author ( ``` First, write a query in the file `author/query.sql`. The query name is -`FindAuthors` and the query returns `:many` rows. A query can return `:many` -rows, `:one` row, or `:exec` for update, insert, and delete queries. +`FindAuthors` and the query returns `:many` rows. A query can return `:many` +rows, `:one` row, `:stream` rows through a callback, or `:exec` for update, +insert, and delete queries. ```sql -- FindAuthors finds authors by first name. -- name: FindAuthors :many SELECT * FROM author WHERE first_name = pggen.arg('first_name'); + +-- StreamAuthors streams authors by first name. +-- name: StreamAuthors :stream +SELECT * FROM author WHERE first_name = pggen.arg('first_name'); ``` Second, use pggen to generate Go code to `author/query.sql.go`: @@ -500,6 +514,9 @@ We'll walk through the generated file `author/query.sql.go`: type Querier interface { // FindAuthors finds authors by first name. FindAuthors(ctx context.Context, firstName string) ([]FindAuthorsRow, error) + + // StreamAuthors streams authors by first name. + StreamAuthors(ctx context.Context, firstName string, yield func(StreamAuthorsRow) error) error } ``` @@ -605,6 +622,21 @@ We'll walk through the generated file `author/query.sql.go`: } return result, nil } + + // StreamAuthors implements Querier.StreamAuthors. + func (q *DBQuerier) StreamAuthors(ctx context.Context, firstName string, yield func(StreamAuthorsRow) error) error { + ctx = context.WithValue(ctx, QueryName{}, "StreamAuthors") + rows, err := q.conn.Query(ctx, streamAuthorsSQL, firstName) + if err != nil { + return fmt.Errorf("query StreamAuthors: %w", err) + } + + var item StreamAuthorsRow + if err := forEachRow(rows, []any{&item.AuthorID, &item.FirstName, &item.LastName, &item.Suffix}, &item, yield); err != nil { + return fmt.Errorf("stream StreamAuthors row: %w", err) + } + return nil + } ``` [`*pgx.Conn`]: https://pkg.go.dev/github.com/jackc/pgx/v5#Conn diff --git a/example/author/codegen_test.go b/example/author/codegen_test.go index 4a2f8205..ffc4299d 100644 --- a/example/author/codegen_test.go +++ b/example/author/codegen_test.go @@ -59,6 +59,10 @@ SELECT * FROM author WHERE author_id = pggen.arg('AuthorID'); -- FindAuthors finds authors by first name. -- name: FindAuthors :many row=Author SELECT * FROM author WHERE first_name = pggen.arg('FirstName'); + +-- StreamAuthors streams authors by first name. +-- name: StreamAuthors :stream row=Author +SELECT * FROM author WHERE first_name = pggen.arg('FirstName'); `), 0o600) if err != nil { t.Fatalf("write query file: %s", err) @@ -88,8 +92,13 @@ SELECT * FROM author WHERE first_name = pggen.arg('FirstName'); assert.Contains(t, got, "type AuthorRow struct {") assert.Contains(t, got, "FindAuthorByID(ctx context.Context, authorID int32) (AuthorRow, error)") assert.Contains(t, got, "FindAuthors(ctx context.Context, firstName string) ([]AuthorRow, error)") + assert.Contains(t, got, "StreamAuthors(ctx context.Context, firstName string, yield func(AuthorRow) error) error") assert.Contains(t, got, "pgx.RowToStructByName[AuthorRow]") + assert.Contains(t, got, "forEachRow(rows, func(item *AuthorRow) []any {") + assert.Contains(t, got, "return []any{&item.AuthorID, &item.FirstName, &item.LastName, &item.Suffix}") + assert.Contains(t, got, "pgx.ForEachRow(rows, scanTargets(&item), func() error") assert.Equal(t, 1, strings.Count(got, "type AuthorRow struct {")) assert.NotContains(t, got, "type FindAuthorByIDRow struct {") assert.NotContains(t, got, "type FindAuthorsRow struct {") + assert.NotContains(t, got, "type StreamAuthorsRow struct {") } diff --git a/example/author/query.sql b/example/author/query.sql index 4657e46e..8fd784d6 100644 --- a/example/author/query.sql +++ b/example/author/query.sql @@ -6,6 +6,10 @@ SELECT * FROM author WHERE author_id = pggen.arg('AuthorID'); -- name: FindAuthors :many row=Author SELECT * FROM author WHERE first_name = pggen.arg('FirstName'); +-- StreamAuthors streams authors by first name. +-- name: StreamAuthors :stream row=Author +SELECT * FROM author WHERE first_name = pggen.arg('FirstName'); + -- FindAuthorNames finds one (or zero) authors by ID. -- name: FindAuthorNames :many SELECT first_name, last_name FROM author ORDER BY author_id = pggen.arg('AuthorID'); diff --git a/example/author/query.sql.go b/example/author/query.sql.go index 4937924c..454675f2 100644 --- a/example/author/query.sql.go +++ b/example/author/query.sql.go @@ -19,6 +19,9 @@ type Querier interface { // FindAuthors finds authors by first name. FindAuthors(ctx context.Context, firstName string) ([]AuthorRow, error) + // StreamAuthors streams authors by first name. + StreamAuthors(ctx context.Context, firstName string, yield func(AuthorRow) error) error + // FindAuthorNames finds one (or zero) authors by ID. FindAuthorNames(ctx context.Context, authorID int32) ([]FindAuthorNamesRow, error) @@ -67,6 +70,14 @@ func NewQuerier(conn genericConn) *DBQuerier { return &DBQuerier{conn: conn} } +func forEachRow[T any](rows pgx.Rows, scanTargets func(*T) []any, yield func(T) error) error { + var item T + _, err := pgx.ForEachRow(rows, scanTargets(&item), func() error { + return yield(item) + }) + return err +} + // RegisterTypes loads custom PostgreSQL types into conn's pgx type map. func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { pending := append([]string(nil), typesToRegister...) @@ -146,6 +157,24 @@ func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]Author return result, nil } +const streamAuthorsSQL = `SELECT * FROM author WHERE first_name = $1;` + +// StreamAuthors implements Querier.StreamAuthors. +func (q *DBQuerier) StreamAuthors(ctx context.Context, firstName string, yield func(AuthorRow) error) error { + ctx = context.WithValue(ctx, QueryName{}, "StreamAuthors") + rows, err := q.conn.Query(ctx, streamAuthorsSQL, firstName) + if err != nil { + return fmt.Errorf("query StreamAuthors: %w", err) + } + + if err := forEachRow(rows, func(item *AuthorRow) []any { + return []any{&item.AuthorID, &item.FirstName, &item.LastName, &item.Suffix} + }, yield); err != nil { + return fmt.Errorf("stream StreamAuthors row: %w", err) + } + return nil +} + const findAuthorNamesSQL = `SELECT first_name, last_name FROM author ORDER BY author_id = $1;` type FindAuthorNamesRow struct { diff --git a/example/author/query.sql_test.go b/example/author/query.sql_test.go index 770467f7..a8ddc188 100644 --- a/example/author/query.sql_test.go +++ b/example/author/query.sql_test.go @@ -91,7 +91,7 @@ func TestNewQuerier_FindAuthors(t *testing.T) { {AuthorID: washingtonID, FirstName: "george", LastName: "washington", Suffix: nil}, {AuthorID: carverID, FirstName: "george", LastName: "carver", Suffix: nil}, } - assert.Equal(t, want, authors) + assert.ElementsMatch(t, want, authors) }) t.Run("FindAuthors - 0 rows - joe", func(t *testing.T) { @@ -101,6 +101,53 @@ func TestNewQuerier_FindAuthors(t *testing.T) { }) } +func TestNewQuerier_StreamAuthors(t *testing.T) { + conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) + defer cleanup() + q := NewQuerier(conn) + adamsID := insertAuthor(t, q, "john", "adams") + washingtonID := insertAuthor(t, q, "george", "washington") + carverID := insertAuthor(t, q, "george", "carver") + + t.Run("StreamAuthors - 2 rows - george", func(t *testing.T) { + var authors []AuthorRow + err := q.StreamAuthors(t.Context(), "george", func(row AuthorRow) error { + authors = append(authors, row) + return nil + }) + require.NoError(t, err) + want := []AuthorRow{ + {AuthorID: washingtonID, FirstName: "george", LastName: "washington", Suffix: nil}, + {AuthorID: carverID, FirstName: "george", LastName: "carver", Suffix: nil}, + } + assert.Equal(t, want, authors) + }) + + t.Run("StreamAuthors - 0 rows - joe", func(t *testing.T) { + var calls int + err := q.StreamAuthors(t.Context(), "joe", func(row AuthorRow) error { + calls++ + return nil + }) + require.NoError(t, err) + assert.Zero(t, calls) + }) + + t.Run("StreamAuthors - early stop", func(t *testing.T) { + stopErr := errors.New("stop streaming") + var authors []AuthorRow + err := q.StreamAuthors(t.Context(), "john", func(row AuthorRow) error { + authors = append(authors, row) + return stopErr + }) + require.Error(t, err) + assert.ErrorIs(t, err, stopErr) + assert.Equal(t, []AuthorRow{ + {AuthorID: adamsID, FirstName: "john", LastName: "adams", Suffix: nil}, + }, authors) + }) +} + func TestNewQuerier_FindFirstNames(t *testing.T) { conn, cleanup := pgtest.NewPostgresSchema(t, []string{"schema.sql"}) defer cleanup() diff --git a/example/go_pointer_types/query.sql b/example/go_pointer_types/query.sql index e545d996..bed08ad1 100644 --- a/example/go_pointer_types/query.sql +++ b/example/go_pointer_types/query.sql @@ -23,3 +23,7 @@ LIMIT 1; -- name: GenSeriesStr :many SELECT n::text FROM generate_series(0, 2) n; + +-- name: StreamSeriesStr :stream +SELECT n::text +FROM generate_series(0, 2) n; diff --git a/example/go_pointer_types/query.sql.go b/example/go_pointer_types/query.sql.go index 2bca0609..bd56dae0 100644 --- a/example/go_pointer_types/query.sql.go +++ b/example/go_pointer_types/query.sql.go @@ -24,6 +24,8 @@ type Querier interface { GenSeriesStr1(ctx context.Context) (*string, error) GenSeriesStr(ctx context.Context) ([]*string, error) + + StreamSeriesStr(ctx context.Context, yield func(*string) error) error } var _ Querier = &DBQuerier{} @@ -44,6 +46,14 @@ func NewQuerier(conn genericConn) *DBQuerier { return &DBQuerier{conn: conn} } +func forEachRow[T any](rows pgx.Rows, scanTargets func(*T) []any, yield func(T) error) error { + var item T + _, err := pgx.ForEachRow(rows, scanTargets(&item), func() error { + return yield(item) + }) + return err +} + // RegisterTypes loads custom PostgreSQL types into conn's pgx type map. func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { pending := append([]string(nil), typesToRegister...) @@ -199,3 +209,22 @@ func (q *DBQuerier) GenSeriesStr(ctx context.Context) ([]*string, error) { } return result, nil } + +const streamSeriesStrSQL = `SELECT n::text +FROM generate_series(0, 2) n;` + +// StreamSeriesStr implements Querier.StreamSeriesStr. +func (q *DBQuerier) StreamSeriesStr(ctx context.Context, yield func(*string) error) error { + ctx = context.WithValue(ctx, QueryName{}, "StreamSeriesStr") + rows, err := q.conn.Query(ctx, streamSeriesStrSQL) + if err != nil { + return fmt.Errorf("query StreamSeriesStr: %w", err) + } + + if err := forEachRow(rows, func(item **string) []any { + return []any{item} + }, yield); err != nil { + return fmt.Errorf("stream StreamSeriesStr row: %w", err) + } + return nil +} diff --git a/example/go_pointer_types/query.sql_test.go b/example/go_pointer_types/query.sql_test.go index 30a3cfc9..08442d68 100644 --- a/example/go_pointer_types/query.sql_test.go +++ b/example/go_pointer_types/query.sql_test.go @@ -88,4 +88,18 @@ func TestQuerier_GenSeriesStr(t *testing.T) { zero, one, two := "0", "1", "2" assert.Equal(t, []*string{&zero, &one, &two}, got) }) + + t.Run("StreamSeriesStr", func(t *testing.T) { + var got []string + err := q.StreamSeriesStr(ctx, func(row *string) error { + if row == nil { + got = append(got, "") + return nil + } + got = append(got, *row) + return nil + }) + require.NoError(t, err) + assert.Equal(t, []string{"0", "1", "2"}, got) + }) } diff --git a/example/void/query.sql b/example/void/query.sql index dfae0a7f..55951685 100644 --- a/example/void/query.sql +++ b/example/void/query.sql @@ -12,3 +12,6 @@ SELECT void_fn(), 'foo' as foo, 'bar' as bar; -- name: VoidThree2 :many SELECT 'foo' as foo, void_fn(), void_fn(); + +-- name: StreamVoidThree :stream +SELECT 'foo' as foo, void_fn(), void_fn(); diff --git a/example/void/query.sql.go b/example/void/query.sql.go index 05939016..baf4bb61 100644 --- a/example/void/query.sql.go +++ b/example/void/query.sql.go @@ -22,6 +22,8 @@ type Querier interface { VoidThree(ctx context.Context) (VoidThreeRow, error) VoidThree2(ctx context.Context) ([]string, error) + + StreamVoidThree(ctx context.Context, yield func(string) error) error } var _ Querier = &DBQuerier{} @@ -42,6 +44,14 @@ func NewQuerier(conn genericConn) *DBQuerier { return &DBQuerier{conn: conn} } +func forEachRow[T any](rows pgx.Rows, scanTargets func(*T) []any, yield func(T) error) error { + var item T + _, err := pgx.ForEachRow(rows, scanTargets(&item), func() error { + return yield(item) + }) + return err +} + // RegisterTypes loads custom PostgreSQL types into conn's pgx type map. func RegisterTypes(ctx context.Context, conn *pgx.Conn) error { pending := append([]string(nil), typesToRegister...) @@ -179,3 +189,21 @@ func (q *DBQuerier) VoidThree2(ctx context.Context) ([]string, error) { } return result, nil } + +const streamVoidThreeSQL = `SELECT 'foo' as foo, void_fn(), void_fn();` + +// StreamVoidThree implements Querier.StreamVoidThree. +func (q *DBQuerier) StreamVoidThree(ctx context.Context, yield func(string) error) error { + ctx = context.WithValue(ctx, QueryName{}, "StreamVoidThree") + rows, err := q.conn.Query(ctx, streamVoidThreeSQL) + if err != nil { + return fmt.Errorf("query StreamVoidThree: %w", err) + } + + if err := forEachRow(rows, func(item *string) []any { + return []any{item, nil, nil} + }, yield); err != nil { + return fmt.Errorf("stream StreamVoidThree row: %w", err) + } + return nil +} diff --git a/example/void/query.sql_test.go b/example/void/query.sql_test.go index 0e69d6be..fd503c64 100644 --- a/example/void/query.sql_test.go +++ b/example/void/query.sql_test.go @@ -45,4 +45,16 @@ func TestQuerier(t *testing.T) { } assert.Equal(t, []string{"foo"}, foos) } + + { + var got []string + err := q.StreamVoidThree(ctx, func(row string) error { + got = append(got, row) + return nil + }) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, []string{"foo"}, got) + } } diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 351f3c2c..4db52f91 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -69,9 +69,10 @@ func (g *CommentGroup) Kind() NodeKind { return KindCommentGroup } type ResultKind string const ( - ResultKindMany ResultKind = ":many" - ResultKindOne ResultKind = ":one" - ResultKindExec ResultKind = ":exec" + ResultKindMany ResultKind = ":many" + ResultKindOne ResultKind = ":one" + ResultKindExec ResultKind = ":exec" + ResultKindStream ResultKind = ":stream" ) // Pragmas are options to control generated code for a single query. diff --git a/internal/codegen/golang/query.gotemplate b/internal/codegen/golang/query.gotemplate index c8112f13..72f44710 100644 --- a/internal/codegen/golang/query.gotemplate +++ b/internal/codegen/golang/query.gotemplate @@ -18,12 +18,16 @@ type QueryName struct{} // Querier is a typesafe Go interface backed by SQL queries. type Querier interface { {{- range $pkgFile := .Pkg.Files -}} -{{- range $i, $q := $pkgFile.Queries }} {{- "\n\t" -}} - {{- if $q.Doc }}{{ $q.Doc }} {{ end -}} - {{.Name}}(ctx context.Context {{- $q.EmitParams }}) ({{ $q.EmitResultType }}, error) - {{- "\n" -}} -{{end -}} -{{- end -}} + {{- range $i, $q := $pkgFile.Queries }} {{- "\n\t" -}} + {{- if $q.Doc }}{{ $q.Doc }} {{ end -}} + {{- if eq $q.ResultKind ":stream" -}} + {{.Name}}(ctx context.Context {{- $q.EmitStreamParams }}) error + {{- else -}} + {{.Name}}(ctx context.Context {{- $q.EmitParams }}) ({{ $q.EmitResultType }}, error) + {{- end -}} + {{- "\n" -}} + {{end -}} + {{- end -}} } var _ Querier = &DBQuerier{} @@ -44,6 +48,17 @@ func NewQuerier(conn genericConn) *DBQuerier { return &DBQuerier{conn: conn} } +{{- if .Pkg.HasStream }} + +func forEachRow[T any](rows pgx.Rows, scanTargets func(*T) []any, yield func(T) error) error { + var item T + _, err := pgx.ForEachRow(rows, scanTargets(&item), func() error { + return yield(item) + }) + return err +} +{{- end }} + {{- with emitDeclarers .Declarers $.PkgPath }}{{- "\n\n" -}}{{ . }}{{- end -}} {{- end -}} @@ -54,6 +69,22 @@ const {{ $q.SQLVarName }} = {{ $q.EmitPreparedSQL }} {{- $q.EmitRowStruct -}} {{- "\n\n" -}} // {{ $q.Name }} implements Querier.{{ $q.Name }}. +{{- if eq $q.ResultKind ":stream" }} +func (q *DBQuerier) {{ $q.Name }}(ctx context.Context {{- $q.EmitStreamParams }}) error { + ctx = context.WithValue(ctx, QueryName{}, "{{ $q.Name }}") + rows, err := q.conn.Query(ctx, {{ $q.SQLVarName }} {{- $q.EmitParamNames }}) + if err != nil { + return fmt.Errorf("query {{ $q.Name }}: %w", err) + } + + if err := forEachRow(rows, func(item *{{ $q.EmitSingularResultType }}) []any { + return []any{ {{- $q.EmitStreamScanTargets -}} } + }, yield); err != nil { + return fmt.Errorf("stream {{ $q.Name }} row: %w", err) + } + return nil +} +{{- else }} func (q *DBQuerier) {{ $q.Name }}(ctx context.Context {{- $q.EmitParams }}) ({{ $q.EmitResultType }}, error) { ctx = context.WithValue(ctx, QueryName{}, "{{ $q.Name }}") {{- if eq $q.ResultKind ":exec" }} @@ -77,6 +108,7 @@ func (q *DBQuerier) {{ $q.Name }}(ctx context.Context {{- $q.EmitParams }}) ({{ return result, nil {{- end }} } +{{- end }} {{- end -}} {{- "\n" -}} {{- end -}} diff --git a/internal/codegen/golang/templated_file.go b/internal/codegen/golang/templated_file.go index d77a9b71..3ab20216 100644 --- a/internal/codegen/golang/templated_file.go +++ b/internal/codegen/golang/templated_file.go @@ -16,6 +16,17 @@ type TemplatedPackage struct { Files []TemplatedFile // sorted lexicographically by path } +func (tp TemplatedPackage) HasStream() bool { + for _, file := range tp.Files { + for _, query := range file.Queries { + if query.ResultKind == ast.ResultKindStream { + return true + } + } + } + return false +} + // TemplatedFile is the Go version of a SQL query file with all information // needed to execute the codegen template. type TemplatedFile struct { @@ -40,7 +51,7 @@ type TemplatedQuery struct { RowStructType string // output row struct type for multi-column results ShouldEmitRow bool // true if this query owns its row struct declaration SQLVarName string // name of the string variable containing the SQL - ResultKind ast.ResultKind // kind of result: :one, :many, or :exec + ResultKind ast.ResultKind // kind of result: :one, :many, :stream, or :exec Doc string // doc from the source query file, formatted for Go PreparedSQL string // SQL query, ready to run with PREPARE statement Inputs []TemplatedParam // input parameters to the query @@ -186,7 +197,7 @@ func (tq TemplatedQuery) EmitPlanScan(idx int, out TemplatedColumn) (string, err switch tq.ResultKind { case ast.ResultKindExec: return "", fmt.Errorf("cannot EmitPlanScanArgs for :exec query %s", tq.Name) - case ast.ResultKindMany, ast.ResultKindOne: + case ast.ResultKindMany, ast.ResultKindOne, ast.ResultKindStream: break // okay default: return "", fmt.Errorf("unhandled EmitPlanScanArgs type: %s", tq.ResultKind) @@ -211,7 +222,7 @@ func (tq TemplatedQuery) EmitRowScanArgs() (string, error) { switch tq.ResultKind { case ast.ResultKindExec: return "", fmt.Errorf("cannot EmitRowScanArgs for :exec query %s", tq.Name) - case ast.ResultKindMany, ast.ResultKindOne: + case ast.ResultKindMany, ast.ResultKindOne, ast.ResultKindStream: break // okay default: return "", fmt.Errorf("unhandled EmitRowScanArgs type: %s", tq.ResultKind) @@ -265,6 +276,8 @@ func (tq TemplatedQuery) EmitCollectionFunc() (string, error) { switch tq.ResultKind { case ast.ResultKindExec: return "", fmt.Errorf("cannot EmitCollectionFunc for :exec query %s", tq.Name) + case ast.ResultKindStream: + return "", fmt.Errorf("cannot EmitCollectionFunc for :stream query %s", tq.Name) case ast.ResultKindMany: return "pgx.CollectRows", nil case ast.ResultKindOne: @@ -278,6 +291,8 @@ func (tq TemplatedQuery) EmitCollectionErrContext() (string, error) { switch tq.ResultKind { case ast.ResultKindExec: return "", fmt.Errorf("cannot EmitCollectionErrContext for :exec query %s", tq.Name) + case ast.ResultKindStream: + return "", fmt.Errorf("cannot EmitCollectionErrContext for :stream query %s", tq.Name) case ast.ResultKindMany: return "scan " + tq.Name + " row", nil case ast.ResultKindOne: @@ -291,7 +306,7 @@ func (tq TemplatedQuery) EmitRowToFunc() (string, error) { switch tq.ResultKind { case ast.ResultKindExec: return "", fmt.Errorf("cannot EmitRowToFunc for :exec query %s", tq.Name) - case ast.ResultKindMany, ast.ResultKindOne: + case ast.ResultKindMany, ast.ResultKindOne, ast.ResultKindStream: if len(tq.Outputs) == 1 { return "pgx.RowTo", nil } @@ -301,6 +316,10 @@ func (tq TemplatedQuery) EmitRowToFunc() (string, error) { } } +func (tq TemplatedQuery) EmitStreamParams() string { + return tq.EmitParams() + ", yield func(" + tq.EmitSingularResultType() + ") error" +} + func (tq TemplatedQuery) EmitRowMapper() (string, error) { rowToFunc, err := tq.EmitRowToFunc() if err != nil { @@ -339,6 +358,22 @@ func (tq TemplatedQuery) EmitScanTargets() (string, error) { return strings.Join(targets, ", "), nil } +func (tq TemplatedQuery) EmitStreamScanTargets() (string, error) { + targets := make([]string, 0, len(tq.ScanCols)) + for _, col := range tq.ScanCols { + if _, ok := gotype.UnwrapNestedType(col.Type).(*gotype.VoidType); ok { + targets = append(targets, "nil") + continue + } + if len(tq.Outputs) == 1 { + targets = append(targets, "item") + continue + } + targets = append(targets, "&item."+col.UpperName) + } + return strings.Join(targets, ", "), nil +} + // EmitSingularResultType returns the string representing a single element // of the overall query result type, like FindAuthorsRow when the overall return // type is []FindAuthorsRow. @@ -368,6 +403,8 @@ func (tq TemplatedQuery) EmitResultType() (string, error) { return "[]" + tq.EmitSingularResultType(), nil case ast.ResultKindOne: return tq.EmitSingularResultType(), nil + case ast.ResultKindStream: + return "error", nil default: return "", fmt.Errorf("unhandled EmitResultType kind: %s", tq.ResultKind) } @@ -406,6 +443,8 @@ func (tq TemplatedQuery) EmitResultTypeInit(name string) (string, error) { case ast.ResultKindExec: return "", fmt.Errorf("cannot EmitResultTypeInit for :exec query %s", tq.Name) + case ast.ResultKindStream: + return "", fmt.Errorf("cannot EmitResultTypeInit for :stream query %s", tq.Name) default: return "", fmt.Errorf("unhandled EmitResultTypeInit for kind %s", tq.ResultKind) @@ -426,6 +465,8 @@ func (tq TemplatedQuery) EmitZeroResult() (string, error) { switch tq.ResultKind { case ast.ResultKindExec: return "pgconn.CommandTag{}", nil + case ast.ResultKindStream: + return "", fmt.Errorf("cannot EmitZeroResult for :stream query %s", tq.Name) case ast.ResultKindMany: return "nil", nil // empty slice case ast.ResultKindOne: @@ -481,6 +522,8 @@ func (tq TemplatedQuery) EmitResultExpr(name string) (string, error) { case ast.ResultKindExec: return "", fmt.Errorf("cannot EmitResultExpr for :exec query %s", tq.Name) + case ast.ResultKindStream: + return "", fmt.Errorf("cannot EmitResultExpr for :stream query %s", tq.Name) default: return "", fmt.Errorf("unhandled EmitResultExpr type: %s", tq.ResultKind) @@ -515,7 +558,7 @@ func (tq TemplatedQuery) EmitRowStruct() string { switch tq.ResultKind { case ast.ResultKindExec: return "" - case ast.ResultKindOne, ast.ResultKindMany: + case ast.ResultKindOne, ast.ResultKindMany, ast.ResultKindStream: if len(tq.Outputs) == 1 { return "" // if there's only 1 output column, return it directly } diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 6cf03a37..0fc6b418 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -203,7 +203,7 @@ func (p *parser) errorExpected(pos gotok.Pos, msg string) { } // Regexp to extract query annotations that control output. -var annotationRegexp = regexp.MustCompile(`name: ([a-zA-Z0-9_$]+)[ \t]+(:many|:one|:exec)[ \t]*(.*)`) +var annotationRegexp = regexp.MustCompile(`name: ([a-zA-Z0-9_$]+)[ \t]+(:many|:one|:exec|:stream)[ \t]*(.*)`) func (p *parser) parseQuery() ast.Query { if p.trace { diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go index e2520aba..648924df 100644 --- a/internal/parser/parser_test.go +++ b/internal/parser/parser_test.go @@ -133,6 +133,18 @@ func TestParseFile_Queries(t *testing.T) { Pragmas: ast.Pragmas{RowType: "Shared"}, }, }, + { + "-- name: StreamAuthors :stream row=Author\nSELECT 1;", + &ast.SourceQuery{ + Name: "StreamAuthors", + Doc: &ast.CommentGroup{List: []*ast.LineComment{{Text: "-- name: StreamAuthors :stream row=Author"}}}, + SourceSQL: "SELECT 1;", + PreparedSQL: "SELECT 1;", + ParamNames: nil, + ResultKind: ast.ResultKindStream, + Pragmas: ast.Pragmas{RowType: "Author"}, + }, + }, } for _, tt := range tests { diff --git a/internal/pginfer/pginfer.go b/internal/pginfer/pginfer.go index 42d9c050..44d7e48e 100644 --- a/internal/pginfer/pginfer.go +++ b/internal/pginfer/pginfer.go @@ -21,7 +21,7 @@ type TypedQuery struct { // Name of the query, from the comment preceding the query. Like 'FindAuthors' // in the source SQL: "-- name: FindAuthors :many" Name string - // The result output kind, :one, :many, or :exec. + // The result output kind, :one, :many, :stream, or :exec. ResultKind ast.ResultKind // The comment lines preceding the query, without the SQL comment syntax and // excluding the :name line. From 740c219d7fc11411bccb44419175645c39bbc6a0 Mon Sep 17 00:00:00 2001 From: adelnizamutdinov Date: Fri, 19 Jun 2026 19:22:44 +0300 Subject: [PATCH 25/27] Add domain type resolution for custom IDs --- example/acceptance_test.go | 1 + example/custom_types/codegen_test.go | 1 + example/custom_types/query.sql | 4 ++ example/custom_types/query.sql.go | 22 ++++++++ example/custom_types/schema.sql | 8 +++ example/custom_types/types.go | 3 + internal/codegen/golang/type_resolver.go | 6 ++ internal/codegen/golang/type_resolver_test.go | 20 +++++++ internal/pg/column.go | 31 +++++++++- internal/pg/column_test.go | 23 ++++++-- internal/pg/query.sql | 19 +++++++ internal/pg/query.sql.go | 47 ++++++++++++++++ internal/pg/type_fetcher.go | 56 +++++++++++++++++++ internal/pg/type_fetcher_test.go | 18 ++++++ internal/pg/types.go | 12 ++-- internal/pginfer/pginfer.go | 35 ++++++++---- internal/pginfer/pginfer_test.go | 33 +++++++++++ 17 files changed, 316 insertions(+), 23 deletions(-) diff --git a/example/acceptance_test.go b/example/acceptance_test.go index 0d68ef88..506f44c7 100644 --- a/example/acceptance_test.go +++ b/example/acceptance_test.go @@ -199,6 +199,7 @@ func TestExamples(t *testing.T) { "--go-type", "int8=github.com/meoyawn/pggen/example/custom_types.CustomInt", "--go-type", "my_int=int", "--go-type", "_my_int=[]int", + "--go-type", "show_id=github.com/meoyawn/pggen/example/custom_types.ShowID", }, }, { diff --git a/example/custom_types/codegen_test.go b/example/custom_types/codegen_test.go index bc75d959..e51d2ece 100644 --- a/example/custom_types/codegen_test.go +++ b/example/custom_types/codegen_test.go @@ -28,6 +28,7 @@ func TestGenerate_Go_Example_CustomTypes(t *testing.T) { "int8": "github.com/meoyawn/pggen/example/custom_types.CustomInt", "my_int": "int", "_my_int": "[]int", + "show_id": "github.com/meoyawn/pggen/example/custom_types.ShowID", }, }) if err != nil { diff --git a/example/custom_types/query.sql b/example/custom_types/query.sql index c4050ed4..e856c9ee 100644 --- a/example/custom_types/query.sql +++ b/example/custom_types/query.sql @@ -9,3 +9,7 @@ SELECT '5'::my_int as int5; -- name: IntArray :many SELECT ARRAY ['5', '6', '7']::int[] as ints; + +-- name: CreateShow :one +INSERT INTO shows DEFAULT VALUES +RETURNING id; diff --git a/example/custom_types/query.sql.go b/example/custom_types/query.sql.go index 990ddb1a..f14ade69 100644 --- a/example/custom_types/query.sql.go +++ b/example/custom_types/query.sql.go @@ -21,6 +21,8 @@ type Querier interface { CustomMyInt(ctx context.Context) (int, error) IntArray(ctx context.Context) ([][]int32, error) + + CreateShow(ctx context.Context) (ShowID, error) } var _ Querier = &DBQuerier{} @@ -155,3 +157,23 @@ func (q *DBQuerier) IntArray(ctx context.Context) ([][]int32, error) { } return result, nil } + +const createShowSQL = `INSERT INTO shows DEFAULT VALUES +RETURNING id;` + +// CreateShow implements Querier.CreateShow. +func (q *DBQuerier) CreateShow(ctx context.Context) (ShowID, error) { + ctx = context.WithValue(ctx, QueryName{}, "CreateShow") + rows, err := q.conn.Query(ctx, createShowSQL) + if err != nil { + var zero ShowID + return zero, fmt.Errorf("query CreateShow: %w", err) + } + + result, err := pgx.CollectOneRow(rows, pgx.RowTo[ShowID]) + if err != nil { + var zero ShowID + return zero, fmt.Errorf("query CreateShow: %w", err) + } + return result, nil +} diff --git a/example/custom_types/schema.sql b/example/custom_types/schema.sql index 4d2a5f11..f4d0c233 100644 --- a/example/custom_types/schema.sql +++ b/example/custom_types/schema.sql @@ -33,3 +33,11 @@ CREATE TYPE my_int ( DELIMITER = ',', COLLATABLE = FALSE ); + +CREATE DOMAIN show_id AS text + DEFAULT 'shw_default' + CHECK (VALUE ~ '^shw_'); + +CREATE TABLE shows ( + id show_id PRIMARY KEY +); diff --git a/example/custom_types/types.go b/example/custom_types/types.go index fde455c4..029fa0ae 100644 --- a/example/custom_types/types.go +++ b/example/custom_types/types.go @@ -2,3 +2,6 @@ package custom_types // CustomInt is a custom type in the same package as the query file. type CustomInt int + +// ShowID is a domain-backed ID type. +type ShowID string diff --git a/internal/codegen/golang/type_resolver.go b/internal/codegen/golang/type_resolver.go index 1cc2c10f..76ba4c52 100644 --- a/internal/codegen/golang/type_resolver.go +++ b/internal/codegen/golang/type_resolver.go @@ -93,6 +93,12 @@ func (tr TypeResolver) Resolve(pgt pg.Type, nullable bool, pkgPath string) (goty return nil, fmt.Errorf("create composite type: %w", err) } return comp, nil + case pg.DomainType: + baseType, err := tr.Resolve(pgt.BaseType, nullable, pkgPath) + if err != nil { + return nil, fmt.Errorf("resolve domain base type for %q: %w", pgt.Name, err) + } + return baseType, nil } return nil, fmt.Errorf("no go type found for Postgres type %s oid=%d", pgt.String(), pgt.OID()) diff --git a/internal/codegen/golang/type_resolver_test.go b/internal/codegen/golang/type_resolver_test.go index bd6a9026..8c41568d 100644 --- a/internal/codegen/golang/type_resolver_test.go +++ b/internal/codegen/golang/type_resolver_test.go @@ -59,6 +59,26 @@ func TestTypeResolver_Resolve(t *testing.T) { Type: &gotype.OpaqueType{PgType: pg.BaseType{Name: "custom_type"}, Name: "QualType"}, }, }, + { + name: "domain override", + overrides: map[string]string{"show_id": "example.com/custom.ShowID"}, + pgType: pg.DomainType{Name: "show_id", BaseType: pg.Text}, + want: &gotype.ImportType{ + PkgPath: "example.com/custom", + Type: &gotype.OpaqueType{ + PgType: pg.DomainType{Name: "show_id", BaseType: pg.Text}, + Name: "ShowID", + }, + }, + }, + { + name: "domain fallback to base type", + pgType: pg.DomainType{Name: "show_id", BaseType: pg.Text}, + want: &gotype.OpaqueType{ + PgType: pg.Text, + Name: "string", + }, + }, { name: "override pointer", overrides: map[string]string{"custom_type": "*example.com/custom.QualType"}, diff --git a/internal/pg/column.go b/internal/pg/column.go index e8c2236e..05b6f032 100644 --- a/internal/pg/column.go +++ b/internal/pg/column.go @@ -76,6 +76,7 @@ func FetchColumns(conn *pgx.Conn, keys []ColumnKey) ([]Column, error) { cls.relname AS table_name, attr.attname AS col_name, attr.attnum AS col_num, + attr.atttypid AS col_type_oid, attr.attnotnull AS col_null FROM pg_class cls JOIN pg_attribute attr ON (attr.attrelid = cls.oid) @@ -87,19 +88,45 @@ func FetchColumns(conn *pgx.Conn, keys []ColumnKey) ([]Column, error) { return nil, fmt.Errorf("fetch column metadata: %w", err) } defer rows.Close() + + type fetchedColumn struct { + col Column + typeOID uint32 + } + fetched := make([]fetchedColumn, 0, len(uncachedKeys)) + typeOIDs := make([]uint32, 0, len(uncachedKeys)) for rows.Next() { col := Column{} notNull := false - if err := rows.Scan(&col.TableOID, &col.TableName, &col.Name, &col.Number, ¬Null); err != nil { + var typeOID uint32 + if err := rows.Scan(&col.TableOID, &col.TableName, &col.Name, &col.Number, &typeOID, ¬Null); err != nil { return nil, fmt.Errorf("scan fetch column row: %w", err) } col.Null = !notNull - columnCache[ColumnKey{col.TableOID, col.Number}] = col + fetched = append(fetched, fetchedColumn{col: col, typeOID: typeOID}) + typeOIDs = append(typeOIDs, typeOID) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("close fetch column rows: %w", err) } + types, err := NewTypeFetcher(conn).FindTypesByOIDs(typeOIDs...) + if err != nil { + return nil, fmt.Errorf("fetch column types: %w", err) + } + + columnsMu.Lock() + for _, fc := range fetched { + colType, ok := types[fc.typeOID] + if !ok { + columnsMu.Unlock() + return nil, fmt.Errorf("missing type for column %s.%s oid=%d", fc.col.TableName, fc.col.Name, fc.typeOID) + } + fc.col.Type = colType + columnCache[ColumnKey{fc.col.TableOID, fc.col.Number}] = fc.col + } + columnsMu.Unlock() + return fetchCachedColumns(keys) } diff --git a/internal/pg/column_test.go b/internal/pg/column_test.go index c6033894..484fe5f3 100644 --- a/internal/pg/column_test.go +++ b/internal/pg/column_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/jackc/pgx/v5" "github.com/meoyawn/pggen/internal/pgtest" "github.com/meoyawn/pggen/internal/texts" @@ -25,23 +26,35 @@ func TestFetchColumns(t *testing.T) { "one col null", "CREATE TABLE author ( first_name text );", []uint16{1}, - []Column{{Name: "first_name", TableName: "author", Number: 1, Null: true}}, + []Column{{Name: "first_name", TableName: "author", Number: 1, Type: Text, Null: true}}, }, { "one col not null", "CREATE TABLE author ( first_name text NOT NULL);", []uint16{1}, - []Column{{Name: "first_name", TableName: "author", Number: 1, Null: false}}, + []Column{{Name: "first_name", TableName: "author", Number: 1, Type: Text, Null: false}}, }, { "two col mixed", "CREATE TABLE author ( first_name text NOT NULL, last_name text);", []uint16{2, 1}, []Column{ - {Name: "last_name", TableName: "author", Number: 2, Null: true}, - {Name: "first_name", TableName: "author", Number: 1, Null: false}, + {Name: "last_name", TableName: "author", Number: 2, Type: Text, Null: true}, + {Name: "first_name", TableName: "author", Number: 1, Type: Text, Null: false}, }, }, + { + "domain col", + "CREATE DOMAIN author_id AS text; CREATE TABLE author ( id author_id );", + []uint16{1}, + []Column{{ + Name: "id", + TableName: "author", + Number: 1, + Type: DomainType{Name: "author_id", BaseType: Text}, + Null: true, + }}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -61,7 +74,7 @@ func TestFetchColumns(t *testing.T) { col.TableOID = oid tt.want[i] = col } - if diff := cmp.Diff(tt.want, cols); diff != "" { + if diff := cmp.Diff(tt.want, cols, cmpopts.IgnoreFields(DomainType{}, "ID")); diff != "" { t.Errorf("FetchColumns() query mismatch (-want +got):\n%s", diff) } diff --git a/internal/pg/query.sql b/internal/pg/query.sql index 4bfb855a..29437871 100644 --- a/internal/pg/query.sql +++ b/internal/pg/query.sql @@ -73,6 +73,18 @@ WHERE arr_typ.typisdefined AND arr_typ.typlen = -1 AND arr_typ.oid = ANY (pggen.arg('OIDs')::oid[]); +-- name: FindDomainTypes :many +SELECT + typ.oid AS oid, + typ.typname::text AS type_name, + COALESCE(typ.typnotnull, false) AS is_not_null, + COALESCE(typ.typdefault IS NOT NULL, false) AS has_default, + typ.typbasetype AS base_oid, + COALESCE(typ.typndims, 0) AS dimensions +FROM pg_type typ +WHERE typ.typisdefined + AND typ.typtype = 'd' + AND typ.oid = ANY (pggen.arg('OIDs')::oid[]); -- A composite type represents a row or record, defined implicitly for each -- table, or explicitly with CREATE TYPE. @@ -134,6 +146,13 @@ WITH RECURSIVE oid_descs(oid) AS ( FROM pg_type arr_typ JOIN pg_type elem_typ ON arr_typ.typelem = elem_typ.oid JOIN all_oids od ON arr_typ.oid = od.oid + UNION + -- Domain base types. + SELECT typ.typbasetype AS oid + FROM pg_type typ + JOIN all_oids od ON typ.oid = od.oid + WHERE typ.typtype = 'd' + AND typ.typbasetype > 0 ) t ) SELECT oid diff --git a/internal/pg/query.sql.go b/internal/pg/query.sql.go index 8eaa9f19..49b3c73e 100644 --- a/internal/pg/query.sql.go +++ b/internal/pg/query.sql.go @@ -17,6 +17,8 @@ type Querier interface { FindArrayTypes(ctx context.Context, oids []uint32) ([]FindArrayTypesRow, error) + FindDomainTypes(ctx context.Context, oids []uint32) ([]FindDomainTypesRow, error) + // A composite type represents a row or record, defined implicitly for each // table, or explicitly with CREATE TYPE. // https://www.postgresql.org/docs/13/rowtypes.html @@ -209,6 +211,44 @@ func (q *DBQuerier) FindArrayTypes(ctx context.Context, oids []uint32) ([]FindAr return result, nil } +const findDomainTypesSQL = `SELECT + typ.oid AS oid, + typ.typname::text AS type_name, + COALESCE(typ.typnotnull, false) AS is_not_null, + COALESCE(typ.typdefault IS NOT NULL, false) AS has_default, + typ.typbasetype AS base_oid, + COALESCE(typ.typndims, 0) AS dimensions +FROM pg_type typ +WHERE typ.typisdefined + AND typ.typtype = 'd' + AND typ.oid = ANY ($1::oid[]);` + +type FindDomainTypesRow struct { + OID uint32 `json:"oid" db:"oid"` + TypeName string `json:"type_name" db:"type_name"` + IsNotNull *bool `json:"is_not_null" db:"is_not_null"` + HasDefault *bool `json:"has_default" db:"has_default"` + BaseOID uint32 `json:"base_oid" db:"base_oid"` + Dimensions *int32 `json:"dimensions" db:"dimensions"` +} + +// FindDomainTypes implements Querier.FindDomainTypes. +func (q *DBQuerier) FindDomainTypes(ctx context.Context, oids []uint32) ([]FindDomainTypesRow, error) { + ctx = context.WithValue(ctx, QueryName{}, "FindDomainTypes") + rows, err := q.conn.Query(ctx, findDomainTypesSQL, oids) + if err != nil { + var zero []FindDomainTypesRow + return zero, fmt.Errorf("query FindDomainTypes: %w", err) + } + + result, err := pgx.CollectRows(rows, pgx.RowToStructByName[FindDomainTypesRow]) + if err != nil { + var zero []FindDomainTypesRow + return zero, fmt.Errorf("scan FindDomainTypes row: %w", err) + } + return result, nil +} + const findCompositeTypesSQL = `WITH table_cols AS ( SELECT cls.relname AS table_name, @@ -290,6 +330,13 @@ const findDescendantOIDsSQL = `WITH RECURSIVE oid_descs(oid) AS ( FROM pg_type arr_typ JOIN pg_type elem_typ ON arr_typ.typelem = elem_typ.oid JOIN all_oids od ON arr_typ.oid = od.oid + UNION + -- Domain base types. + SELECT typ.typbasetype AS oid + FROM pg_type typ + JOIN all_oids od ON typ.oid = od.oid + WHERE typ.typtype = 'd' + AND typ.typbasetype > 0 ) t ) SELECT oid diff --git a/internal/pg/type_fetcher.go b/internal/pg/type_fetcher.go index 00cb0ab5..d2b05859 100644 --- a/internal/pg/type_fetcher.go +++ b/internal/pg/type_fetcher.go @@ -73,6 +73,16 @@ func (tf *TypeFetcher) FindTypesByOIDs(oids ...uint32) (map[uint32]Type, error) delete(uncached, arr.ID) } + domains, err := tf.findDomainTypes(ctx, uncached) + if err != nil { + return nil, fmt.Errorf("find domain types: %w", err) + } + for _, domain := range domains { + types[domain.ID] = domain + tf.cache.addType(domain) + delete(uncached, domain.ID) + } + unknowns, err := tf.findUnknownTypes(ctx, uncached) if err != nil { return nil, fmt.Errorf("find unknown types: %w", err) @@ -87,6 +97,9 @@ func (tf *TypeFetcher) FindTypesByOIDs(oids ...uint32) (map[uint32]Type, error) if err := tf.resolvePlaceholderTypes(types); err != nil { return nil, err } + for _, typ := range types { + tf.cache.addType(typ) + } if len(uncached) > 0 { return nil, fmt.Errorf("had %d unclassified types: %v", len(uncached), uncached) @@ -94,6 +107,42 @@ func (tf *TypeFetcher) FindTypesByOIDs(oids ...uint32) (map[uint32]Type, error) return types, nil } +func (tf *TypeFetcher) findDomainTypes(ctx context.Context, uncached map[uint32]struct{}) ([]DomainType, error) { + oids := oidKeys(uncached) + rows, err := tf.querier.FindDomainTypes(ctx, oids) + if err != nil { + return nil, fmt.Errorf("find domain oid types: %w", err) + } + types := make([]DomainType, len(rows)) + for i, row := range rows { + isNotNull := false + if row.IsNotNull != nil { + isNotNull = *row.IsNotNull + } + hasDefault := false + if row.HasDefault != nil { + hasDefault = *row.HasDefault + } + dimensions := 0 + if row.Dimensions != nil { + dimensions = int(*row.Dimensions) + } + baseType, ok := tf.cache.getOID(row.BaseOID) + if !ok { + baseType = placeholderType{ID: row.BaseOID} + } + types[i] = DomainType{ + ID: row.OID, + Name: row.TypeName, + IsNotNull: isNotNull, + HasDefault: hasDefault, + BaseType: baseType, + Dimensions: dimensions, + } + } + return types, nil +} + func (tf *TypeFetcher) findEnumTypes(ctx context.Context, uncached map[uint32]struct{}) ([]EnumType, error) { oids := oidKeys(uncached) rows, err := tf.querier.FindEnumTypes(ctx, oids) @@ -223,6 +272,13 @@ func (tf *TypeFetcher) resolvePlaceholderTypes(knownTypes map[uint32]Type) error } typ.Elem = newType return typ, nil + case DomainType: + newType, err := resolveType(typ.BaseType) + if err != nil { + return nil, fmt.Errorf("domain %q base: %w", typ.Name, err) + } + typ.BaseType = newType + return typ, nil case placeholderType: newType, ok := knownTypes[typ.ID] if !ok { diff --git a/internal/pg/type_fetcher_test.go b/internal/pg/type_fetcher_test.go index a68e59cf..09a68744 100644 --- a/internal/pg/type_fetcher_test.go +++ b/internal/pg/type_fetcher_test.go @@ -184,6 +184,23 @@ func TestNewTypeFetcher(t *testing.T) { }, }, }, + { + name: "domain", + schema: texts.Dedent(` + CREATE DOMAIN show_id AS text + DEFAULT 'shw_default' + CHECK (VALUE ~ '^shw_'); + `), + fetchOID: "show_id", + wants: []Type{ + DomainType{ + Name: "show_id", + HasDefault: true, + BaseType: Text, + }, + Text, + }, + }, } for _, tt := range tests { @@ -237,6 +254,7 @@ func TestNewTypeFetcher(t *testing.T) { cmpopts.IgnoreFields(EnumType{}, "ChildOIDs", "ID"), cmpopts.IgnoreFields(CompositeType{}, "ID"), cmpopts.IgnoreFields(ArrayType{}, "ID"), + cmpopts.IgnoreFields(DomainType{}, "ID"), } sortTypes(wantTypes) sortTypes(gotTypes) diff --git a/internal/pg/types.go b/internal/pg/types.go index ed7c06ef..04780811 100644 --- a/internal/pg/types.go +++ b/internal/pg/types.go @@ -89,12 +89,12 @@ type ( // DomainType is a user-create domain type. DomainType struct { - ID uint32 // pg_type.oid: row identifier - Name string // pg_type.typname: data type name - IsNotNull bool // pg_type.typnotnull: domains only, not null constraint for domains - HasDefault bool // pg_type.typdefault: domains only, if there's a default value - BaseType BaseType // pg_type.typbasetype: domains only, the base type - Dimensions int // pg_type.typndims: domains on array type only, 0 otherwise, number of array dimensions + ID uint32 // pg_type.oid: row identifier + Name string // pg_type.typname: data type name + IsNotNull bool // pg_type.typnotnull: domains only, not null constraint for domains + HasDefault bool // pg_type.typdefault: domains only, if there's a default value + BaseType Type // pg_type.typbasetype: domains only, the base type + Dimensions int // pg_type.typndims: domains on array type only, 0 otherwise, number of array dimensions } // CompositeType is a type containing multiple columns and is represented as diff --git a/internal/pginfer/pginfer.go b/internal/pginfer/pginfer.go index 44d7e48e..6faabd93 100644 --- a/internal/pginfer/pginfer.go +++ b/internal/pginfer/pginfer.go @@ -181,6 +181,10 @@ func (inf *Inferrer) prepareTypes(query *ast.SourceQuery) (_a []InputParam, _ [] if err != nil { return nil, nil, fmt.Errorf("fetch oid types: %w", err) } + outputColumnsMeta, err := inf.fetchOutputColumns(stmtDesc.Fields) + if err != nil { + return nil, nil, fmt.Errorf("fetch output columns: %w", err) + } // Output nullability. nullables, err := inf.inferOutputNullability(query, stmtDesc.Fields) @@ -195,6 +199,9 @@ func (inf *Inferrer) prepareTypes(query *ast.SourceQuery) (_a []InputParam, _ [] if !ok { return nil, nil, fmt.Errorf("no postgrestype name found for column %s with oid %d", desc.Name, desc.DataTypeOID) } + if outputColumnsMeta[i].Type != nil { + pgType = outputColumnsMeta[i].Type + } outputColumns = append(outputColumns, OutputColumn{ PgName: desc.Name, PgType: pgType, @@ -204,6 +211,23 @@ func (inf *Inferrer) prepareTypes(query *ast.SourceQuery) (_a []InputParam, _ [] return inputParams, outputColumns, nil } +func (inf *Inferrer) fetchOutputColumns(descs []pgconn.FieldDescription) ([]pg.Column, error) { + columnKeys := make([]pg.ColumnKey, len(descs)) + for i, desc := range descs { + if desc.TableOID > 0 { + columnKeys[i] = pg.ColumnKey{ + TableOID: desc.TableOID, + Number: desc.TableAttributeNumber, + } + } + } + cols, err := pg.FetchColumns(inf.conn, columnKeys) + if err != nil { + return nil, err + } + return cols, nil +} + // inferOutputNullability infers which of the output columns produced by the // query and described by descs can be null. func (inf *Inferrer) inferOutputNullability(query *ast.SourceQuery, descs []pgconn.FieldDescription) ([]bool, error) { @@ -215,16 +239,7 @@ func (inf *Inferrer) inferOutputNullability(query *ast.SourceQuery, descs []pgco return nil, err } - columnKeys := make([]pg.ColumnKey, len(descs)) - for i, desc := range descs { - if desc.TableOID > 0 { - columnKeys[i] = pg.ColumnKey{ - TableOID: desc.TableOID, - Number: desc.TableAttributeNumber, - } - } - } - cols, err := pg.FetchColumns(inf.conn, columnKeys) + cols, err := inf.fetchOutputColumns(descs) if err != nil { return nil, fmt.Errorf("fetch column for nullability: %w", err) } diff --git a/internal/pginfer/pginfer_test.go b/internal/pginfer/pginfer_test.go index 10f30408..7ecf4965 100644 --- a/internal/pginfer/pginfer_test.go +++ b/internal/pginfer/pginfer_test.go @@ -30,6 +30,14 @@ func TestInferrer_InferTypes(t *testing.T) { ); CREATE DOMAIN us_postal_code AS text; + + CREATE DOMAIN show_id AS text + DEFAULT 'shw_default' + CHECK (VALUE ~ '^shw_'); + + CREATE TABLE shows ( + id show_id PRIMARY KEY + ); `)) defer cleanupFunc() q := pg.NewQuerier(conn) @@ -37,6 +45,8 @@ func TestInferrer_InferTypes(t *testing.T) { require.NoError(t, err) deviceTypeArrOID, err := q.FindOIDByName(t.Context(), "_device_type") require.NoError(t, err) + showIDTypeOID, err := q.FindOIDByName(t.Context(), "show_id") + require.NoError(t, err) tests := []struct { name string @@ -94,6 +104,29 @@ func TestInferrer_InferTypes(t *testing.T) { }}, }, }, + { + name: "table column domain type", + query: &ast.SourceQuery{ + Name: "CreateShow", + PreparedSQL: "INSERT INTO shows DEFAULT VALUES RETURNING id", + ResultKind: ast.ResultKindOne, + }, + want: TypedQuery{ + Name: "CreateShow", + ResultKind: ast.ResultKindOne, + PreparedSQL: "INSERT INTO shows DEFAULT VALUES RETURNING id", + Outputs: []OutputColumn{{ + PgName: "id", + PgType: pg.DomainType{ + ID: showIDTypeOID, + Name: "show_id", + HasDefault: true, + BaseType: pg.Text, + }, + Nullable: false, + }}, + }, + }, { name: "one col domain type", query: &ast.SourceQuery{ From abf9635060a3907ca44afdbd75a034d06b75ad47 Mon Sep 17 00:00:00 2001 From: adelnizamutdinov Date: Fri, 19 Jun 2026 19:39:12 +0300 Subject: [PATCH 26/27] Document shared row structs and streaming queries --- README.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1a1ccb32..de5f7186 100644 --- a/README.md +++ b/README.md @@ -324,6 +324,65 @@ Examples embedded in the repo: } ``` +- **Shared row structs**: Add `row=` to a `:one`, `:many`, or `:stream` + query annotation to share one generated row struct across multiple queries + with the same output columns. pggen emits the struct as `Row`. + + ```sql + -- name: FindAuthorByID :one row=Author + SELECT * FROM author WHERE author_id = pggen.arg('author_id'); + + -- name: FindAuthors :many row=Author + SELECT * FROM author WHERE first_name = pggen.arg('first_name'); + ``` + + Generates: + + ```go + type AuthorRow struct { + AuthorID int32 `json:"author_id" db:"author_id"` + FirstName string `json:"first_name" db:"first_name"` + LastName string `json:"last_name" db:"last_name"` + Suffix *string `json:"suffix" db:"suffix"` + } + + func (q *DBQuerier) FindAuthorByID(ctx context.Context, authorID int32) (AuthorRow, error) {} + func (q *DBQuerier) FindAuthors(ctx context.Context, firstName string) ([]AuthorRow, error) {} + ``` + + Queries sharing a `row=` name must produce identical output columns, + including column names, Go field names, Go types, and nullability. pggen + reports an error if the row shapes differ. Single-column result queries + still return the column type directly, so `row=` only changes generated code + for multi-column result rows. + +- **Streaming queries**: Use the `:stream` annotation to process result rows + one at a time instead of collecting the whole result set into a slice. pggen + generates a method that accepts a `yield` callback and returns only an + error. + + ```sql + -- name: StreamAuthors :stream + SELECT * FROM author WHERE first_name = pggen.arg('first_name'); + ``` + + Generates: + + ```go + func (q *DBQuerier) StreamAuthors( + ctx context.Context, + firstName string, + yield func(StreamAuthorsRow) error, + ) error {} + ``` + + The callback runs once for each row. Return an error from the callback to + stop streaming early; pggen returns that error to the caller. Single-column + `:stream` queries pass the column type directly to `yield`, while + multi-column queries use the generated row struct. `row=` works with + `:stream` queries when you want to share the row struct with `:one` or + `:many` queries that return the same columns. + - **Acronyms**: Custom acronym support so that `author_id` renders as `AuthorID` instead of `AuthorId`. Supports two formats: @@ -561,7 +620,8 @@ We'll walk through the generated file `author/query.sql.go`: const findAuthorsSQL = `SELECT * FROM author WHERE first_name = $1;` ``` -- pggen generates a row struct for each query named `Row`. +- pggen generates a row struct for each multi-column result query named + `Row`. pggen transforms the output column names into struct field names from `lower_snake_case` to `UpperCamelCase` in [internal/casing/casing.go]. pggen derives JSON struct tags from the Postgres column names. To change the @@ -580,6 +640,20 @@ We'll walk through the generated file `author/query.sql.go`: creating the `Row` struct and returns the type directly. For example, the generated query for `SELECT author_id from author` returns `int32`, not a `Row` struct. + + To reuse the same row struct across multiple multi-column result queries, + add `row=` to each query annotation. pggen emits one `Row` + struct and uses it for all queries with that row name. Every query sharing a + row name must have the same output column names, Go field names, Go types, + and nullability. + + ```sql + -- name: FindAuthorByID :one row=Author + SELECT * FROM author WHERE author_id = pggen.arg('author_id'); + + -- name: FindAuthors :many row=Author + SELECT * FROM author WHERE first_name = pggen.arg('first_name'); + ``` pggen infers struct field types by preparing the query. When Postgres prepares a query, Postgres returns the parameter and column types as OIDs. @@ -631,8 +705,9 @@ We'll walk through the generated file `author/query.sql.go`: return fmt.Errorf("query StreamAuthors: %w", err) } - var item StreamAuthorsRow - if err := forEachRow(rows, []any{&item.AuthorID, &item.FirstName, &item.LastName, &item.Suffix}, &item, yield); err != nil { + if err := forEachRow(rows, func(item *StreamAuthorsRow) []any { + return []any{&item.AuthorID, &item.FirstName, &item.LastName, &item.Suffix} + }, yield); err != nil { return fmt.Errorf("stream StreamAuthors row: %w", err) } return nil From 9fc19c8979eb3592baa02c4af91db9d1fc7b0d15 Mon Sep 17 00:00:00 2001 From: adelnizamutdinov Date: Mon, 20 Jul 2026 16:14:28 +0300 Subject: [PATCH 27/27] Require downstream Postgres connection for generation --- ARCHITECTURE.md | 10 ++- CONTRIBUTING.md | 11 ++- README.md | 79 +++++++++++------- Taskfile.yaml | 11 ++- cmd/pggen/pggen.go | 46 ++++++---- cmd/pggen/pggen_test.go | 65 +++++++++++++++ docker-compose.yml | 5 +- generate.go | 147 ++++++++++++++++++++------------ generate_test.go | 152 ++++++++++++++++++++++++++++++++++ internal/pgdocker/pgdocker.go | 7 +- internal/pgdocker/template.go | 2 +- timescale_integration_test.go | 102 +++++++++++++++++++++++ 12 files changed, 519 insertions(+), 118 deletions(-) create mode 100644 cmd/pggen/pggen_test.go create mode 100644 timescale_integration_test.go diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 2026ea05..6043e11c 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -8,10 +8,12 @@ in the following steps. the `--schema-glob` flag in [cmd/pggen/pggen.go]. Pass the normalized options to `pggen.Generate` in [generate.go]. -2. Start Postgres by either connecting to the database specified in - `--postgres-connection` or by starting a new Dockerized Postgres instance. - [internal/pgdocker/pgdocker.go] creates and destroys Docker images for - pggen. +2. Connect to the downstream Postgres database specified by the required + `--postgres-connection`. With schema files, load them in a transaction on + that connection. Infer on the same session, roll the transaction back, and + close the connection before emitting generated files. pggen never + provisions Postgres. [internal/pgdocker/pgdocker.go] is test-only + infrastructure. 3. Parse each query files into an `*ast.File` containing many `*ast.SourceQuery` nodes in [internal/parser/interface.go]. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 727d0672..ae82922a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,8 +66,8 @@ You need to install 1 dependency: ## Testing -To test pggen, you'll typically start a long-lived Docker container with a -Postgres instance. The pggen tests create a new Postgres schema to isolate +To test pggen, you'll typically start a long-lived vanilla PostgreSQL container. +The pggen tests create a new Postgres schema to isolate tests from one another. Creating a new schema is much faster than spinning up a new Dockerized Postgres instance. @@ -76,6 +76,13 @@ task start task test # all unit tests ``` +Extension-aware generation has a separate opt-in Timescale test. Point it at a +downstream Timescale server; it is never part of `task check`: + +```shell +TIMESCALE_PGURL='postgres://...' task timescale-test +``` + To run the acceptance tests to validate that pggen produces the same code as the checked-in example code: diff --git a/README.md b/README.md index de5f7186..39ee9bad 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,8 @@ How to use pggen in three steps: 2. Run pggen to generate Go code to create type-safe methods for each query. ```bash - pggen gen go \ + go tool pggen gen go \ + --postgres-connection "$PGURL" \ --schema-glob schema.sql \ --query-glob 'screenshots/*.sql' \ --go-type 'int8=int' \ @@ -141,9 +142,9 @@ is far more revealing than the pitch. investing in really learning SQL; it will payoff. Otherwise, use [squirrel], [goqu], or [go-sqlbuilder] -- You don't want to add a Postgres or Docker dependency to your build phase. - Use [sqlc], though you might still need Docker. sqlc generates code by parsing - the schema file and queries in Go without using Postgres. +- You don't want to connect to Postgres during your build phase. Use [sqlc], + which generates code by parsing schema files and queries in Go without + running Postgres. [myriad]: https://github.com/d-tsuji/awesome-go-orms [sqlc]: https://github.com/kyleconroy/sqlc @@ -155,6 +156,19 @@ is far more revealing than the pitch. # Install +### Go tool dependency + +Go 1.24 or newer can track pggen as a tool dependency in the downstream +module: + +```shell +go get -tool github.com/meoyawn/pggen/cmd/pggen@latest +go tool pggen gen go --help +``` + +The examples below use `go tool pggen gen go`. A standalone `pggen gen go` +binary remains supported. + ### Download precompiled binaries Precompiled binaries from the latest release. Change `~/bin` if you want to @@ -200,9 +214,9 @@ Make sure pggen works: pggen gen go --help ``` -### Install from source +### Install a standalone binary from source -Requires Go 1.16 because pggen uses `go:embed`. Installs to `$GOPATH/bin`. +Installs to `$GOPATH/bin`. ```shell go install github.com/meoyawn/pggen/cmd/pggen@latest @@ -216,34 +230,37 @@ pggen gen go --help ## Usage -Generate code using Docker to create the Postgres database from a schema file: +`--postgres-connection` is always required. Set `PGURL` to the downstream +Postgres database used for inference; pggen does not provision Postgres. -```bash -# --schema-glob runs all matching files on Dockerized Postgres during database -# creation. -pggen gen go \ - --schema-glob author/schema.sql \ - --query-glob author/query.sql +Connection-only mode infers against objects already present in that database: -# Output: author/query.go.sql - -# Or with multiple schema files. The schema files run on Postgres -# in the order they appear on the command line. -pggen gen go \ - --schema-glob author/schema.sql \ - --schema-glob book/schema.sql \ - --schema-glob publisher/schema.sql \ +```bash +go tool pggen gen go \ + --postgres-connection "$PGURL" \ --query-glob author/query.sql # Output: author/query.sql.go ``` -Generate code using an existing Postgres database (useful for custom setups): +Connection-plus-schema mode loads `.sql` schema files in one transaction on +the connected database. pggen infers on the same session, then rolls the +transaction back and closes the connection before writing generated files. +Custom extension binaries must already exist on the downstream server. ```bash -pggen gen go \ - --query-glob author/query.sql \ - --postgres-connection "user=postgres port=5555 dbname=pggen" +go tool pggen gen go \ + --postgres-connection "$PGURL" \ + --schema-glob author/schema.sql \ + --query-glob author/query.sql + +# Multiple schema files run in command-line order. +go tool pggen gen go \ + --postgres-connection "$PGURL" \ + --schema-glob author/schema.sql \ + --schema-glob book/schema.sql \ + --schema-glob publisher/schema.sql \ + --query-glob author/query.sql # Output: author/query.sql.go ``` @@ -253,7 +270,8 @@ the same directory. If query files reside in different directories, you can use `--output-dir` to set a single output directory: ```bash -pggen gen go \ +go tool pggen gen go \ + --postgres-connection "$PGURL" \ --schema-glob schema.sql \ --query-glob author/fiction.sql \ --query-glob author/nonfiction.sql \ @@ -265,7 +283,8 @@ pggen gen go \ # Or, using a glob. Notice quotes around glob pattern to prevent shell # expansion. -pggen gen go \ +go tool pggen gen go \ + --postgres-connection "$PGURL" \ --schema-glob schema.sql \ --query-glob 'author/*.sql' ``` @@ -423,7 +442,8 @@ Examples embedded in the repo: example: ```sh - pggen gen go \ + go tool pggen gen go \ + --postgres-connection "$PGURL" \ --schema-glob example/custom_types/schema.sql \ --query-glob example/custom_types/query.sql \ --go-type 'int8=*int' \ @@ -559,7 +579,8 @@ SELECT * FROM author WHERE first_name = pggen.arg('first_name'); Second, use pggen to generate Go code to `author/query.sql.go`: ```bash -pggen gen go \ +go tool pggen gen go \ + --postgres-connection "$PGURL" \ --schema-glob author/schema.sql \ --query-glob author/query.sql ``` diff --git a/Taskfile.yaml b/Taskfile.yaml index f304a20b..9cc8f548 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -1,4 +1,4 @@ -version: '3' +version: 3 vars: VERSION: @@ -62,7 +62,7 @@ tasks: cmd: psql --host=127.0.0.1 --port=5555 --username=postgres pggen test: - desc: Run the test suite through pgdocker + desc: Run the test suite through test-only pgdocker run: once cmd: go run ./internal/pgdocker/cmd/pgdocker -- go test ./... sources: @@ -70,6 +70,13 @@ tasks: - "**/*.sql" - go.sum + timescale-test: + desc: Run opt-in generation test against a downstream Timescale server + preconditions: + - sh: test -n "$TIMESCALE_PGURL" + msg: TIMESCALE_PGURL must contain a Timescale PostgreSQL connection string + cmd: go test -tags=timescale_test -run '^TestTimescaleGeneration$' . + acceptance-test: desc: Run acceptance tests run: once diff --git a/cmd/pggen/pggen.go b/cmd/pggen/pggen.go index 98a86ba3..1328155d 100644 --- a/cmd/pggen/pggen.go +++ b/cmd/pggen/pggen.go @@ -29,19 +29,18 @@ const flagHelp = `pggen generates type-safe code from files containing Postgres the queries on Postgres to get type information. EXAMPLES - # Generate code for a single query file using an existing postgres database. + # Generate code from a database that already contains the required schema. pggen gen go --query-glob author/queries.sql --postgres-connection "user=postgres port=5555 dbname=pggen" - # Generate code using Docker to create the postgres database with a schema - # file. --schema-glob arg implies using Dockerized postgres. - pggen gen go --schema-glob author/schema.sql --query-glob author/queries.sql + # Load schema files transactionally on the supplied database for inference. + pggen gen go --postgres-connection "user=postgres port=5555 dbname=pggen" --schema-glob author/schema.sql --query-glob author/queries.sql # Generate code for all queries underneath a directory. Glob should be quoted # to prevent shell expansion. - pggen gen go --schema-glob author/schema.sql --query-glob 'author/**/*.sql' + pggen gen go --postgres-connection "user=postgres port=5555 dbname=pggen" --schema-glob author/schema.sql --query-glob 'author/**/*.sql' # Use custom acronym when converting from camel_case_api to camelCaseAPI. - pggen gen go --schema-glob schema.sql --query-glob query.sql --acronym api + pggen gen go --postgres-connection "user=postgres port=5555 dbname=pggen" --schema-glob schema.sql --query-glob query.sql --acronym api ` func run() error { @@ -83,13 +82,12 @@ func newGenCmd() *ffcli.Command { outputDir := fset.String("output-dir", "", "where to write generated code; defaults to same directory as query files") postgresConn := fset.String("postgres-connection", "", - `optional connection string to a postgres database, like: `+ + `required connection string to a postgres database, like: `+ `"user=postgres host=localhost dbname=pggen"`) queryGlobs := flags.Strings(fset, "query-glob", nil, "generate code for all SQL files that match glob, like 'queries/**/*.sql'") schemaGlobs := flags.Strings(fset, "schema-glob", nil, - "create schema in Postgres from all sql, sql.gz, or shell "+ - "scripts (*.sh) that match a glob, like 'migrations/*.sql'") + "load all matching .sql files transactionally for inference, like 'migrations/*.sql'") acronyms := flags.Strings(fset, "acronym", nil, "lowercase acronym that should convert to all caps like 'api', "+ "or custom mapping like 'apis=APIs'") @@ -100,23 +98,29 @@ func newGenCmd() *ffcli.Command { "number of params (inclusive) to inline when calling querier methods; 0 always generates a struct") goSubCmd := &ffcli.Command{ Name: "go", - ShortUsage: "pggen gen go --query-glob glob [--schema-glob ]... [flags]", + ShortUsage: "pggen gen go --postgres-connection connection --query-glob glob [--schema-glob glob]... [flags]", ShortHelp: "generates go code for Postgres query files", FlagSet: fset, LongHelp: flagHelp + "\n" + texts.Dedent(` - pggen uses the provided --postgres-connection to query the database. If not - present, pggen creates a Docker container to query the database. + pggen always uses --postgres-connection for inference. Without + --schema-glob, the connected database must already contain the schema. + With --schema-glob, pggen loads the SQL files in a transaction on the + connected database and rolls it back after inference. Extension binaries + used by those files must already exist on that Postgres server. `), Exec: func(ctx context.Context, args []string) error { // Preconditions. + if *postgresConn == "" { + return fmt.Errorf("pggen gen go: --postgres-connection is required") + } if len(*queryGlobs) == 0 { - return fmt.Errorf("pggen gen go: at least one file in --query-glob must match") + return fmt.Errorf("pggen gen go: at least one --query-glob is required") } - queries, err := expandSortGlobs(*queryGlobs) + queries, err := expandSortGlobs("--query-glob", *queryGlobs) if err != nil { return err } - schemas, err := expandSortGlobs(*schemaGlobs) + schemas, err := expandSortGlobs("--schema-glob", *schemaGlobs) if err != nil { return err } @@ -202,14 +206,17 @@ func newGenCmd() *ffcli.Command { // files lexicographically within each glob but not across all globs. The order // of a glob relative to other globs is important for schemas where a schema // might depend on a previous schema. -func expandSortGlobs(globs []string) ([]string, error) { +func expandSortGlobs(flagName string, globs []string) ([]string, error) { files := make([]string, 0, len(globs)*4) for _, glob := range globs { var matches []string if !strings.ContainsAny(glob, "*?[{") { // A regular file, not a glob. Check if it exists. - if _, err := os.Stat(glob); os.IsNotExist(err) { - return nil, fmt.Errorf("file does not exist: %w", err) + if _, err := os.Stat(glob); err != nil { + if os.IsNotExist(err) { + return nil, fmt.Errorf("%s %q does not exist", flagName, glob) + } + return nil, fmt.Errorf("stat %s %q: %w", flagName, glob, err) } matches = append(matches, glob) } else { @@ -221,6 +228,9 @@ func expandSortGlobs(globs []string) ([]string, error) { sort.Strings(ms) matches = ms } + if len(matches) == 0 { + return nil, fmt.Errorf("%s %q matched no files", flagName, glob) + } files = append(files, matches...) } for i, schema := range files { diff --git a/cmd/pggen/pggen_test.go b/cmd/pggen/pggen_test.go new file mode 100644 index 00000000..6b84169b --- /dev/null +++ b/cmd/pggen/pggen_test.go @@ -0,0 +1,65 @@ +package main + +import ( + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenGoValidation(t *testing.T) { + tests := []struct { + name string + args []string + wantErr string + contains bool + }{ + { + name: "missing connection", + args: []string{"go"}, + wantErr: "pggen gen go: --postgres-connection is required", + }, + { + name: "missing query glob", + args: []string{"go", "--postgres-connection", "host=localhost"}, + wantErr: "pggen gen go: at least one --query-glob is required", + }, + { + name: "unmatched query glob", + args: []string{ + "go", + "--postgres-connection", "host=localhost", + "--query-glob", filepath.Join(t.TempDir(), "*.sql"), + }, + wantErr: "matched no files", + contains: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := newGenCmd().ParseAndRun(t.Context(), tt.args) + if tt.contains { + assert.ErrorContains(t, err, tt.wantErr) + } else { + assert.EqualError(t, err, tt.wantErr) + } + }) + } +} + +func TestGenGoHelpDescribesDownstreamConnectionModes(t *testing.T) { + genCmd := newGenCmd() + goCmd := genCmd.Subcommands[0] + assert.Contains(t, goCmd.ShortUsage, "--postgres-connection connection") + assert.Contains(t, goCmd.LongHelp, "always uses --postgres-connection") + assert.Contains(t, goCmd.LongHelp, "loads the SQL files in a transaction") + assert.NotContains(t, strings.ToLower(goCmd.LongHelp), "docker") + assert.Contains(t, goCmd.FlagSet.Lookup("postgres-connection").Usage, "required") +} + +func TestExpandSortGlobsRejectsEachUnmatchedGlob(t *testing.T) { + tmpDir := t.TempDir() + _, err := expandSortGlobs("--schema-glob", []string{filepath.Join(tmpDir, "*.sql")}) + assert.EqualError(t, err, "--schema-glob \""+filepath.Join(tmpDir, "*.sql")+"\" matched no files") +} diff --git a/docker-compose.yml b/docker-compose.yml index 5f0b9616..824b6754 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,8 @@ -version: "3.6" services: postgresql: - image: "postgres:15" + image: postgres:18-alpine ports: - - "5555:5432" + - 5555:5432 restart: always command: ["postgres", "-c", "log_statement=all"] environment: diff --git a/generate.go b/generate.go index 04003ebf..cb6a7743 100644 --- a/generate.go +++ b/generate.go @@ -14,12 +14,17 @@ import ( "github.com/meoyawn/pggen/internal/ast" "github.com/meoyawn/pggen/internal/codegen" "github.com/meoyawn/pggen/internal/codegen/golang" - "github.com/meoyawn/pggen/internal/errs" "github.com/meoyawn/pggen/internal/parser" - "github.com/meoyawn/pggen/internal/pgdocker" "github.com/meoyawn/pggen/internal/pginfer" ) +const cleanupTimeout = 30 * time.Second + +type schemaFile struct { + path string + sql string +} + // Lang is a supported codegen language. type Lang string @@ -44,8 +49,8 @@ type GenerateOptions struct { ConnString string // Generate code for each of the SQL query file paths. QueryFiles []string - // Schema files to run on Postgres init. Can be *.sql, *.sql.gz, or executable - // *.sh files . + // SQL schema files to load in a transaction on the database in ConnString. + // The transaction is rolled back after inference. SchemaFiles []string // The name of the Go package for the file. If empty, defaults to the // directory name. @@ -69,7 +74,7 @@ type GenerateOptions struct { // ast.SourceQuery in opts.QueryFiles. // // Generate must only be called once per output directory. -func Generate(opts GenerateOptions) (mErr error) { +func Generate(opts GenerateOptions) error { // Preconditions. if opts.Language == "" { return fmt.Errorf("generate language must be set; got empty string") @@ -77,24 +82,34 @@ func Generate(opts GenerateOptions) (mErr error) { if len(opts.QueryFiles) == 0 { return fmt.Errorf("got 0 query files, at least 1 must be set") } + if opts.ConnString == "" { + return fmt.Errorf("generate postgres connection must be set; got empty string") + } if opts.OutputDir == "" { return fmt.Errorf("output dir must be set") } + schemaFiles, err := readSchemaFiles(opts.SchemaFiles) + if err != nil { + return err + } // Postgres connection. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() - pgConn, errEnricher, cleanup, err := connectPostgres(ctx, opts) + pgConn, cleanup, err := connectPostgres(ctx, opts.ConnString, schemaFiles) if err != nil { return fmt.Errorf("connect postgres: %w", err) } - defer errs.Capture(&mErr, cleanup, "close postgres connection") // Parse queries. inferrer := pginfer.NewInferrer(pgConn) queryFiles, err := parseQueryFiles(opts.QueryFiles, inferrer) + cleanupErr := cleanup() if err != nil { - return errEnricher(err) + return errors.Join(err, cleanupErr) + } + if cleanupErr != nil { + return fmt.Errorf("clean up postgres inference session: %w", cleanupErr) } // Codegen. @@ -122,60 +137,82 @@ func Generate(opts GenerateOptions) (mErr error) { return nil } -// connectPostgres connects to postgres using connString if given or by -// running a Docker postgres container and connecting to that. -func connectPostgres(ctx context.Context, opts GenerateOptions) (*pgx.Conn, func(error) error, func() error, error) { - // Create connection by starting dockerized Postgres. - if opts.ConnString == "" { - client, err := pgdocker.Start(ctx, opts.SchemaFiles) - if err != nil { - return nil, nil, nil, fmt.Errorf("start dockerized postgres: %w", err) - } - stopDocker := func() error { return client.Stop(ctx) } - connStr, err := client.ConnString() - if err != nil { - return nil, nil, nil, fmt.Errorf("get dockerized postgres conn string: %w", err) +func readSchemaFiles(paths []string) ([]schemaFile, error) { + files := make([]schemaFile, len(paths)) + for i, path := range paths { + if filepath.Ext(path) != ".sql" { + return nil, fmt.Errorf("schema file %q must have .sql extension", path) } - pgConn, err := pgx.Connect(ctx, connStr) + contents, err := os.ReadFile(path) if err != nil { - return nil, nil, nil, fmt.Errorf("connect to pggen dockerized postgres database: %w", err) + return nil, fmt.Errorf("read schema file %q: %w", path, err) } - errEnricher := func(e error) error { - if e == nil { - return nil - } - logs, err := client.GetContainerLogs() - if err != nil { - return errors.Join(e, err) - } - return fmt.Errorf("Container logs for Postgres container:\n\n%s\n\n%w", logs, e) - } - return pgConn, errEnricher, stopDocker, nil + files[i] = schemaFile{path: path, sql: string(contents)} + } + return files, nil +} + +// connectPostgres connects to the configured database. Schema files are loaded +// in a transaction that cleanup rolls back after inference. +func connectPostgres( + ctx context.Context, + connString string, + schemaFiles []schemaFile, +) (*pgx.Conn, func() error, error) { + config, err := pgx.ParseConfig(connString) + if err != nil { + return nil, nil, fmt.Errorf("parse postgres connection: invalid connection string") } - // Use existing Postgres. - nopCleanup := func() error { return nil } - nopErrEnricher := func(e error) error { return e } - pgConn, err := pgx.Connect(ctx, opts.ConnString) + conn, err := pgx.ConnectConfig(ctx, config) if err != nil { - return nil, nil, nil, fmt.Errorf("connect to pggen postgres database: %w", err) - } - // Run SQL init scripts. pgdocker runs these in the other case by copying - // the files into the entrypoint folder. Emulate the behavior for a subset of - // supported files. - for _, script := range opts.SchemaFiles { - if filepath.Ext(script) != ".sql" { - return nil, nopErrEnricher, nopCleanup, fmt.Errorf("cannot run non-sql schema file on Postgres "+ - "(*.sh and *.sql.gz files only supported without --postgres-connection): %s", script) - } - bs, err := os.ReadFile(script) - if err != nil { - return nil, nil, nopCleanup, fmt.Errorf("read schema file: %w", err) - } - if _, err := pgConn.Exec(ctx, string(bs)); err != nil { - return nil, nopErrEnricher, nopCleanup, fmt.Errorf("load schema file into Postgres: %w", err) + return nil, nil, fmt.Errorf("connect to postgres database: %w", err) + } + if len(schemaFiles) == 0 { + return conn, func() error { return closeConnection(conn) }, nil + } + + cleanup := schemaTransactionCleanup(conn) + if _, err := conn.Exec(ctx, "BEGIN"); err != nil { + return nil, nil, errors.Join( + fmt.Errorf("begin schema inference transaction: %w", err), + closeConnection(conn), + ) + } + for _, file := range schemaFiles { + if _, err := conn.Exec(ctx, file.sql); err != nil { + return nil, nil, errors.Join( + fmt.Errorf("load schema file %q: %w", file.path, err), + cleanup(), + ) } } - return pgConn, nopErrEnricher, nopCleanup, nil + return conn, cleanup, nil +} + +func schemaTransactionCleanup(conn *pgx.Conn) func() error { + return func() error { + cleanupCtx, cancel := context.WithTimeout(context.Background(), cleanupTimeout) + defer cancel() + _, rollbackErr := conn.Exec(cleanupCtx, "ROLLBACK") + closeErr := conn.Close(cleanupCtx) + return errors.Join( + wrapCleanupError(rollbackErr, "roll back schema inference transaction"), + wrapCleanupError(closeErr, "close postgres inference connection"), + ) + } +} + +func closeConnection(conn *pgx.Conn) error { + cleanupCtx, cancel := context.WithTimeout(context.Background(), cleanupTimeout) + defer cancel() + return wrapCleanupError(conn.Close(cleanupCtx), "close postgres inference connection") +} + +func wrapCleanupError(err error, message string) error { + if err == nil { + return nil + } + return fmt.Errorf("%s: %w", message, err) } func parseQueryFiles(queryFiles []string, inferrer *pginfer.Inferrer) ([]codegen.QueryFile, error) { diff --git a/generate_test.go b/generate_test.go index 709a3266..f77c9296 100644 --- a/generate_test.go +++ b/generate_test.go @@ -1,10 +1,12 @@ package pggen import ( + "context" "os" "path/filepath" "testing" + "github.com/jackc/pgx/v5" "github.com/meoyawn/pggen/internal/pgtest" "github.com/meoyawn/pggen/internal/texts" "github.com/stretchr/testify/assert" @@ -76,3 +78,153 @@ func TestGenerate_Golang_Error(t *testing.T) { }) } } + +func TestGenerateRequiresConnection(t *testing.T) { + tmpDir := t.TempDir() + err := Generate(GenerateOptions{ + Language: LangGo, + QueryFiles: []string{filepath.Join(tmpDir, "query.sql")}, + OutputDir: tmpDir, + }) + assert.EqualError(t, err, "generate postgres connection must be set; got empty string") +} + +func TestGenerateRejectsNonSQLSchemaBeforeConnecting(t *testing.T) { + tmpDir := t.TempDir() + schemaPath := filepath.Join(tmpDir, "schema.sh") + if err := os.WriteFile(schemaPath, []byte("exit 1"), 0o600); err != nil { + t.Fatal(err) + } + err := Generate(GenerateOptions{ + Language: LangGo, + ConnString: "host=invalid.invalid", + QueryFiles: []string{filepath.Join(tmpDir, "query.sql")}, + SchemaFiles: []string{schemaPath}, + OutputDir: tmpDir, + }) + assert.EqualError(t, err, "schema file \""+schemaPath+"\" must have .sql extension") +} + +func TestGenerateDoesNotExposeMalformedConnectionSecret(t *testing.T) { + tmpDir := t.TempDir() + secret := t.Name() + err := Generate(GenerateOptions{ + Language: LangGo, + ConnString: "postgres://user:" + secret + "@localhost/%zz", + QueryFiles: []string{filepath.Join(tmpDir, "query.sql")}, + OutputDir: tmpDir, + }) + assert.EqualError(t, err, "connect postgres: parse postgres connection: invalid connection string") + assert.NotContains(t, err.Error(), secret) +} + +func TestGenerateSchemaTransactionLifecycle(t *testing.T) { + conn, cleanupFunc := pgtest.NewPostgresSchemaString(t, "") + defer cleanupFunc() + tmpDir := t.TempDir() + schemaPath := filepath.Join(tmpDir, "schema.sql") + queryPath := filepath.Join(tmpDir, "query.sql") + if err := os.WriteFile(schemaPath, []byte(`CREATE TABLE lifecycle_items (id bigint PRIMARY KEY);`), 0o600); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(queryPath, []byte(` +-- name: ListLifecycleItems :many +SELECT id FROM lifecycle_items WHERE id > pggen.arg('minimum_id'); +`), 0o600); err != nil { + t.Fatal(err) + } + opts := GenerateOptions{ + Language: LangGo, + ConnString: conn.Config().ConnString(), + QueryFiles: []string{queryPath}, + SchemaFiles: []string{schemaPath}, + OutputDir: tmpDir, + GoPackage: "lifecycletest", + } + for range 2 { + if err := Generate(opts); err != nil { + t.Fatal(err) + } + assertRelationMissing(t, conn, "lifecycle_items") + } + if _, err := os.Stat(filepath.Join(tmpDir, "query.sql.go")); err != nil { + t.Fatalf("stat generated file: %s", err) + } +} + +func TestGenerateSchemaTransactionCleanupAfterErrors(t *testing.T) { + tests := []struct { + name string + schema string + query string + missingOut bool + wantError string + }{ + { + name: "schema", + schema: `CREATE TABLE cleanup_items (id bigint); SELECT broken syntax;`, + query: `-- name: ListItems :many` + "\n" + `SELECT id FROM cleanup_items;`, + wantError: "load schema file", + }, + { + name: "inference", + schema: `CREATE TABLE cleanup_items (id bigint);`, + query: `-- name: ListItems :many` + "\n" + `SELECT missing_column FROM cleanup_items;`, + wantError: "infer typed named query", + }, + { + name: "code generation", + schema: `CREATE TABLE cleanup_items (id bigint);`, + query: `-- name: ListItems :many` + "\n" + `SELECT id FROM cleanup_items;`, + missingOut: true, + wantError: "generate go code", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + conn, cleanupFunc := pgtest.NewPostgresSchemaString(t, "") + defer cleanupFunc() + tmpDir := t.TempDir() + schemaPath := filepath.Join(tmpDir, "schema.sql") + queryPath := filepath.Join(tmpDir, "query.sql") + if err := os.WriteFile(schemaPath, []byte(tt.schema), 0o600); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(queryPath, []byte(tt.query), 0o600); err != nil { + t.Fatal(err) + } + outputDir := tmpDir + if tt.missingOut { + outputDir = filepath.Join(tmpDir, "missing") + } + err := Generate(GenerateOptions{ + Language: LangGo, + ConnString: conn.Config().ConnString(), + QueryFiles: []string{queryPath}, + SchemaFiles: []string{schemaPath}, + OutputDir: outputDir, + GoPackage: "cleanuptest", + }) + if err == nil { + t.Fatal("expected generation error") + } + assert.Contains(t, err.Error(), tt.wantError) + assertRelationMissing(t, conn, "cleanup_items") + }) + } +} + +func assertRelationMissing( + t *testing.T, + conn interface { + QueryRow(context.Context, string, ...any) pgx.Row + }, + relation string, +) { + t.Helper() + var exists bool + if err := conn.QueryRow(t.Context(), `SELECT to_regclass($1) IS NOT NULL`, relation).Scan(&exists); err != nil { + t.Fatalf("check relation cleanup: %s", err) + } + assert.False(t, exists, "relation %s should be rolled back", relation) +} diff --git a/internal/pgdocker/pgdocker.go b/internal/pgdocker/pgdocker.go index 2ca854fd..0005dd12 100644 --- a/internal/pgdocker/pgdocker.go +++ b/internal/pgdocker/pgdocker.go @@ -1,5 +1,4 @@ -// Package pgdocker creates one-off Postgres docker images to use so pggen can -// introspect the schema. +// Package pgdocker provides Postgres containers for tests. package pgdocker import ( @@ -33,7 +32,7 @@ type Client struct { connString string } -// Start builds a Docker image and runs the image in a container. +// Start builds a Postgres test image and runs it in a container. func Start(ctx context.Context, initScripts []string) (client *Client, mErr error) { now := time.Now() dockerCl, err := dockerClient.NewClientWithOpts(dockerClient.FromEnv) @@ -216,7 +215,7 @@ func (c *Client) runContainer(ctx context.Context, imageID string) (string, port PortBindings: nat.PortMap{ "5432/tcp": []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: strconv.Itoa(port)}}, }, - Tmpfs: map[string]string{"/var/lib/postgresql/data": ""}, + Tmpfs: map[string]string{"/var/lib/postgresql": ""}, } resp, err := c.docker.ContainerCreate(ctx, containerCfg, hostCfg, nil, nil, "") if err != nil { diff --git a/internal/pgdocker/template.go b/internal/pgdocker/template.go index 11d9b756..75dcfe87 100644 --- a/internal/pgdocker/template.go +++ b/internal/pgdocker/template.go @@ -8,7 +8,7 @@ type pgTemplate struct { const dockerfileTemplate = ` {{- /*gotype: github.com/meoyawn/pggen/internal/pgdocker.pgTemplate*/ -}} {{- define "dockerfile" -}} -FROM postgres:13 +FROM timescale/timescaledb:2.28.3-pg18 {{ range .InitScripts }} COPY {{.}} /docker-entrypoint-initdb.d/ {{ end }} diff --git a/timescale_integration_test.go b/timescale_integration_test.go new file mode 100644 index 00000000..2f102a3c --- /dev/null +++ b/timescale_integration_test.go @@ -0,0 +1,102 @@ +//go:build timescale_test + +package pggen + +import ( + "bytes" + "context" + "fmt" + "math/rand/v2" + "os" + "os/exec" + "path/filepath" + "strconv" + "testing" + "time" + + "github.com/jackc/pgx/v5" + "github.com/stretchr/testify/assert" +) + +func TestTimescaleGeneration(t *testing.T) { + connString := os.Getenv("TIMESCALE_PGURL") + if connString == "" { + t.Skip("TIMESCALE_PGURL is not set") + } + tableName := "pggen_timescale_" + strconv.FormatUint(rand.Uint64(), 36) //nolint:gosec + tmpDir := t.TempDir() + schemaPath := filepath.Join(tmpDir, "schema.sql") + queryPath := filepath.Join(tmpDir, "query.sql") + schema := fmt.Sprintf(` +CREATE EXTENSION IF NOT EXISTS timescaledb; +CREATE TABLE %s ( + recorded_at TIMESTAMPTZ NOT NULL, + value DOUBLE PRECISION NOT NULL +); +SELECT create_hypertable('%s', 'recorded_at'); +`, tableName, tableName) + query := fmt.Sprintf(` +-- name: ReadMetrics :many +SELECT recorded_at, value +FROM %s +WHERE recorded_at >= pggen.arg('since') +ORDER BY recorded_at; +`, tableName) + if err := os.WriteFile(schemaPath, []byte(schema), 0o600); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(queryPath, []byte(query), 0o600); err != nil { + t.Fatal(err) + } + if err := Generate(GenerateOptions{ + Language: LangGo, + ConnString: connString, + QueryFiles: []string{queryPath}, + SchemaFiles: []string{schemaPath}, + OutputDir: tmpDir, + GoPackage: "timescaletest", + }); err != nil { + t.Fatal(err) + } + + generatedPath := filepath.Join(tmpDir, "query.sql.go") + generated, err := os.ReadFile(generatedPath) + if err != nil { + t.Fatalf("read generated Go: %s", err) + } + assert.Contains(t, string(generated), "Since pgtype.Timestamptz") + assert.Contains(t, string(generated), "RecordedAt pgtype.Timestamptz") + compileGeneratedGo(t, generatedPath) + + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) + defer cancel() + conn, err := pgx.Connect(ctx, connString) + if err != nil { + t.Fatalf("connect to verify schema rollback: %s", err) + } + defer func() { + if err := conn.Close(ctx); err != nil { + t.Errorf("close verification connection: %s", err) + } + }() + var exists bool + if err := conn.QueryRow(ctx, `SELECT to_regclass($1) IS NOT NULL`, tableName).Scan(&exists); err != nil { + t.Fatalf("check Timescale table cleanup: %s", err) + } + assert.False(t, exists, "Timescale schema transaction should be rolled back") +} + +func compileGeneratedGo(t *testing.T, generatedPath string) { + t.Helper() + goBin, err := exec.LookPath("go") + if err != nil { + t.Fatalf("look up go: %s", err) + } + cmd := exec.Command(goBin, "test", generatedPath) + cmd.Dir = "." + cmd.Env = os.Environ() + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("compile generated Go: %s\n%s", err, bytes.TrimSpace(output)) + } +}