postgres

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Dec 2, 2025 License: MIT Imports: 9 Imported by: 0

README

PostgreSQL Implementation for SimpleORM

A production-ready PostgreSQL database adapter for SimpleORM with advanced configuration, connection pooling, comprehensive error handling, and optimized batch operations.

Features

  • ✅ Full orm.Database interface implementation
  • ✅ Advanced configuration with connection pooling
  • ✅ Comprehensive PostgreSQL-specific error handling
  • ✅ Optimized batch INSERT operations
  • ✅ Type-safe parameter conversion ($1, $2, $3...)
  • ✅ DSN parsing and generation (URL and key=value formats)
  • ✅ Connection validation and health checks
  • ✅ Real-time database statistics and metrics
  • ✅ Fluent configuration API with method chaining
  • ✅ Extensive test coverage

Installation

go get github.com/medatechnology/simpleorm

Quick Start

package main

import (
    "log"
    "github.com/medatechnology/simpleorm/postgres"
    orm "github.com/medatechnology/simpleorm"
)

func main() {
    // Create configuration
    config := postgres.NewConfig(
        "localhost", // host
        5432,        // port
        "postgres",  // user
        "password",  // password
        "mydb",      // database name
    )

    // Customize configuration
    config.WithSSLMode("disable").
        WithConnectionPool(25, 5, 0, 0).
        WithApplicationName("my-app")

    // Connect to database
    db, err := postgres.NewDatabase(*config)
    if err != nil {
        log.Fatalf("Connection failed: %v", err)
    }
    defer db.Close()

    // Insert a record
    record := orm.DBRecord{
        TableName: "users",
        Data: map[string]interface{}{
            "name":  "John Doe",
            "email": "john@example.com",
            "age":   30,
        },
    }

    result := db.InsertOneDBRecord(record, false)
    if result.Error != nil {
        log.Fatalf("Insert failed: %v", result.Error)
    }

    // Query records
    users, err := db.SelectMany("users")
    if err != nil {
        log.Fatalf("Query failed: %v", err)
    }

    for _, user := range users {
        log.Printf("User: %+v", user.Data)
    }
}

Configuration

Basic Configuration
// Method 1: Using NewConfig
config := postgres.NewConfig("localhost", 5432, "user", "password", "dbname")

// Method 2: Using NewDefaultConfig with method chaining
config := postgres.NewDefaultConfig().
    WithSSLMode("require").
    WithConnectionPool(50, 10, 10*time.Minute, 5*time.Minute).
    WithTimeouts(20*time.Second, 60*time.Second).
    WithApplicationName("my-service")

// Method 3: Manual configuration
config := &postgres.PostgresConfig{
    Host:            "db.example.com",
    Port:            5432,
    User:            "app_user",
    Password:        "secure_pass",
    DBName:          "production_db",
    SSLMode:         "verify-full",
    MaxOpenConns:    100,
    MaxIdleConns:    20,
    ConnMaxLifetime: 30 * time.Minute,
    ConnMaxIdleTime: 10 * time.Minute,
    ConnectTimeout:  15 * time.Second,
    QueryTimeout:    45 * time.Second,
    ApplicationName: "production-api",
    SearchPath:      "app_schema,public",
    Timezone:        "UTC",
}
Configuration Options
Option Type Default Description
Host string "localhost" Database host
Port int 5432 Database port
User string required Database user
Password string required User password
DBName string required Database name
SSLMode string "disable" SSL mode: disable, require, verify-ca, verify-full
MaxOpenConns int 25 Maximum open connections
MaxIdleConns int 5 Maximum idle connections
ConnMaxLifetime duration 5m Maximum connection lifetime
ConnMaxIdleTime duration 10m Maximum connection idle time
ConnectTimeout duration 10s Connection timeout
QueryTimeout duration 30s Query execution timeout
ApplicationName string "simpleorm" Application identifier
SearchPath string "" Schema search path
Timezone string "" Timezone setting
ExtraParams map[string]string {} Additional parameters
SSL Modes
  • disable - No SSL (default)
  • require - Require SSL, but don't verify server certificate
  • verify-ca - Require SSL and verify that server certificate is signed by a trusted CA
  • verify-full - Require SSL and verify that server certificate matches the server hostname
Connection Pool Tuning

Different workload types benefit from different connection pool settings:

// High-throughput API server
apiConfig := postgres.NewDefaultConfig().
    WithConnectionPool(100, 25, 15*time.Minute, 5*time.Minute)

// Background job processor
jobConfig := postgres.NewDefaultConfig().
    WithConnectionPool(10, 2, 30*time.Minute, 10*time.Minute)

// Analytics/reporting
analyticsConfig := postgres.NewDefaultConfig().
    WithConnectionPool(5, 1, 1*time.Hour, 30*time.Minute)
DSN Generation and Parsing
// Generate URL format DSN
dsn, _ := config.ToDSN()
// postgres://user:pass@localhost:5432/dbname?sslmode=disable

// Generate simple format DSN
dsn, _ := config.ToSimpleDSN()
// host=localhost port=5432 user=user password=pass dbname=dbname

// Parse existing DSN
config, _ := postgres.ParseDSN("postgres://user:pass@host:5432/db")

Basic Operations

Insert Operations
// Insert single record
record := orm.DBRecord{
    TableName: "users",
    Data: map[string]interface{}{
        "name": "Alice",
        "age":  25,
    },
}
result := db.InsertOneDBRecord(record, false)

// Batch insert (optimized multi-row INSERT)
records := []orm.DBRecord{
    {TableName: "users", Data: map[string]interface{}{"name": "Bob", "age": 30}},
    {TableName: "users", Data: map[string]interface{}{"name": "Carol", "age": 35}},
}
results, err := db.InsertManyDBRecordsSameTable(records, false)

// Insert from struct
type User struct {
    Name  string `db:"name"`
    Email string `db:"email"`
}

user := User{Name: "Dave", Email: "dave@example.com"}
result := db.InsertOneTableStruct(user, false)
Select Operations
// Select one record
record, err := db.SelectOne("users")

// Select all records
records, err := db.SelectMany("users")

// Select with simple condition
condition := &orm.Condition{
    Field:    "age",
    Operator: ">",
    Value:    25,
}
records, err := db.SelectManyWithCondition("users", condition)

// Select with complex nested conditions
condition := &orm.Condition{
    Logic: "OR",
    Nested: []orm.Condition{
        {
            Logic: "AND",
            Nested: []orm.Condition{
                {Field: "age", Operator: ">", Value: 30},
                {Field: "status", Operator: "=", Value: "active"},
            },
        },
        {Field: "role", Operator: "=", Value: "admin"},
    },
}
records, err := db.SelectManyWithCondition("users", condition)
Raw SQL Queries
// Parameterized query
sql := orm.ParametereizedSQL{
    Query:  "SELECT * FROM users WHERE age >= $1 AND status = $2",
    Values: []interface{}{25, "active"},
}
records, err := db.SelectOnlyOneSQLParameterized(sql)

// Multiple queries
sqls := []orm.ParametereizedSQL{
    {Query: "SELECT * FROM users WHERE id = $1", Values: []interface{}{1}},
    {Query: "SELECT * FROM users WHERE id = $1", Values: []interface{}{2}},
}
results, err := db.SelectManySQLParameterized(sqls)

Error Handling

The PostgreSQL adapter provides comprehensive error detection and handling:

Error Detection Functions
// Constraint violations
if postgres.IsUniqueViolation(err) {
    // Handle duplicate key error
}
if postgres.IsForeignKeyViolation(err) {
    // Handle foreign key constraint error
}
if postgres.IsNotNullViolation(err) {
    // Handle NOT NULL constraint error
}
if postgres.IsConstraintViolation(err) {
    // Handle any constraint violation
}

// Table/column errors
if postgres.IsUndefinedTable(err) {
    // Table doesn't exist
}
if postgres.IsUndefinedColumn(err) {
    // Column doesn't exist
}

// Connection errors
if postgres.IsConnectionError(err) {
    // Connection failed or lost
}

// Transaction errors
if postgres.IsDeadlock(err) {
    // Deadlock detected - retry transaction
}
if postgres.IsSerializationFailure(err) {
    // Serialization failure - retry transaction
}

// General retry check
if postgres.IsRetryable(err) {
    // Error is transient and can be retried
}
Error Details
// Get PostgreSQL error code
code := postgres.GetPostgreSQLErrorCode(err)

// Get detailed error information
code, message, detail, hint := postgres.GetPostgreSQLErrorDetail(err)
fmt.Printf("Error %s: %s\n", code, message)
if detail != "" {
    fmt.Printf("Detail: %s\n", detail)
}
if hint != "" {
    fmt.Printf("Hint: %s\n", hint)
}

// Format error for logging
formatted := postgres.FormatPostgreSQLError(err)
log.Println(formatted)
Retry Pattern
func retryableOperation(db orm.Database, record orm.DBRecord) error {
    maxRetries := 3
    for attempt := 0; attempt < maxRetries; attempt++ {
        result := db.InsertOneDBRecord(record, false)
        if result.Error == nil {
            return nil
        }

        if postgres.IsRetryable(result.Error) {
            time.Sleep(time.Second * time.Duration(attempt+1))
            continue
        }

        return result.Error // Non-retryable
    }
    return fmt.Errorf("max retries exceeded")
}

Database Status and Metrics

status, err := db.Status()
if err != nil {
    log.Fatalf("Failed to get status: %v", err)
}

fmt.Printf("DBMS: %s\n", status.DBMS)                   // "postgresql"
fmt.Printf("Driver: %s\n", status.DBMSDriver)           // "lib/pq"
fmt.Printf("Version: %s\n", status.Version)             // PostgreSQL version string
fmt.Printf("Leader: %s\n", status.Leader)               // Connection info with pool stats
fmt.Printf("Nodes: %d\n", status.Nodes)                 // Always 1 for PostgreSQL
fmt.Printf("Is Leader: %v\n", status.IsLeader)          // Always true for PostgreSQL

// Access peer information (includes DB size and other metrics)
for id, peer := range status.Peers {
    fmt.Printf("Peer %d: %s (Size: %d bytes)\n", id, peer.NodeID, peer.DBSize)
}

Advanced Features

Batch Operations

The PostgreSQL adapter uses optimized multi-row INSERT syntax:

records := []orm.DBRecord{
    {TableName: "users", Data: map[string]interface{}{"name": "User1"}},
    {TableName: "users", Data: map[string]interface{}{"name": "User2"}},
    {TableName: "users", Data: map[string]interface{}{"name": "User3"}},
}

// Generates: INSERT INTO users (name) VALUES ($1), ($2), ($3)
results, err := db.InsertManyDBRecordsSameTable(records, false)
Custom PostgreSQL Parameters
config := postgres.NewDefaultConfig().
    WithExtraParam("statement_timeout", "60000").                    // 60s query timeout
    WithExtraParam("lock_timeout", "10000").                         // 10s lock timeout
    WithExtraParam("idle_in_transaction_session_timeout", "300000") // 5min idle tx timeout

Testing

Run the test suite:

cd postgres
go test -v

Run tests with coverage:

go test -v -cover -coverprofile=coverage.out
go tool cover -html=coverage.out

Examples

See the examples directory for complete working examples:

Performance Tips

  1. Connection Pooling: Tune MaxOpenConns and MaxIdleConns based on your workload
  2. Batch Inserts: Use InsertManyDBRecordsSameTable for bulk operations
  3. Prepared Statements: Use parameterized queries for repeated operations
  4. Connection Lifetime: Set appropriate ConnMaxLifetime to prevent stale connections
  5. Timeouts: Configure QueryTimeout to prevent long-running queries

Supported PostgreSQL Versions

  • PostgreSQL 9.6+
  • PostgreSQL 10.x
  • PostgreSQL 11.x
  • PostgreSQL 12.x
  • PostgreSQL 13.x
  • PostgreSQL 14.x
  • PostgreSQL 15.x
  • PostgreSQL 16.x

Dependencies

  • github.com/lib/pq - Pure Go PostgreSQL driver
  • github.com/medatechnology/simpleorm - SimpleORM core
  • github.com/medatechnology/goutil - Utility functions

Thread Safety

All operations are thread-safe. The underlying *sql.DB from database/sql package handles connection pooling and thread safety automatically.

Limitations

  • Queue mode (queue bool parameter) is not supported and will return an error
  • Leader() and Peers() methods return single-node information (PostgreSQL is not clustered like RQLite)
  • Some ORM features like migrations are not included (by design - SimpleORM focuses on basic CRUD)

License

Same as parent SimpleORM project.

Contributing

See the main SimpleORM repository for contribution guidelines.

Support

For issues, questions, or contributions, please visit the GitHub repository.

Documentation

Overview

Package postgres provides a PostgreSQL implementation for the orm.Database interface.

Index

Constants

View Source
const (
	DefaultHost            = "localhost"
	DefaultPort            = 5432
	DefaultSSLMode         = "disable"
	DefaultConnectTimeout  = 10 * time.Second
	DefaultQueryTimeout    = 30 * time.Second
	DefaultMaxOpenConns    = 25
	DefaultMaxIdleConns    = 5
	DefaultConnMaxLifetime = 5 * time.Minute
	DefaultConnMaxIdleTime = 10 * time.Minute
	DefaultApplicationName = "simpleorm"
)

Default configuration values

View Source
const (
	// Class 23 - Integrity Constraint Violation
	ErrCodeUniqueViolation     = "23505"
	ErrCodeForeignKeyViolation = "23503"
	ErrCodeNotNullViolation    = "23502"
	ErrCodeCheckViolation      = "23514"
	ErrCodeExclusionViolation  = "23P01"

	// Class 42 - Syntax Error or Access Rule Violation
	ErrCodeUndefinedTable    = "42P01"
	ErrCodeUndefinedColumn   = "42703"
	ErrCodeDuplicateTable    = "42P07"
	ErrCodeDuplicateColumn   = "42701"
	ErrCodeInvalidSchemaName = "3F000"

	// Class 08 - Connection Exception
	ErrCodeConnectionException    = "08000"
	ErrCodeConnectionFailure      = "08006"
	ErrCodeSQLClientCannotConnect = "08001"

	// Class 57 - Operator Intervention
	ErrCodeAdminShutdown    = "57P01"
	ErrCodeCrashShutdown    = "57P02"
	ErrCodeCannotConnectNow = "57P03"

	// Class 53 - Insufficient Resources
	ErrCodeInsufficientResources = "53000"
	ErrCodeDiskFull              = "53100"
	ErrCodeOutOfMemory           = "53200"
	ErrCodeTooManyConnections    = "53300"

	// Class 40 - Transaction Rollback
	ErrCodeDeadlockDetected     = "40P01"
	ErrCodeSerializationFailure = "40001"
)

PostgreSQL-specific error codes See: https://www.postgresql.org/docs/current/errcodes-appendix.html

Variables

View Source
var (
	ErrPostgresNotConnected      medaerror.MedaError = medaerror.MedaError{Message: "PostgreSQL database is not connected"}
	ErrPostgresInvalidDSN        medaerror.MedaError = medaerror.MedaError{Message: "invalid PostgreSQL DSN connection string"}
	ErrPostgresConnectionFailed  medaerror.MedaError = medaerror.MedaError{Message: "failed to connect to PostgreSQL database"}
	ErrPostgresQueryFailed       medaerror.MedaError = medaerror.MedaError{Message: "PostgreSQL query execution failed"}
	ErrPostgresTransactionFailed medaerror.MedaError = medaerror.MedaError{Message: "PostgreSQL transaction failed"}
	ErrPostgresInvalidConfig     medaerror.MedaError = medaerror.MedaError{Message: "invalid PostgreSQL configuration"}
	ErrPostgresTimeout           medaerror.MedaError = medaerror.MedaError{Message: "PostgreSQL operation timed out"}
	ErrPostgresNoAffectedRows    medaerror.MedaError = medaerror.MedaError{Message: "PostgreSQL query affected zero rows"}
)

Custom PostgreSQL errors using medaerror

Functions

func ConvertPostgreSQLError

func ConvertPostgreSQLError(err error) error

ConvertPostgreSQLError converts a PostgreSQL error to an appropriate ORM error

func FormatPostgreSQLError

func FormatPostgreSQLError(err error) string

FormatPostgreSQLError formats a PostgreSQL error for logging or display

func GetPostgreSQLErrorCode

func GetPostgreSQLErrorCode(err error) string

GetPostgreSQLErrorCode extracts the PostgreSQL error code from an error

func GetPostgreSQLErrorDetail

func GetPostgreSQLErrorDetail(err error) (code, message, detail, hint string)

GetPostgreSQLErrorDetail extracts the detailed error information

func IsCheckViolation

func IsCheckViolation(err error) bool

IsCheckViolation checks if the error is a CHECK constraint violation

func IsConnectionError

func IsConnectionError(err error) bool

IsConnectionError checks if the error is related to database connection

func IsConstraintViolation

func IsConstraintViolation(err error) bool

IsConstraintViolation checks if the error is any type of constraint violation

func IsDeadlock

func IsDeadlock(err error) bool

IsDeadlock checks if the error is due to a deadlock

func IsDuplicateColumn

func IsDuplicateColumn(err error) bool

IsDuplicateColumn checks if the error is due to attempting to create an existing column

func IsDuplicateTable

func IsDuplicateTable(err error) bool

IsDuplicateTable checks if the error is due to attempting to create an existing table

func IsForeignKeyViolation

func IsForeignKeyViolation(err error) bool

IsForeignKeyViolation checks if the error is a foreign key constraint violation

func IsInsufficientResources

func IsInsufficientResources(err error) bool

IsInsufficientResources checks if the error is due to insufficient resources

func IsNotNullViolation

func IsNotNullViolation(err error) bool

IsNotNullViolation checks if the error is a NOT NULL constraint violation

func IsRetryable

func IsRetryable(err error) bool

IsRetryable checks if the error is transient and the operation can be retried

func IsSerializationFailure

func IsSerializationFailure(err error) bool

IsSerializationFailure checks if the error is a serialization failure

func IsTooManyConnections

func IsTooManyConnections(err error) bool

IsTooManyConnections checks if the error is due to too many connections

func IsUndefinedColumn

func IsUndefinedColumn(err error) bool

IsUndefinedColumn checks if the error is due to a non-existent column

func IsUndefinedTable

func IsUndefinedTable(err error) bool

IsUndefinedTable checks if the error is due to a non-existent table

func IsUniqueViolation

func IsUniqueViolation(err error) bool

IsUniqueViolation checks if the error is a unique constraint violation

func WrapPostgreSQLError

func WrapPostgreSQLError(err error, operation, table, query string) error

WrapPostgreSQLError wraps a PostgreSQL error with additional context

Types

type PostgreSQLError

type PostgreSQLError struct {
	Operation string // The operation that failed (e.g., "INSERT", "SELECT", "UPDATE")
	Table     string // The table involved (if applicable)
	Query     string // The SQL query that failed (if applicable)
	Code      string // PostgreSQL error code
	Message   string // Error message
	Detail    string // Detailed error information
	Hint      string // Hint for fixing the error
	Err       error  // Original error
}

PostgreSQLError wraps PostgreSQL-specific errors with additional context

func (*PostgreSQLError) Error

func (e *PostgreSQLError) Error() string

Error implements the error interface

func (*PostgreSQLError) Unwrap

func (e *PostgreSQLError) Unwrap() error

Unwrap returns the underlying error

type PostgresConfig

type PostgresConfig struct {
	// Connection parameters
	Host     string // Database host (default: "localhost")
	Port     int    // Database port (default: 5432)
	User     string // Database user (required)
	Password string // Database password (required)
	DBName   string // Database name (required)
	SSLMode  string // SSL mode: "disable", "require", "verify-ca", "verify-full" (default: "disable")

	// Connection pooling
	MaxOpenConns    int           // Maximum number of open connections (default: 25)
	MaxIdleConns    int           // Maximum number of idle connections (default: 5)
	ConnMaxLifetime time.Duration // Maximum lifetime of a connection (default: 5 minutes)
	ConnMaxIdleTime time.Duration // Maximum idle time of a connection (default: 10 minutes)

	// Timeouts
	ConnectTimeout time.Duration // Connection timeout (default: 10 seconds)
	QueryTimeout   time.Duration // Query execution timeout (default: 30 seconds)

	// Additional parameters
	ApplicationName string            // Application name for logging (default: "simpleorm")
	SearchPath      string            // Schema search path (optional)
	Timezone        string            // Timezone (optional, e.g., "UTC")
	ExtraParams     map[string]string // Additional connection parameters (optional)
}

PostgresConfig holds the configuration for PostgreSQL database connection

func NewConfig

func NewConfig(host string, port int, user, password, dbName string) *PostgresConfig

NewConfig creates a new PostgresConfig with the specified database credentials and applies default values for other settings

func NewDefaultConfig

func NewDefaultConfig() *PostgresConfig

NewDefaultConfig creates a new PostgresConfig with default values

func ParseDSN

func ParseDSN(dsn string) (*PostgresConfig, error)

ParseDSN parses a PostgreSQL DSN connection string and returns a PostgresConfig Supports both URL format and key=value format

func (*PostgresConfig) Clone

func (c *PostgresConfig) Clone() *PostgresConfig

Clone creates a deep copy of the PostgresConfig

func (*PostgresConfig) String

func (c *PostgresConfig) String() string

String returns a safe string representation of the config (without password)

func (*PostgresConfig) ToDSN

func (c *PostgresConfig) ToDSN() (string, error)

ToDSN converts the PostgresConfig to a Data Source Name (DSN) connection string Format: postgres://user:password@host:port/dbname?param1=value1&param2=value2

func (*PostgresConfig) ToSimpleDSN

func (c *PostgresConfig) ToSimpleDSN() (string, error)

ToSimpleDSN converts the PostgresConfig to a simple DSN connection string Format: host=localhost port=5432 user=postgres password=secret dbname=mydb sslmode=disable

func (*PostgresConfig) Validate

func (c *PostgresConfig) Validate() error

Validate checks if the configuration is valid

func (*PostgresConfig) WithApplicationName

func (c *PostgresConfig) WithApplicationName(name string) *PostgresConfig

WithApplicationName sets the application name and returns the config for method chaining

func (*PostgresConfig) WithConnectionPool

func (c *PostgresConfig) WithConnectionPool(maxOpen, maxIdle int, maxLifetime, maxIdleTime time.Duration) *PostgresConfig

WithConnectionPool sets the connection pool parameters and returns the config for method chaining

func (*PostgresConfig) WithExtraParam

func (c *PostgresConfig) WithExtraParam(key, value string) *PostgresConfig

WithExtraParam adds an extra connection parameter and returns the config for method chaining

func (*PostgresConfig) WithSSLMode

func (c *PostgresConfig) WithSSLMode(mode string) *PostgresConfig

WithSSLMode sets the SSL mode and returns the config for method chaining

func (*PostgresConfig) WithSearchPath

func (c *PostgresConfig) WithSearchPath(path string) *PostgresConfig

WithSearchPath sets the schema search path and returns the config for method chaining

func (*PostgresConfig) WithTimeouts

func (c *PostgresConfig) WithTimeouts(connectTimeout, queryTimeout time.Duration) *PostgresConfig

WithTimeouts sets the timeout parameters and returns the config for method chaining

func (*PostgresConfig) WithTimezone

func (c *PostgresConfig) WithTimezone(tz string) *PostgresConfig

WithTimezone sets the timezone and returns the config for method chaining

type PostgresDirectDB

type PostgresDirectDB = *postgres

PostgresDirectDB is an alias to *postgres for external use The actual implementation is in the private postgres struct

func NewDatabase

func NewDatabase(config PostgresConfig) (PostgresDirectDB, error)

NewDatabase creates a new PostgreSQL database instance. It takes a PostgresConfig and returns an implementation of orm.Database.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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