postgres

package module
v1.1.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 19, 2026 License: MIT Imports: 21 Imported by: 0

README

Go PostgreSQL Library

A PostgreSQL library built on top of sqlx with focus on performance, security, and ease of use.

✨ Features

  • Connection Pooling: Optimal connection pool configuration
  • Transaction Support: Pipeline transactions with automatic rollback
  • Memory Safe: Prepared statements automatically closed to prevent memory leaks
  • Type Safety: Custom types for PostgreSQL arrays
  • Parameter Binding: Named parameters to prevent SQL injection
  • Pagination: High-performance Offset and Cursor-based pagination with generic types
  • Debug Mode: Query debugging with parameter substitution for development

🚀 Quick Start

package main

import (
    "context"
    "log"
    "time"
    
    "github.com/andryhardiyanto/go-postgres"
)

func main() {
    // Initialize database
    db, err := postgres.New(
        postgres.WithHost("localhost"),
        postgres.WithPort(5432),
        postgres.WithUser("username"),
        postgres.WithPassword("password"),
        postgres.WithDBName("mydb"),
        postgres.WithSSLMode("disable"),
        postgres.WithDriverName("postgres"),
        postgres.WithMaxOpenConns(25),
        postgres.WithMaxIdleConns(5),
        postgres.WithConnMaxLifetime(5*time.Minute),
        postgres.WithConnMaxIdleTime(1*time.Minute),
    )
    if err != nil {
        log.Fatal(err)
    }

    ctx := context.Background()

    // Select single record
    var user User
    found, err := db.Select("SELECT * FROM users WHERE id = :id", &user, "id", 1).One(ctx)
    if err != nil {
        log.Fatal(err)
    }
    if found {
        log.Printf("User: %+v", user)
    }

    // Select multiple records with debug
    var users []User
    found, err = db.Select("SELECT * FROM users WHERE active = :active", &users, "active", true).Debug().Many(ctx)
    if err != nil {
        log.Fatal(err)
    }

    // Insert with returning ID
    id, err := db.Insert("INSERT INTO users (name, email) VALUES (:name, :email) RETURNING id", 
        "name", "John Doe", 
        "email", "john@example.com").Exec(ctx)
    if err != nil {
        log.Fatal(err)
    }

    // Transaction
    result, err := db.Insert("INSERT INTO users (name) VALUES (:name) RETURNING id", "name", "User1").
        Insert("INSERT INTO profiles (user_id, bio) VALUES (:user_id, :bio)", 
            "user_id", db.FromResult("INSERT INTO users (name) VALUES (:name) RETURNING id"), 
            "bio", "User bio").
        ExecInTx(ctx)
    if err != nil {
        log.Fatal(err)
    }
}

📑 Pagination

The library provides robust pagination support using both Offset and Cursor-based strategies, leveraging Go generics for type safety.

Offset Pagination

Ideal for simple lists where data doesn't change frequently and page jumping is required.

pagination := postgres.NewPagination[User](db)

resp, err := pagination.Offset(ctx, &postgres.RequestPaginationOffset{
    Page:       1,
    Size:       10,
    Query:      "SELECT id, name, email FROM users WHERE status = :status ORDER BY created_at DESC LIMIT :limit OFFSET :offset",
    QueryCount: "SELECT count(*) FROM users WHERE status = :status",
    Kv:         []any{"status", "active", pagination.GetKvLimit(10), pagination.GetKvOffset(1, 10)},
})

log.Printf("Total Pages: %d, Items: %d", resp.TotalPages, len(resp.Items))
Cursor Pagination

Ideal for infinite scroll, large datasets, and real-time feeds where data changes frequently.

pagination := postgres.NewPagination[User](db)

resp, err := pagination.Cursor(ctx, &postgres.RequestPaginationCursor{
    Query:      "SELECT id, name, email, created_at FROM users WHERE status = :status",
    QueryCount: "SELECT count(*) FROM users WHERE status = :status",
    Size:       10,
    Kv:         []any{"status", "active"},
    Sorts: []postgres.SortField{
        {Column: "created_at", Key: "created_at", IsDesc: true},
        {Column: "id", Key: "id", IsDesc: true}, // Always include unique identifier for deterministic sorting
    },
    Token: "", // Pass PreviousToken or NextToken from previous response here
})

if resp.NextToken != nil {
    log.Printf("Next Page Token: %s", *resp.NextToken)
}

📊 Performance Optimizations

1. Connection Pool Configuration
db, err := postgres.New(
    // Optimal for web applications with moderate traffic
    postgres.WithMaxOpenConns(25),        // Max connections
    postgres.WithMaxIdleConns(5),         // Idle connections
    postgres.WithConnMaxLifetime(5*time.Minute),  // Connection lifetime
    postgres.WithConnMaxIdleTime(1*time.Minute),  // Idle timeout
)
2. Context with Timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

found, err := db.Select("SELECT * FROM users WHERE id = :id", &user, "id", 1).One(ctx)
3. Prepared Statements

The library automatically uses prepared statements and closes them to prevent memory leaks.

4. Debug Mode

Enable debug mode for individual queries to see SQL execution:

// Debug a specific select query
found, err := db.Select("SELECT * FROM users WHERE id = :id", &user, "id", 1).Debug().One(ctx)

// Debug an insert query
id, err := db.Insert("INSERT INTO users (name) VALUES (:name) RETURNING id", "name", "John").Debug().Exec(ctx)

🔒 Security Best Practices

1. Parameter Binding

Always use named parameters to prevent SQL injection:

// ✅ CORRECT
db.Select("SELECT * FROM users WHERE email = :email", &user, "email", userEmail)

// ❌ WRONG - vulnerable to SQL injection
db.Select(fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", userEmail), &user)
2. Connection Security
postgres.New(
    postgres.WithSSLMode("require"),  // Use SSL in production
    postgres.WithHost("localhost"),   // Don't expose to public
)

🔧 Error Handling

_, err := db.Insert("INSERT INTO users (email) VALUES (:email)", "email", "duplicate@example.com").Exec(ctx)
if err != nil {
    // Handle database errors
    log.Printf("Database error: %v", err)
    return err
}

🤝 Contributing

We welcome contributions! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

🌟 Support

If you encounter any issues or have questions, please open an issue on GitHub.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Filter

func Filter(slice []string, filterMap map[string]string) (result []string)

Filter filters the slice of strings based on the map.

func NewPipeline

func NewPipeline() *pipeline

NewPipeline creates a new empty pipeline instance. The pipeline can be used to chain multiple database operations that should be executed atomically in a transaction.

func Pairs

func Pairs(keyValuePairs []any) (map[string]any, error)

Pairs converts a slice of key-value pairs to a map.

func PairsHook

func PairsHook(keyValuePairs []any, identifiers map[string]any, hook string) (map[string]any, error)

PairsHook converts a slice of key-value pairs to a map. If the value is a string and starts with the hook, it will be replaced with the value from the ids map.

Types

type CursorToken added in v1.1.5

type CursorToken map[string]any

type Exec

type Exec interface {
	Debug() Exec
	Exec(ctx context.Context) (any, error)
	ExecInTx(ctx context.Context) (result *ExecResult, err error)
	Insert(query string, keyValuePairs ...any) Exec
	Update(query string, keyValuePairs ...any) Exec
	Delete(query string, keyValuePairs ...any) Exec
	Select(query string, destination any, keyValuePairs ...any) Exec
	Wrap(exec Exec) Exec
	FromResult(from string) string
}

type ExecResult

type ExecResult struct {
	// contains filtered or unexported fields
}

ExecResult is the result of an exec query.

func (*ExecResult) TxResult

func (e *ExecResult) TxResult(query string) any

type Option

type Option func(*config)

Option is a function that configures the postgres database.

func WithConnMax

func WithConnMax(maxOpenConns int) Option

WithConnMax sets the max conn. maxOpenConns is the maximum number of open connections to the database.

func WithConnMaxIdleTime

func WithConnMaxIdleTime(maxConnIdleTime time.Duration) Option

WithConnMaxIdleTime sets the max conn idle time. maxConnIdleTime is the maximum amount of time a connection may be idle.

func WithConnMaxLifetime

func WithConnMaxLifetime(maxConnLifetime time.Duration) Option

WithConnMaxLifetime sets the max conn lifetime. maxConnLifetime is the maximum amount of time a connection may be reused.

func WithDBName

func WithDBName(dbName string) Option

WithDBName sets the db name. dbName is the name of the database to connect to.

func WithDriverName

func WithDriverName(driverName string) Option

WithDriverName sets the driver name.

func WithDsn

func WithDsn(dsn string) Option

WithDsn sets the dsn. dsn is the data source name.

func WithHost

func WithHost(host string) Option

WithHost sets the host.

func WithMaxIdleConns

func WithMaxIdleConns(maxIdleConns int) Option

func WithMaxOpenConns

func WithMaxOpenConns(maxOpenConns int) Option

WithMaxOpenConns sets the max open conns. maxOpenConns is the maximum number of open connections to the database.

func WithPassword

func WithPassword(password string) Option

WithPassword sets the password. password is the password to connect with.

func WithPort

func WithPort(port int) Option

WithPort sets the port. port is the port number to connect to.

func WithSSLMode

func WithSSLMode(sslMode string) Option

WithSSLMode sets the ssl mode. sslMode is the SSL mode to use.

func WithUser

func WithUser(user string) Option

WithUser sets the user. user is the username to connect as.

type OptionPagination added in v1.1.6

type OptionPagination[T any] func(*pagination[T])

func WithMaxPage added in v1.1.6

func WithMaxPage[T any](max int) OptionPagination[T]

func WithMaxPageSize added in v1.1.6

func WithMaxPageSize[T any](max int) OptionPagination[T]

func WithMinPage added in v1.1.6

func WithMinPage[T any](min int) OptionPagination[T]

func WithMinPageSize added in v1.1.6

func WithMinPageSize[T any](min int) OptionPagination[T]

type Pagination added in v1.1.5

type Pagination[T any] interface {
	Debug() Pagination[T]
	Offset(ctx context.Context, req *RequestPaginationOffset) (*ResponsePaginationOffset[T], error)
	Cursor(ctx context.Context, req *RequestPaginationCursor) (*ResponsePaginationCursor[T], error)
	GetKvLimit(pageSize int) []any
	GetKvOffset(page, pageSize int) []any
}

func NewPagination added in v1.1.5

func NewPagination[T any](repo Postgres, opts ...OptionPagination[T]) Pagination[T]

type Postgres

type Postgres interface {
	Select(query string, destination any, keyValuePairs ...any) Select
	Insert(query string, keyValuePairs ...any) Exec
	Update(query string, keyValuePairs ...any) Exec
	Delete(query string, keyValuePairs ...any) Exec
	FromResult(from string) string
}

Postgres is the interface for the postgres database client.

func New

func New(opts ...Option) (Postgres, error)

New creates a new postgres client

type RequestGetCursorTokenFromEncoded added in v1.1.5

type RequestGetCursorTokenFromEncoded struct {
	// Token is the base64 encoded string containing the cursor state from a previous request.
	Token string

	// Sorts is a list of sorting definitions that acts as the "Single Source of Truth."
	// This field is automatically used to build two components:
	// 1. The SQL ORDER BY clause.
	// 2. The Row Comparison filter condition (e.g., (col1, col2) > (:val1, :val2)).
	//
	// IMPORTANT: The order in this slice determines sort priority. The last element
	// MUST be a unique column (such as a Primary Key/ID) to ensure deterministic
	// pagination (preventing skipped or duplicate data across pages).
	Sorts []SortField

	// Query is the base SQL query excluding the ORDER BY clause and cursor conditions.
	Query string

	// Kv contains existing query parameters in a key-value pair format (named parameters).
	Kv []any

	// IsCustomCTE should be set to true if the Query uses fmt.Sprintf placeholders (%s).
	// Typically, the first placeholder is for the cursor condition and the second for ORDER BY.
	IsCustomCTE bool

	// Keys is a list of field keys to be extracted from the token.
	// Usually populated automatically based on the 'Key' fields defined in Sorts.
	Keys []string
}

RequestGetCursorTokenFromEncoded holds the parameters required to decode a cursor and rebuild the SQL query for the next or previous page.

type RequestPaginationCursor added in v1.1.5

type RequestPaginationCursor struct {
	Query       string
	QueryCount  string
	Token       string
	Size        int
	IsCustomCTE bool
	Kv          []any
	Sorts       []SortField
}

type RequestPaginationOffset added in v1.1.5

type RequestPaginationOffset struct {
	Page       int
	Size       int
	Query      string
	Kv         []any
	QueryCount string
}

func (*RequestPaginationOffset) GetTotalPages added in v1.1.5

func (r *RequestPaginationOffset) GetTotalPages(total int64) int

type ResponseGetCursorTokenFromEncoded added in v1.1.5

type ResponseGetCursorTokenFromEncoded struct {
	IsPrev        bool
	Query         string
	ReverseResult bool
	CursorToken   map[string]any
	Kv            []any
	Keys          []string
}

type ResponsePaginationCursor added in v1.1.5

type ResponsePaginationCursor[T any] struct {
	PreviousToken *string `json:"previous_token"`
	NextToken     *string `json:"next_token"`
	TotalItems    int64   `json:"total_items"`
	Items         []T     `json:"items"`
}

type ResponsePaginationOffset added in v1.1.5

type ResponsePaginationOffset[T any] struct {
	Page       int   `json:"page"`
	Items      []T   `json:"items"`
	TotalItems int64 `json:"total_items"`
	TotalPages int   `json:"total_pages"`
}

type Select

type Select interface {
	Debug() Select
	One(ctx context.Context) (found bool, err error)
	Many(ctx context.Context) (found bool, err error)
}

Select is an interface for selecting data from the database.

type SortField added in v1.1.5

type SortField struct {
	// Column is the full SQL column name, including table aliases if necessary.
	// Example: "u.created_at" or "p.id".
	Column string

	// Key is the field name used as the key within the JSON map/token.
	// This should match the JSON tag or DB tag on the target struct.
	// Example: "created_at" or "id".
	Key string

	// IsDesc determines the sort direction. If true, it uses DESC; if false, it uses ASC.
	IsDesc bool
}

SortField defines the mapping between a database column and a token key for ordering.

type StringSlice

type StringSlice []string

func (*StringSlice) Scan

func (s *StringSlice) Scan(src any) error

func (StringSlice) Value

func (s StringSlice) Value() (driver.Value, error)

Directories

Path Synopsis
example
basic command
pagination command
transaction command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL