diff --git a/README.md b/README.md index 1f3cd185..4448eb23 100644 --- a/README.md +++ b/README.md @@ -172,3 +172,7 @@ pgerrcode contains constants for the PostgreSQL error codes. ### [github.com/georgysavva/scany](https://github.com/georgysavva/scany) Library for scanning data from a database into Go structs and more. + +### [https://github.com/otan/gopgkrb5](https://github.com/otan/gopgkrb5) + +Adds GSSAPI / Kerberos authentication support. diff --git a/conn.go b/conn.go index ca34cdf3..d0c9fe33 100644 --- a/conn.go +++ b/conn.go @@ -388,7 +388,8 @@ func (c *Conn) Exec(ctx context.Context, sql string, arguments ...any) (pgconn.C commandTag, err := c.exec(ctx, sql, arguments...) if err != nil { if c.shouldLog(LogLevelError) { - c.log(ctx, LogLevelError, "Exec", map[string]any{"sql": sql, "args": logQueryArgs(arguments), "err": err}) + endTime := time.Now() + c.log(ctx, LogLevelError, "Exec", map[string]any{"sql": sql, "args": logQueryArgs(arguments), "err": err, "time": endTime.Sub(startTime)}) } return commandTag, err } diff --git a/internal/sanitize/sanitize.go b/internal/sanitize/sanitize.go index 64e67ca6..3b7bb41f 100644 --- a/internal/sanitize/sanitize.go +++ b/internal/sanitize/sanitize.go @@ -246,7 +246,7 @@ func oneLineCommentState(l *sqlLexer) stateFn { case '\\': _, width = utf8.DecodeRuneInString(l.src[l.pos:]) l.pos += width - case '\n': + case '\n', '\r': return rawState case utf8.RuneError: if l.pos-l.start > 0 { diff --git a/internal/sanitize/sanitize_test.go b/internal/sanitize/sanitize_test.go index 7b4c08ef..9b5800ec 100644 --- a/internal/sanitize/sanitize_test.go +++ b/internal/sanitize/sanitize_test.go @@ -77,12 +77,16 @@ func TestNewQuery(t *testing.T) { expected: sanitize.Query{Parts: []sanitize.Part{"select -- a baby's toy\n'barbie', ", 1}}, }, { - sql: "select 42 -- is a Thinker's favorite number", - expected: sanitize.Query{Parts: []sanitize.Part{"select 42 -- is a Thinker's favorite number"}}, + sql: "select 42 -- is a Deep Thought's favorite number", + expected: sanitize.Query{Parts: []sanitize.Part{"select 42 -- is a Deep Thought's favorite number"}}, }, { - sql: "select 42, -- \\nis a Thinker's favorite number\n$1", - expected: sanitize.Query{Parts: []sanitize.Part{"select 42, -- \\nis a Thinker's favorite number\n", 1}}, + sql: "select 42, -- \\nis a Deep Thought's favorite number\n$1", + expected: sanitize.Query{Parts: []sanitize.Part{"select 42, -- \\nis a Deep Thought's favorite number\n", 1}}, + }, + { + sql: "select 42, -- \\nis a Deep Thought's favorite number\r$1", + expected: sanitize.Query{Parts: []sanitize.Part{"select 42, -- \\nis a Deep Thought's favorite number\r", 1}}, }, } diff --git a/pgxpool/pool.go b/pgxpool/pool.go index 6e50198d..0446a245 100644 --- a/pgxpool/pool.go +++ b/pgxpool/pool.go @@ -112,7 +112,7 @@ type Config struct { // MaxConnIdleTime is the duration after which an idle connection will be automatically closed by the health check. MaxConnIdleTime time.Duration - // MaxConns is the maximum size of the pool. + // MaxConns is the maximum size of the pool. The default is the greater of 4 or runtime.NumCPU(). MaxConns int32 // MinConns is the minimum size of the pool. The health check will increase the number of connections to this diff --git a/pgxpool/pool_test.go b/pgxpool/pool_test.go index b58791fd..a6d0a083 100644 --- a/pgxpool/pool_test.go +++ b/pgxpool/pool_test.go @@ -415,7 +415,14 @@ func TestPoolBackgroundChecksMaxConnIdleTime(t *testing.T) { c, err := db.Acquire(context.Background()) require.NoError(t, err) c.Release() - time.Sleep(config.HealthCheckPeriod + 500*time.Millisecond) + time.Sleep(config.HealthCheckPeriod) + + for i := 0; i < 1000; i++ { + if db.Stat().TotalConns() == 0 { + break + } + time.Sleep(time.Millisecond) + } stats := db.Stat() assert.EqualValues(t, 0, stats.TotalConns()) diff --git a/tx.go b/tx.go index 8a078d1a..7254e3dc 100644 --- a/tx.go +++ b/tx.go @@ -192,7 +192,7 @@ func (tx *dbTx) Begin(ctx context.Context) (Tx, error) { return nil, err } - return &dbSavepoint{tx: tx, savepointNum: tx.savepointNum}, nil + return &dbSimulatedNestedTx{tx: tx, savepointNum: tx.savepointNum}, nil } func (tx *dbTx) BeginFunc(ctx context.Context, f func(Tx) error) (err error) { @@ -329,15 +329,15 @@ func (tx *dbTx) Conn() *Conn { return tx.conn } -// dbSavepoint represents a nested transaction implemented by a savepoint. -type dbSavepoint struct { +// dbSimulatedNestedTx represents a simulated nested transaction implemented by a savepoint. +type dbSimulatedNestedTx struct { tx Tx savepointNum int64 closed bool } // Begin starts a pseudo nested transaction implemented with a savepoint. -func (sp *dbSavepoint) Begin(ctx context.Context) (Tx, error) { +func (sp *dbSimulatedNestedTx) Begin(ctx context.Context) (Tx, error) { if sp.closed { return nil, ErrTxClosed } @@ -345,7 +345,7 @@ func (sp *dbSavepoint) Begin(ctx context.Context) (Tx, error) { return sp.tx.Begin(ctx) } -func (sp *dbSavepoint) BeginFunc(ctx context.Context, f func(Tx) error) (err error) { +func (sp *dbSimulatedNestedTx) BeginFunc(ctx context.Context, f func(Tx) error) (err error) { if sp.closed { return ErrTxClosed } @@ -354,7 +354,7 @@ func (sp *dbSavepoint) BeginFunc(ctx context.Context, f func(Tx) error) (err err } // Commit releases the savepoint essentially committing the pseudo nested transaction. -func (sp *dbSavepoint) Commit(ctx context.Context) error { +func (sp *dbSimulatedNestedTx) Commit(ctx context.Context) error { if sp.closed { return ErrTxClosed } @@ -367,7 +367,7 @@ func (sp *dbSavepoint) Commit(ctx context.Context) error { // Rollback rolls back to the savepoint essentially rolling back the pseudo nested transaction. Rollback will return // ErrTxClosed if the dbSavepoint is already closed, but is otherwise safe to call multiple times. Hence, a defer sp.Rollback() // is safe even if sp.Commit() will be called first in a non-error condition. -func (sp *dbSavepoint) Rollback(ctx context.Context) error { +func (sp *dbSimulatedNestedTx) Rollback(ctx context.Context) error { if sp.closed { return ErrTxClosed } @@ -378,7 +378,7 @@ func (sp *dbSavepoint) Rollback(ctx context.Context) error { } // Exec delegates to the underlying Tx -func (sp *dbSavepoint) Exec(ctx context.Context, sql string, arguments ...any) (commandTag pgconn.CommandTag, err error) { +func (sp *dbSimulatedNestedTx) Exec(ctx context.Context, sql string, arguments ...any) (commandTag pgconn.CommandTag, err error) { if sp.closed { return pgconn.CommandTag{}, ErrTxClosed } @@ -387,7 +387,7 @@ func (sp *dbSavepoint) Exec(ctx context.Context, sql string, arguments ...any) ( } // Prepare delegates to the underlying Tx -func (sp *dbSavepoint) Prepare(ctx context.Context, name, sql string) (*pgconn.StatementDescription, error) { +func (sp *dbSimulatedNestedTx) Prepare(ctx context.Context, name, sql string) (*pgconn.StatementDescription, error) { if sp.closed { return nil, ErrTxClosed } @@ -396,7 +396,7 @@ func (sp *dbSavepoint) Prepare(ctx context.Context, name, sql string) (*pgconn.S } // Query delegates to the underlying Tx -func (sp *dbSavepoint) Query(ctx context.Context, sql string, args ...any) (Rows, error) { +func (sp *dbSimulatedNestedTx) Query(ctx context.Context, sql string, args ...any) (Rows, error) { if sp.closed { // Because checking for errors can be deferred to the *Rows, build one with the error err := ErrTxClosed @@ -407,13 +407,13 @@ func (sp *dbSavepoint) Query(ctx context.Context, sql string, args ...any) (Rows } // QueryRow delegates to the underlying Tx -func (sp *dbSavepoint) QueryRow(ctx context.Context, sql string, args ...any) Row { +func (sp *dbSimulatedNestedTx) QueryRow(ctx context.Context, sql string, args ...any) Row { rows, _ := sp.Query(ctx, sql, args...) return (*connRow)(rows.(*connRows)) } // QueryFunc delegates to the underlying Tx. -func (sp *dbSavepoint) QueryFunc(ctx context.Context, sql string, args []any, scans []any, f func(QueryFuncRow) error) (pgconn.CommandTag, error) { +func (sp *dbSimulatedNestedTx) QueryFunc(ctx context.Context, sql string, args []any, scans []any, f func(QueryFuncRow) error) (pgconn.CommandTag, error) { if sp.closed { return pgconn.CommandTag{}, ErrTxClosed } @@ -422,7 +422,7 @@ func (sp *dbSavepoint) QueryFunc(ctx context.Context, sql string, args []any, sc } // CopyFrom delegates to the underlying *Conn -func (sp *dbSavepoint) CopyFrom(ctx context.Context, tableName Identifier, columnNames []string, rowSrc CopyFromSource) (int64, error) { +func (sp *dbSimulatedNestedTx) CopyFrom(ctx context.Context, tableName Identifier, columnNames []string, rowSrc CopyFromSource) (int64, error) { if sp.closed { return 0, ErrTxClosed } @@ -431,7 +431,7 @@ func (sp *dbSavepoint) CopyFrom(ctx context.Context, tableName Identifier, colum } // SendBatch delegates to the underlying *Conn -func (sp *dbSavepoint) SendBatch(ctx context.Context, b *Batch) BatchResults { +func (sp *dbSimulatedNestedTx) SendBatch(ctx context.Context, b *Batch) BatchResults { if sp.closed { return &batchResults{err: ErrTxClosed} } @@ -439,10 +439,10 @@ func (sp *dbSavepoint) SendBatch(ctx context.Context, b *Batch) BatchResults { return sp.tx.SendBatch(ctx, b) } -func (sp *dbSavepoint) LargeObjects() LargeObjects { +func (sp *dbSimulatedNestedTx) LargeObjects() LargeObjects { return LargeObjects{tx: sp} } -func (sp *dbSavepoint) Conn() *Conn { +func (sp *dbSimulatedNestedTx) Conn() *Conn { return sp.tx.Conn() }