Skip to content

akhilmhdh/pgsb

Repository files navigation

PGSB - PostgreSQL Schema Builder for Go

A fluent, expressive PostgreSQL schema builder for Go that provides a Laravel/Knex.js-like API for creating and managing database schemas. Perfect for use with migration tools like Goose.

Features

  • 🚀 Fluent API - Chainable, expressive syntax for building schemas
  • 🗃️ PostgreSQL Focused - Full support for PostgreSQL-specific features
  • 🔧 Type Safe - Strongly typed with comprehensive column types
  • 📦 Migration Ready - Designed to work seamlessly with Goose migrations
  • Well Tested - Comprehensive test suite with snapshot testing
  • 🎯 Zero Dependencies - Only uses Go standard library (except for tests)

Installation

go get github.com/akhilmhdh/pgsb

Quick Start

package main

import (
    "database/sql"
    "github.com/akhilmhdh/pgsb"
    _ "github.com/lib/pq"
)

func main() {
    db, _ := sql.Open("postgres", "your-connection-string")
    tx, _ := db.Begin()

    s := pgsb.New(tx)

    // Create a users table
    err := s.CreateTable("users", func(t *pgsb.Table) {
        t.Increments("id")
        t.String("email", 255).NotNullable().Unique()
        t.String("name", 255).NotNullable()
        t.Timestamps()
    })

    if err != nil {
        tx.Rollback()
        return
    }

    tx.Commit()
}

Column Types

Basic Types

t.String("name", 255)           // VARCHAR(255)
t.Text("description")           // TEXT
t.Integer("count")              // INTEGER
t.BigInteger("big_count")       // BIGINT
t.SmallInteger("small_count")   // SMALLINT
t.Float("price")                // REAL
t.Double("big_price")           // DOUBLE PRECISION
t.Decimal("amount", 10, 2)      // DECIMAL(10,2)
t.Boolean("is_active")          // BOOLEAN

Date & Time Types

t.Date("birth_date")                                    // DATE
t.Time("start_time")                                    // TIME
t.DateTime("created_at")                                // TIMESTAMPTZ
t.DateTime("event_time", DateTimeOptions{              // TIMESTAMPTZ(6)
    UseTz:     true,
    Precision: 6,
})
t.Timestamp("updated_at")                               // Alias for DateTime
t.Timestamps()                                          // created_at & updated_at

Advanced Types

t.UUID("external_id")                                   // UUID
t.JSON("data")                                          // JSON
t.JSONB("metadata")                                     // JSONB
t.Binary("file_data")                                   // BYTEA
t.SpecificType("coordinates", "POINT")                  // Custom types
t.SpecificType("tags", "TEXT[]")                        // Arrays

Auto-Incrementing Columns

t.Increments("id")                  // SERIAL PRIMARY KEY
t.BigIncrements("id", true)         // BIGSERIAL PRIMARY KEY

Column Modifiers

Constraints

t.String("email", 255).NotNullable()                    // NOT NULL
t.String("email", 255).Nullable()                       // Allow NULL (default)
t.String("email", 255).Unique()                         // UNIQUE constraint
t.Integer("user_id").Primary()                          // PRIMARY KEY
t.String("email", 255).DefaultTo("'[email protected]'") // DEFAULT value

Check Constraints

t.Integer("age").CheckPositive()                        // age > 0
t.Integer("debt").CheckNegative()                       // debt < 0
t.Integer("score").CheckBetween(0, 100)                 // score BETWEEN 0 AND 100
t.String("status", 20).CheckIn([]string{"active", "inactive"}) // status IN (...)
t.Integer("count").Unsigned()                           // count >= 0

Foreign Keys

t.Integer("user_id").References("id").InTable("users")
t.Integer("user_id").References("id").InTable("users").OnDelete("CASCADE")
t.Integer("user_id").References("id").InTable("users").OnUpdate("RESTRICT")

Indexes

// Single column index
t.Index("email")
t.Index("email", "custom_index_name")

// Composite index
t.Index([]string{"user_id", "created_at"})
t.Index([]string{"user_id", "status"}, "idx_user_status")

// Unique constraints
t.Unique("email")
t.Unique([]string{"user_id", "slug"})

Enum Types

// Create enum type and column
t.Enum("status", []string{"draft", "published", "archived"}, EnumOptions{
    UseNative: true,
    EnumName:  "post_status",
}).DefaultTo("'draft'")

// Use existing enum type
t.Enum("status", []string{}, EnumOptions{
    UseNative:    true,
    EnumName:     "post_status",
    ExistingType: true,
})

Table Operations

Creating Tables

// Create table
s.CreateTable("users", func(t *Table) {
    t.Increments("id")
    t.String("name", 255).NotNullable()
})

// Create table if not exists
s.CreateTableIfNotExists("users", func(t *Table) {
    t.Increments("id")
    t.String("name", 255).NotNullable()
})

Altering Tables

s.AlterTable("users", func(t *TableAlterer) {
    // Add columns
    t.String("avatar_url", 500)
    t.AddColumn("last_login_at", "TIMESTAMPTZ", func(col *Column) {
        col.Nullable = true
    })

    // Rename column
    t.RenameColumn("full_name", "display_name")

    // Drop column
    t.DropColumn("old_field")
})

Dropping Tables

s.DropTable("users")                // DROP TABLE users
s.DropTableIfExists("users")        // DROP TABLE IF EXISTS users
s.RenameTable("old_users", "users") // ALTER TABLE old_users RENAME TO users

Checking Existence

exists, err := s.HasTable("users")
if err != nil {
    return err
}

hasColumn, err := s.HasColumn("users", "email")
if err != nil {
    return err
}

Complex Table Example

s.CreateTable("orders", func(t *Table) {
    // Primary key
    t.UUID("id").Primary().DefaultTo("gen_random_uuid()")

    // Basic columns
    t.String("order_number", 20).NotNullable().Unique()
    t.BigInteger("user_id").NotNullable()

    // Decimal columns with constraints
    t.Decimal("subtotal", 12, 2).NotNullable().CheckPositive()
    t.Decimal("tax", 12, 2).NotNullable().DefaultTo(0)
    t.Decimal("total", 12, 2).NotNullable()

    // Enum with native PostgreSQL type
    t.Enum("status", []string{"pending", "processing", "shipped", "delivered"},
        EnumOptions{
            UseNative: true,
            EnumName:  "order_status",
        }).NotNullable().DefaultTo("'pending'")

    // Optional columns
    t.Text("notes").Nullable()
    t.DateTime("shipped_at").Nullable()
    t.Timestamps()

    // Foreign key
    t.Foreign("user_id").References("id").InTable("users").OnDelete("CASCADE")

    // Check constraints
    t.Check("total = subtotal + tax", "check_total_calculation")
    t.Check("shipped_at IS NULL OR shipped_at <= delivered_at", "check_shipping_logic")

    // Indexes
    t.Index("user_id")
    t.Index("status")
    t.Index([]string{"created_at", "status"}, "idx_orders_recent_by_status")

    // Table comment
    t.Comment("Orders table with business logic constraints")
})

Usage with Goose Migrations

package migrations

import (
    "context"
    "database/sql"
    "github.com/pressly/goose/v3"
    "github.com/akhilmhdh/pgsb"
)

func init() {
    goose.AddMigrationContext(upCreateUsers, downCreateUsers)
}

func upCreateUsers(ctx context.Context, tx *sql.Tx) error {
    s := pgsb.New(tx)

    return s.CreateTable("users", func(t *pgsb.Table) {
        t.Increments("id")
        t.String("email", 255).NotNullable().Unique()
        t.String("username", 100).NotNullable().Unique()
        t.String("full_name", 255)
        t.Boolean("is_active").DefaultTo(true)
        t.Timestamps()

        t.Index("email")
        t.Comment("Main users table")
    })
}

func downCreateUsers(ctx context.Context, tx *sql.Tx) error {
    s := pgsb.New(tx)
    return s.DropTable("users")
}

PostgreSQL-Specific Features

Table Inheritance

s.CreateTable("audit_users", func(t *Table) {
    t.Inherits("audit_base")
    t.Check("table_name = 'users'", "check_users_audit")
})

Array Types

t.SpecificType("tags", "TEXT[]").DefaultTo("ARRAY[]::TEXT[]")
t.SpecificType("scores", "INTEGER[]")

Spatial Types

t.SpecificType("coordinates", "POINT").NotNullable()
t.SpecificType("area", "POLYGON")

Network Types

t.SpecificType("ip_address", "INET")
t.SpecificType("mac_address", "MACADDR")

Utility Functions

// Raw SQL
pgsb.Raw("SELECT NOW()")

// Current timestamp
pgsb.Now() // Returns "CURRENT_TIMESTAMP"

Testing

The library includes comprehensive tests with snapshot testing to ensure SQL generation is correct:

go test ./...

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Add tests for your changes
  4. Commit your changes (git commit -m 'Add some amazing feature')
  5. Push to the branch (git push origin feature/amazing-feature)
  6. Open a Pull Request

License

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

Acknowledgments

  • Inspired by Laravel's Schema Builder and Knex.js
  • Built specifically for PostgreSQL's rich feature set
  • Designed to work seamlessly with Goose migrations
  • This project is built from Claude Code

About

A postgres schema builder

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages