zoobzio January 3, 2026 Edit this page

Quickstart

Requirements

Go 1.24 or later.

Installation

go get github.com/zoobz-io/edamame

Basic Usage

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/jmoiron/sqlx"
    _ "github.com/lib/pq" // or mariadb, sqlite3, mssql driver
    "github.com/zoobz-io/astql/pkg/postgres" // or mariadb, sqlite, mssql
    "github.com/zoobz-io/edamame"
)

// Define your model with struct tags
type User struct {
    ID    int    `db:"id" type:"integer" constraints:"primarykey"`
    Email string `db:"email" type:"text" constraints:"notnull,unique"`
    Name  string `db:"name" type:"text"`
    Age   *int   `db:"age" type:"integer"`
}

// Define statements as package-level variables
var (
    QueryAll = edamame.NewQueryStatement("query-all", "Query all users", edamame.QuerySpec{})

    SelectByID = edamame.NewSelectStatement("select-by-id", "Select user by ID", edamame.SelectSpec{
        Where: []edamame.ConditionSpec{{Field: "id", Operator: "=", Param: "id"}},
    })

    CountAll = edamame.NewAggregateStatement("count-all", "Count all users", edamame.AggCount, edamame.AggregateSpec{})
)

func main() {
    // Connect to database (PostgreSQL shown; MariaDB, SQLite, SQL Server also supported)
    db, err := sqlx.Connect("postgres", "postgres://user:pass@localhost/mydb?sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }

    // Create exec
    exec, err := edamame.New[User](db, "users", postgres.New())
    if err != nil {
        log.Fatal(err)
    }

    ctx := context.Background()

    // Query all users
    users, err := exec.ExecQuery(ctx, QueryAll, nil)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Found %d users\n", len(users))

    // Select user by ID
    user, err := exec.ExecSelect(ctx, SelectByID, map[string]any{"id": 1})
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("User: %s\n", user.Name)

    // Insert a new user
    age := 25
    newUser := &User{
        Email: "alice@example.com",
        Name:  "Alice",
        Age:   &age,
    }
    inserted, err := exec.ExecInsert(ctx, newUser)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Inserted user ID: %d\n", inserted.ID)

    // Count users
    count, err := exec.ExecAggregate(ctx, CountAll, nil)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Total users: %.0f\n", count)
}

What's Happening

  1. New[User](db, "users", postgres.New()) creates a exec for the User type bound to the "users" table with the PostgreSQL renderer
  2. Statements are defined as package-level variables with unique names and descriptions
  3. ExecQuery returns all records matching the statement's spec
  4. ExecSelect returns a single record (or error if not found)
  5. ExecInsert inserts a record and returns it with generated fields (like ID)
  6. ExecAggregate runs an aggregate function and returns the result

Defining Custom Statements

// Define statements for your domain
var (
    // Query for active adult users
    ActiveAdults = edamame.NewQueryStatement("active-adults", "Find active users above minimum age", edamame.QuerySpec{
        Where: []edamame.ConditionSpec{
            {Field: "active", Operator: "=", Param: "active"},
            {Field: "age", Operator: ">=", Param: "min_age"},
        },
        OrderBy: []edamame.OrderBySpec{
            {Field: "name", Direction: "asc"},
        },
    })

    // Update user name
    UpdateName = edamame.NewUpdateStatement("update-name", "Update user name by ID", edamame.UpdateSpec{
        Set:   map[string]string{"name": "new_name"},
        Where: []edamame.ConditionSpec{{Field: "id", Operator: "=", Param: "id"}},
    })

    // Delete inactive users
    DeleteInactive = edamame.NewDeleteStatement("delete-inactive", "Delete inactive users", edamame.DeleteSpec{
        Where: []edamame.ConditionSpec{{Field: "active", Operator: "=", Param: "active"}},
    })

    // Sum of ages
    SumAges = edamame.NewAggregateStatement("sum-ages", "Sum all user ages", edamame.AggSum, edamame.AggregateSpec{
        Field: "age",
    })
)

// Use the statements
users, err := exec.ExecQuery(ctx, ActiveAdults, map[string]any{
    "active":  true,
    "min_age": 18,
})

affected, err := exec.ExecUpdate(ctx, UpdateName, map[string]any{
    "id":       123,
    "new_name": "New Name",
})

deleted, err := exec.ExecDelete(ctx, DeleteInactive, map[string]any{
    "active": false,
})

total, err := exec.ExecAggregate(ctx, SumAges, nil)

Statement Parameters

Statements automatically derive their parameters from the spec. You can inspect them:

// Check statement parameters
for _, param := range ActiveAdults.Params() {
    fmt.Printf("Param: %s (type: %s, required: %v)\n", param.Name, param.Type, param.Required)
}
// Output:
// Param: active (type: any, required: true)
// Param: min_age (type: any, required: true)

Next Steps