Skip to content

Jibaru/gormless

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Gormless

Go Version License Go Report Card Build Status Release Docker Image

A powerful, lightweight code generator that creates type-safe Data Access Objects (DAOs) for Go applications with support for PostgreSQL, MySQL, SQL Server, Oracle, and SQLite.

Features

  • Lightning Fast: Generates comprehensive DAOs in seconds
  • Type-Safe: Leverages Go's type system for compile-time safety
  • Multi-Database: Supports PostgreSQL, MySQL, SQL Server, Oracle, and SQLite with native query optimization
  • Zero Dependencies: Generated code uses only standard library packages
  • Smart Tagging: Automatic field mapping using struct tags
  • Transaction Support: Built-in transaction management
  • Rich Operations: CRUD, bulk operations, pagination, counting, and sorting
  • Interface Generation: Generate DAO interfaces for better abstraction and testing
  • Clean Code: Generates readable, maintainable Go code
  • CLI Ready: Simple command-line interface

Quick Start

Installation

Install via go install:

go install github.com/Jibaru/gormless@latest

Basic Usage

# Generate PostgreSQL DAOs
gormless --input ./models --output ./dao --driver postgres

# Generate MySQL DAOs  
gormless --input ./models/user.go --output ./dao --driver mysql

# Generate SQL Server DAOs
gormless --input ./models --output ./dao --driver sqlserver

# Generate Oracle DAOs
gormless --input ./models --output ./dao --driver oracle

# Generate SQLite DAOs
gormless --input ./models --output ./dao --driver sqlite

# Generate DAO interfaces (database-agnostic)
gormless --input ./models --output ./dao --interface

Documentation

Model Definition

Define your models with struct tags to specify database mapping:

package models

import "time"

type User struct {
    ID        string    `sql:"id,primary"`
    Name      string    `sql:"name"`
    Email     string    `sql:"email"`
    Age       *int      `sql:"age"`
    CreatedAt time.Time `sql:"created_at"`
    UpdatedAt time.Time `sql:"updated_at"`
}

// Optional: Custom table name
func (u *User) TableName() string {
    return "users"
}

Generated DAO

Gormless generates a comprehensive DAO with the following methods:

type UserDAO struct {
    db *sql.DB
}

// CRUD Operations
func (dao *UserDAO) Create(ctx context.Context, user *User) error
func (dao *UserDAO) Update(ctx context.Context, user *User) error
func (dao *UserDAO) FindByPk(ctx context.Context, pk string) (*User, error)
func (dao *UserDAO) DeleteByPk(ctx context.Context, pk string) error

// Bulk Operations
func (dao *UserDAO) CreateMany(ctx context.Context, users []*User) error
func (dao *UserDAO) UpdateMany(ctx context.Context, users []*User) error
func (dao *UserDAO) DeleteManyByPks(ctx context.Context, pks []string) error

// Query Operations
func (dao *UserDAO) FindOne(ctx context.Context, where string, sort string, args ...interface{}) (*User, error)
func (dao *UserDAO) FindAll(ctx context.Context, where string, sort string, args ...interface{}) ([]*User, error)
func (dao *UserDAO) FindPaginated(ctx context.Context, limit, offset int, where string, sort string, args ...interface{}) ([]*User, error)
func (dao *UserDAO) Count(ctx context.Context, where string, args ...interface{}) (int64, error)

// Advanced Operations
func (dao *UserDAO) PartialUpdate(ctx context.Context, pk string, fields map[string]interface{}) error
func (dao *UserDAO) WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error

Interface Generation

Generate database-agnostic DAO interfaces for better abstraction, dependency injection, and testing:

# Generate interfaces for all models in a directory
gormless --input ./models --output ./dao --interface

# Generate interface for a specific model
gormless --input ./models/user.go --output ./dao --interface

This generates clean interfaces like:

package dao

import (
    "context"
    "your-project/models"
)

type User = models.User

type UserDAO interface {
    // CRUD Operations
    Create(ctx context.Context, m *User) error
    Update(ctx context.Context, m *User) error
    FindByPk(ctx context.Context, pk string) (*User, error)
    DeleteByPk(ctx context.Context, pk string) error

    // Bulk Operations
    CreateMany(ctx context.Context, models []*User) error
    UpdateMany(ctx context.Context, models []*User) error
    DeleteManyByPks(ctx context.Context, pks []string) error

    // Query Operations
    FindOne(ctx context.Context, where string, sort string, args ...interface{}) (*User, error)
    FindAll(ctx context.Context, where string, sort string, args ...interface{}) ([]*User, error)
    FindPaginated(ctx context.Context, limit, offset int, where string, sort string, args ...interface{}) ([]*User, error)
    Count(ctx context.Context, where string, args ...interface{}) (int64, error)

    // Advanced Operations
    PartialUpdate(ctx context.Context, pk string, fields map[string]interface{}) error
    WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error
}

Benefits of Interface Generation:

  • Database Agnostic: Switch between different database implementations seamlessly
  • Better Testing: Easily mock DAO interfaces for unit testing
  • Dependency Injection: Use interfaces for cleaner architecture
  • Team Development: Define contracts before implementation

Usage Examples

PostgreSQL

package main

import (
    "context"
    "database/sql"
    "log"
    
    "your-project/dao/postgres"
    "your-project/models"
    _ "github.com/lib/pq"
)

func main() {
    db, err := sql.Open("postgres", "postgresql://user:password@localhost/dbname")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    userDAO := postgres.NewUserDAO(db)
    
    // Create a user
    user := &models.User{
        ID:    "user-123",
        Name:  "John Doe", 
        Email: "john@example.com",
    }
    
    err = userDAO.Create(context.Background(), user)
    if err != nil {
        log.Fatal(err)
    }
    
    // Find user by primary key
    foundUser, err := userDAO.FindByPk(context.Background(), "user-123")
    if err != nil {
        log.Fatal(err)
    }
    
    // Use transactions
    err = userDAO.WithTransaction(context.Background(), func(ctx context.Context) error {
        return userDAO.Update(ctx, foundUser)
    })
}

SQL Server

package main

import (
    "context"
    "database/sql"
    "log"
    
    "your-project/dao/sqlserver"
    "your-project/models"
    _ "github.com/denisenkom/go-mssqldb"
)

func main() {
    db, err := sql.Open("sqlserver", "sqlserver://user:password@localhost:1433?database=dbname")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    userDAO := sqlserver.NewUserDAO(db)
    
    // Same API as other drivers
    user := &models.User{
        ID:    "user-123",
        Name:  "John Doe", 
        Email: "john@example.com",
    }
    
    err = userDAO.Create(context.Background(), user)
    if err != nil {
        log.Fatal(err)
    }
}

Oracle

package main

import (
    "context"
    "database/sql"
    "log"
    
    "your-project/dao/oracle"
    "your-project/models"
    _ "github.com/godror/godror"
)

func main() {
    db, err := sql.Open("godror", "user/password@localhost:1521/XE")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    userDAO := oracle.NewUserDAO(db)
    
    // Same API as other drivers
    user := &models.User{
        ID:    "user-123",
        Name:  "John Doe", 
        Email: "john@example.com",
    }
    
    err = userDAO.Create(context.Background(), user)
    if err != nil {
        log.Fatal(err)
    }
}

SQLite

package main

import (
    "context"
    "database/sql"
    "log"
    
    "your-project/dao/sqlite"
    "your-project/models"
    _ "github.com/mattn/go-sqlite3"
)

func main() {
    db, err := sql.Open("sqlite3", "./database.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    userDAO := sqlite.NewUserDAO(db)
    
    // Same API as other drivers
    user := &models.User{
        ID:    "user-123",
        Name:  "John Doe", 
        Email: "john@example.com",
    }
    
    err = userDAO.Create(context.Background(), user)
    if err != nil {
        log.Fatal(err)
    }
}

Using Interfaces

Generate and use DAO interfaces for better architecture:

package main

import (
    "context"
    "database/sql"
    "log"
    
    "your-project/dao"           // Generated interfaces
    "your-project/dao/postgres"  // Concrete implementation
    "your-project/models"
    _ "github.com/lib/pq"
)

// Service uses the interface for database independence
type UserService struct {
    userDAO dao.UserDAO
}

func NewUserService(userDAO dao.UserDAO) *UserService {
    return &UserService{userDAO: userDAO}
}

func (s *UserService) CreateUser(ctx context.Context, name, email string) (*models.User, error) {
    user := &models.User{
        ID:    generateID(),
        Name:  name,
        Email: email,
    }
    
    if err := s.userDAO.Create(ctx, user); err != nil {
        return nil, err
    }
    
    return user, nil
}

func main() {
    // Setup database connection
    db, err := sql.Open("postgres", "postgresql://user:password@localhost/dbname")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    // Use concrete implementation
    userDAO := postgres.NewUserDAO(db)
    
    // Pass to service via interface
    userService := NewUserService(userDAO)
    
    user, err := userService.CreateUser(context.Background(), "John Doe", "john@example.com")
    if err != nil {
        log.Fatal(err)
    }
    
    log.Printf("Created user: %+v", user)
}

func generateID() string {
    // Your ID generation logic
    return "user-123"
}

Sort Expressions

The FindOne, FindAll, and FindPaginated methods support optional sort expressions to control the order of returned results.

Sort Syntax

Pass a sort expression as a string using standard SQL ORDER BY syntax:

// Basic sorting
users, err := userDAO.FindAll(ctx, "", "name ASC")
users, err := userDAO.FindAll(ctx, "", "created_at DESC")

// Multiple columns
users, err := userDAO.FindAll(ctx, "", "name ASC, created_at DESC")

// With WHERE conditions
users, err := userDAO.FindAll(ctx, "age > $1", "name ASC", 21)

// Complex sorting
users, err := userDAO.FindPaginated(ctx, 10, 0, "email LIKE $1", "created_at DESC, name ASC", "%@example.com")

Sort Examples by Database

PostgreSQL:

// Find users ordered by name ascending
users, err := userDAO.FindAll(ctx, "", "name ASC")

// Find users ordered by creation date (newest first)
users, err := userDAO.FindAll(ctx, "", "created_at DESC")

// Find users with conditions and sorting
users, err := userDAO.FindAll(ctx, "age >= $1", "age ASC, name DESC", 18)

// Paginated results with sorting
users, err := userDAO.FindPaginated(ctx, 5, 10, "email LIKE $1", "name ASC", "%@gmail.com")

MySQL:

// Find users ordered by name ascending
users, err := userDAO.FindAll(ctx, "", "name ASC")

// Find users with conditions and sorting
users, err := userDAO.FindAll(ctx, "age >= ?", "age ASC, name DESC", 18)

// Paginated results with sorting
users, err := userDAO.FindPaginated(ctx, 5, 10, "email LIKE ?", "name ASC", "%@gmail.com")

SQL Server:

// Find users ordered by name ascending
users, err := userDAO.FindAll(ctx, "", "name ASC")

// Find users with conditions and sorting
users, err := userDAO.FindAll(ctx, "age >= @p1", "age ASC, name DESC", 18)

// Paginated results with sorting
users, err := userDAO.FindPaginated(ctx, 5, 10, "email LIKE @p1", "name ASC", "%@gmail.com")

Oracle:

// Find users ordered by name ascending
users, err := userDAO.FindAll(ctx, "", "name ASC")

// Find users with conditions and sorting
users, err := userDAO.FindAll(ctx, "age >= :1", "age ASC, name DESC", 18)

// Paginated results with sorting
users, err := userDAO.FindPaginated(ctx, 5, 10, "email LIKE :1", "name ASC", "%@gmail.com")

SQLite:

// Find users ordered by name ascending
users, err := userDAO.FindAll(ctx, "", "name ASC")

// Find users with conditions and sorting
users, err := userDAO.FindAll(ctx, "age >= ?", "age ASC, name DESC", 18)

// Paginated results with sorting
users, err := userDAO.FindPaginated(ctx, 5, 10, "email LIKE ?", "name ASC", "%@gmail.com")

Important Notes

  • Empty Sort: Pass an empty string "" for the sort parameter to use default ordering (or no explicit ordering)
  • SQL Injection: Sort expressions are concatenated directly into queries. Only use trusted input or validate sort expressions carefully
  • Column Names: Use the actual database column names in sort expressions, not Go field names
  • Database-Specific: Sort expressions use standard SQL syntax but may have database-specific features available

Configuration

Command Line Options

Option Short Description Required
--input -i Path to model file or directory
--output -o Output directory for generated DAOs
--driver -d Database driver (postgres, mysql, sqlserver, oracle, sqlite) ✅*
--interface Generate DAO interfaces instead of concrete implementations

* Required only when not using --interface

Struct Tags

Tag Description Example
sql:"column_name" Map field to database column sql:"user_name"
sql:"column_name,primary" Mark field as primary key sql:"id,primary"

Database Support

Database Driver Placeholder Style
PostgreSQL postgres $1, $2, $3
MySQL mysql ?, ?, ?
SQL Server sqlserver @p1, @p2, @p3
Oracle oracle :1, :2, :3
SQLite sqlite ?, ?, ?

License

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

Acknowledgments

  • Inspired by the need for simple, efficient database access patterns in Go
  • Built with Cobra CLI for excellent command-line experience

Links

About

no more gorm, let's generate DAO code

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors