thing

package module
v0.1.10 Latest Latest
Warning

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

Go to latest
Published: May 17, 2025 License: MIT Imports: 22 Imported by: 0

README

Thing ORM: High-Performance Go ORM with Built-in Caching

Go Report Card Build Status Go Reference MIT License Go Version

Thing ORM is a high-performance, open-source Object-Relational Mapper for Go, designed for modern application needs:

  • Unique Integrated Caching: Thing ORM provides built-in support for either Redis or in-memory caching, making cache integration seamless and efficient. It automatically caches single-entity and list queries, manages cache invalidation, and provides cache hit/miss monitoring—out of the box. No third-party plugins or manual cache wiring required.
  • Type-Safe Generics-Based API: Built on Go generics, Thing ORM provides compile-time type safety, better performance, and a more intuitive, IDE-friendly API—unlike most Go ORMs that rely on reflection and runtime type checks.
  • Multi-Database Support: Effortlessly switch between MySQL, PostgreSQL, and SQLite with a unified API and automatic SQL dialect adaptation.
  • Simple, Efficient CRUD and List Queries: Focused on the most common application patterns—thread-safe Create, Read, Update, Delete, and efficient list retrieval with filtering, ordering, and pagination.
  • Focused API: Designed for fast CRUD and list operations. Complex SQL features like JOINs are out of the ORM's direct scope, but raw SQL execution is supported.
  • Elegant, Developer-Friendly API: Clean, extensible, and idiomatic Go API, with flexible JSON serialization, relationship management, and hooks/events system.
  • Open Source and Community-Ready: Well-documented, thoroughly tested, and designed for easy adoption and contribution by the Go community.

Table of Contents

Installation

go get github.com/burugo/thing

Configuration

Thing ORM is configured by providing a database adapter (DBAdapter) and an optional cache client (CacheClient) when creating a Thing instance using thing.New. This allows for flexible setup tailored to your application's needs.

1. Choose a Database Adapter

First, create an instance of a database adapter for your chosen database (MySQL, PostgreSQL, or SQLite).

import (
	// "github.com/burugo/thing/drivers/db/mysql"
	// "github.com/burugo/thing/drivers/db/postgres"
	"github.com/burugo/thing/drivers/db/sqlite"
)

// Example: SQLite (replace ":memory:" with your file path)
dbAdapter, err := sqlite.NewSQLiteAdapter(":memory:")
if err != nil {
	log.Fatal("Failed to create SQLite adapter:", err)
}

// Example: MySQL (replace with your actual DSN)
// mysqlDSN := "user:password@tcp(127.0.0.1:3306)/database?parseTime=true"
// dbAdapter, err := mysql.NewMySQLAdapter(mysqlDSN)
// if err != nil {
// 	log.Fatal("Failed to create MySQL adapter:", err)
// }

// Example: PostgreSQL (replace with your actual DSN)
// pgDSN := "host=localhost user=user password=password dbname=database port=5432 sslmode=disable TimeZone=Asia/Shanghai"
// dbAdapter, err := postgres.NewPostgreSQLAdapter(pgDSN)
// if err != nil {
// 	log.Fatal("Failed to create PostgreSQL adapter:", err)
// }
2. Choose a Cache Client (Optional)

Thing ORM includes a built-in in-memory cache, which is used by default if no cache client is provided. For production or distributed systems, using Redis is recommended.

import (
	"github.com/redis/go-redis/v9"
	redisCache "github.com/burugo/thing/drivers/cache/redis"
	"github.com/burugo/thing"
)

// Option A: Use Default In-Memory Cache
// Simply pass nil as the cache client when calling thing.New
var cacheClient thing.CacheClient = nil

// Option B: Use Redis
// redisAddr := "localhost:6379"
// redisPassword := ""
// redisDB := 0
// rdb := redis.NewClient(&redis.Options{
// 	Addr:     redisAddr,
// 	Password: redisPassword,
// 	DB:       redisDB,
// })
// cacheClient = redisCache.NewClient(rdb) // Create Thing's Redis client wrapper

3. Create Thing Instance

Use thing.New to create an ORM instance for your specific model type, passing the chosen database adapter and cache client.

import (
	"github.com/burugo/thing"
	// import your models package
)

// Create a Thing instance for the User model
userThing, err := thing.New[*models.User](dbAdapter, cacheClient)
if err != nil {
	log.Fatal("Failed to create Thing instance for User:", err)
}

// Now you can use userThing for CRUD, queries, etc.
// userThing.Save(...)
// userThing.ByID(...)
// userThing.Query(...)
Global Configuration (Alternative)

For simpler applications or global setup, you can use thing.Configure once at startup and then thing.Use to get model-specific instances. Note: This uses global variables and is less flexible for managing multiple database/cache connections.

// At application startup:
// err := thing.Configure(dbAdapter, cacheClient)
// if err != nil { ... }

// Later, in your code:
// userThing, err := thing.Use[*models.User]()
// if err != nil { ... }

API Documentation

Full API documentation is available on pkg.go.dev.

(Note: The documentation link will become active after the first official release/tag of the package.)

AI Assistant Integration (e.g., Cursor)

For developers using AI coding assistants like Cursor, a dedicated project rule file is available to help the AI understand and correctly utilize the Thing ORM within this project.

You can find the rule file here: Thing ORM Cursor Rules

Referencing this rule (@thing) in your prompts can improve the AI's accuracy when generating or modifying code related to Thing ORM.

Basic CRUD Example

Here is a minimal example demonstrating how to use Thing ORM for basic CRUD operations:

package main

import (
	"fmt"
	"log"

	"github.com/burugo/thing"
)

// User model definition
// Only basic fields for demonstration
// No relationships

type User struct {
	thing.BaseModel
	Name string `db:"name"`
	Age  int    `db:"age"`
}

func main() {

	// Configure Thing ORM (in-memory DB and cache for demo)
	thing.Configure()

	// Auto-migrate User table
	if err := thing.AutoMigrate(&User{}); err != nil {
		log.Fatal(err)
	}

	// Get the User ORM object
	users, err := thing.Use[*User]()

	// Create
	u := &User{Name: "Alice", Age: 30}
	if err := users.Save(u); err != nil {
		log.Fatal(err)
	}
	fmt.Println("Created:", u)

	// ByID
	found, err := users.ByID(u.ID)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("ByID:", found)

	// Update
	found.Age = 31
	if err := users.Save(found); err != nil {
		log.Fatal(err)
	}
	fmt.Println("Updated:", found)

	// Chainable Query API
	usersOver18, err := users.Where("age > ?", 18).Order("name ASC").Fetch(0, 100)
	if err != nil { /* handle error */ }
	fmt.Println(usersOver18)

	// Delete
	if err := users.Delete(u); err != nil {
		log.Fatal(err)
	}
	fmt.Println("Deleted user.")
}

Flexible JSON Serialization

Thing ORM provides multiple ways to control JSON output fields, order, and nesting:

  • Include(fields ...string): Specify exactly which top-level fields to output, in order. Best for simple, flat cases.
  • Exclude(fields ...string): Specify top-level fields to exclude from output. Can be combined with Include or used alone.
  • WithFields(dsl string): Use a powerful DSL string to control inclusion, exclusion, order, and nested fields (e.g. "name,profile{avatar},-id").

Note:

  • Include and Exclude only support flat (top-level) fields.
  • For nested field control, use WithFields DSL.
  • You can combine Include, Exclude, and WithFields, but only WithFields supports nested and ordered field selection.
Examples
// Only include id, name, and full_name (method-based virtual)
thing.ToJSON(user, thing.Include("id", "name", "full_name"))

// Exclude sensitive fields
thing.ToJSON(user, thing.Exclude("password", "email"))

// Combine Include and Exclude (still only affects top-level fields)
thing.ToJSON(user, thing.Include("id", "name", "email"), thing.Exclude("email"))

// Use WithFields DSL for advanced/nested control
thing.ToJSON(user, thing.WithFields("name,profile{avatar},-id"))
  • WithFields supports nested fields, exclusion (with -field), and output order.
  • Include/Exclude are Go-idiomatic and best for simple, flat cases.
  • Struct tags (e.g. json:"-") always take precedence.
Method-based Virtual Properties

You can define computed (virtual) fields on your model by adding exported, zero-argument, single-return-value methods. These methods will only be included in the JSON output if you explicitly reference their corresponding field name in the DSL string or Include option.

  • Method Naming: Use Go's exported method naming (e.g., FullName). The field name in the DSL should be the snake_case version (e.g., full_name).
  • How it works:
    • If the DSL or Include includes a field name that matches a method (converted to snake_case), the method will be called and its return value included in the output.
    • If the DSL/Include does not mention the virtual field, it will not be output.

Example:

type User struct {
    FirstName string
    LastName  string
}

// Virtual property method
func (u *User) FullName() string {
    return u.FirstName + " " + u.LastName
}

user := &User{FirstName: "Alice", LastName: "Smith"}
jsonBytes, _ := thing.ToJSON(user, thing.WithFields("first_name,full_name"))
fmt.Println(string(jsonBytes))
// Output: {"first_name":"Alice","full_name":"Alice Smith"}
  • If you omit full_name from the DSL or Include, the FullName() method will not be called or included in the output.

This approach gives you full control over which computed fields are exposed, and ensures only explicitly requested virtuals are included in the JSON output.

Relationship Management

Defining Relationships

Thing ORM supports basic relationship management, including preloading related models.

Use thing struct tags to define relationships. The db:"-" tag prevents the ORM from treating the relationship field as a database column.

package models // assuming models are in a separate package

import "github.com/burugo/thing"

// User has many Books
type User struct {
	thing.BaseModel
	Name  string `db:"name"`
	Email string `db:"email"`
	// Define HasMany relationship:
	// - fk: Foreign key in the 'Book' table (user_id)
	// - model: Name of the related model struct (Book)
	Books []*Book `thing:"hasMany;fk:user_id;model:Book" db:"-"`
}

func (u *User) TableName() string { return "users" }

// Book belongs to a User
type Book struct {
	thing.BaseModel
	Title  string `db:"title"`
	UserID int64  `db:"user_id"` // Foreign key column
	// Define BelongsTo relationship:
	// - fk: Foreign key in the 'Book' table itself (user_id)
	User *User `thing:"belongsTo;fk:user_id" db:"-"`
}

func (b *Book) TableName() string { return "books" }

Use the Preloads field in QueryParams to specify relationships to eager-load.

package main

import (
	"fmt"
	"log"

	"github.com/burugo/thing"
	// import your models package e.g., "yourproject/models"
)

func main() {
	// Assume thing.Configure() and AutoMigrate(&models.User{}, &models.Book{}) are done
	// Assume user and books are created...

	userThing, _ := thing.Use[*models.User]()
	bookThing, _ := thing.Use[*models.Book]()

	// Example 1: Find a user and preload their books (HasMany)
	userParams := thing.QueryParams{
		Where:    "id = ?",
		Args:     []interface{}{1}, // Assuming user with ID 1 exists
		Preloads: []string{"Books"}, // Specify the relationship field name
	}
	userResult := userThing.Query(userParams)
	fetchedUsers, _ := userResult.Fetch(0, 1)
	if len(fetchedUsers) > 0 {
		fmt.Printf("User: %s, Number of Books: %d\n", fetchedUsers[0].Name, len(fetchedUsers[0].Books))
		// fetchedUsers[0].Books is now populated
	}

	// Example 2: Find a book and preload its user (BelongsTo)
	bookParams := thing.QueryParams{
		Where:    "id = ?",
		Args:     []interface{}{5}, // Assuming book with ID 5 exists
		Preloads: []string{"User"}, // Specify the relationship field name
	}
	bookResult := bookThing.Query(bookParams)
	fetchedBooks, _ := bookResult.Fetch(0, 1)
	if len(fetchedBooks) > 0 && fetchedBooks[0].User != nil {
		fmt.Printf("Book: %s, Owner: %s\n", fetchedBooks[0].Title, fetchedBooks[0].User.Name)
		// fetchedBooks[0].User is now populated
	}
}

Thing ORM automatically fetches the related models in an optimized way, utilizing the cache where possible.

Hooks & Events

Thing ORM provides a hook system that allows you to register functions (listeners) to be executed before or after specific database operations. This is useful for tasks like validation, logging, data modification, or triggering side effects.

Available Events
  • EventTypeBeforeSave: Before creating or updating a record.
  • EventTypeAfterSave: After successfully creating or updating a record.
  • EventTypeBeforeCreate: Before creating a new record (subset of BeforeSave).
  • EventTypeAfterCreate: After successfully creating a new record.
  • EventTypeBeforeDelete: Before hard deleting a record.
  • EventTypeAfterDelete: After successfully hard deleting a record.
  • EventTypeBeforeSoftDelete: Before soft deleting a record.
  • EventTypeAfterSoftDelete: After successfully soft deleting a record.
Registering Listeners

Use thing.RegisterListener to attach your hook function to an event type. The listener function receives the context, event type, the model instance, and optional event-specific data.

Listener Signature:

func(ctx context.Context, eventType thing.EventType, model interface{}, eventData interface{}) error
  • Returning an error from a Before* hook will abort the database operation.
  • eventData for EventTypeAfterSave contains a map[string]interface{} of changed fields.
Example
package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	"github.com/burugo/thing"
	// Assume User model is defined
)

// Example Hook: Validate email before saving
func validateEmailHook(ctx context.Context, eventType thing.EventType, model interface{}, eventData interface{}) error {
	if user, ok := model.(*User); ok { // Type assert to your model
		log.Printf("[HOOK %s] Checking user: %s, Email: %s", eventType, user.Name, user.Email)
		if user.Email == "invalid@example.com" {
			return errors.New("invalid email provided")
		}
	}
	return nil
}

// Example Hook: Log after creation
func logAfterCreateHook(ctx context.Context, eventType thing.EventType, model interface{}, eventData interface{}) error {
	if user, ok := model.(*User); ok {
		log.Printf("[HOOK %s] User created! ID: %d, Name: %s", eventType, user.ID, user.Name)
	}
	return nil
}

func main() {
	// Assume thing.Configure() and thing.AutoMigrate(&User{}) are done

	// Register hooks
	thing.RegisterListener(thing.EventTypeBeforeSave, validateEmailHook)
	thing.RegisterListener(thing.EventTypeAfterCreate, logAfterCreateHook)

	// Get ORM instance
	users, _ := thing.Use[*User]()

	// 1. Attempt to save user with invalid email (will be aborted by hook)
	invalidUser := &User{Name: "Invalid", Email: "invalid@example.com"}
	err := users.Save(invalidUser)
	if err != nil {
		fmt.Printf("Failed to save invalid user (as expected): %v\n", err)
	}

	// 2. Save a valid user (triggers BeforeSave and AfterCreate hooks)
	validUser := &User{Name: "Valid Hook User", Email: "valid@example.com"}
	err = users.Save(validUser)
	if err != nil {
		log.Fatalf("Failed to save valid user: %v", err)
	} else {
		fmt.Printf("Successfully saved valid user ID: %d\n", validUser.ID)
	}

	// Unregistering listeners is also possible with thing.UnregisterListener
}

Caching & Monitoring

Cache Monitoring & Hit/Miss Statistics

Thing ORM provides built-in cache operation monitoring for all cache clients (including Redis and the mock client used in tests). This monitoring capability is a core, integrated feature, not an add-on.

You can call GetCacheStats(ctx)

Documentation

Index

Constants

View Source
const (
	LockDuration   = 5 * time.Second
	LockRetryDelay = 50 * time.Millisecond
	LockMaxRetries = 5
)

Lock duration constants for cache locking

View Source
const (
	ByIDBatchSize = 100 // Size of batches for fetching by ID from DB
)

--- Constants used internally ---

Variables

This section is empty.

Functions

func AutoMigrate

func AutoMigrate(models ...interface{}) error

AutoMigrate 生成并执行建表 SQL,支持批量建表和 schema diff

func Configure

func Configure(args ...interface{}) error

Configure sets up the package-level database and cache clients, and the global cache TTL. Usage:

Configure(db) // uses provided DB, default local cache
Configure(db, cache) // uses provided DB and cache
Configure(db, cache, ttl) // uses all provided

This MUST be called once during application initialization before using Use[T].

func ConfigureWithConfig

func ConfigureWithConfig(cfg Config) error

ConfigureWithConfig sets up the package-level database and cache clients using a Config struct.

func GenerateCacheKey

func GenerateCacheKey(prefix, tableName string, params QueryParams) string

GenerateCacheKey generates a cache key for list or count queries with normalized arguments.

func GenerateMigrationSQL

func GenerateMigrationSQL(models ...interface{}) ([]string, error)

GenerateMigrationSQL 生成建表 SQL,但不执行,支持批量模型

func GenerateQueryHash

func GenerateQueryHash(params QueryParams) string

GenerateQueryHash generates a unique hash for a given query.

func NewThingByType

func NewThingByType(modelType reflect.Type, db DBAdapter, cache CacheClient) (interface{}, error)

NewThingByType creates a *Thing for a given model type (reflect.Type)

func RegisterIntrospectorFactory

func RegisterIntrospectorFactory(dialect string, factory IntrospectorFactory)

RegisterIntrospectorFactory registers a factory for a given dialect (e.g. "sqlite", "mysql", "postgres").

func RegisterListener

func RegisterListener(eventType EventType, listener EventListener)

RegisterListener adds a listener function for a specific event type.

func ResetListeners

func ResetListeners()

ResetListeners clears all registered event listeners. Primarily intended for use in tests.

func UnregisterListener

func UnregisterListener(eventType EventType, listenerToRemove EventListener)

UnregisterListener removes a specific listener function for a specific event type. It compares function pointers to find the listener to remove.

func WithLock

func WithLock(ctx context.Context, cache CacheClient, lockKey string, action func(ctx context.Context) error) error

WithLock acquires a lock, executes the action, and releases the lock.

Types

type BaseModel

type BaseModel struct {
	ID        int64     `json:"id" db:"id,pk"`              // Primary key (Added pk tag)
	CreatedAt time.Time `json:"created_at" db:"created_at"` // Timestamp for creation
	UpdatedAt time.Time `json:"updated_at" db:"updated_at"` // Timestamp for last update
	Deleted   bool      `json:"deleted" db:"deleted"`       // Soft delete flag
	// contains filtered or unexported fields
}

BaseModel provides common fields and functionality for database models. It should be embedded into specific model structs.

func (BaseModel) GetID

func (b BaseModel) GetID() int64

GetID returns the primary key value.

func (*BaseModel) IsNewRecord

func (b *BaseModel) IsNewRecord() bool

IsNewRecord returns whether this is a new record.

func (BaseModel) KeepItem

func (b BaseModel) KeepItem() bool

KeepItem checks if the record is considered active (not soft-deleted).

func (*BaseModel) SetID

func (b *BaseModel) SetID(id int64)

SetID sets the primary key value.

func (*BaseModel) SetNewRecordFlag

func (b *BaseModel) SetNewRecordFlag(isNew bool)

SetNewRecordFlag sets the internal isNewRecord flag.

func (BaseModel) TableName

func (b BaseModel) TableName() string

TableName returns the database table name for the model. Default implementation returns empty string, relying on getTableNameFromType. Override this method in your specific model struct for custom table names.

type CacheClient

type CacheClient interface {
	Get(ctx context.Context, key string) (string, error)
	Set(ctx context.Context, key string, value string, expiration time.Duration) error
	Delete(ctx context.Context, key string) error

	GetModel(ctx context.Context, key string, dest interface{}) error
	SetModel(ctx context.Context, key string, model interface{}, fieldsToCache []string, expiration time.Duration) error
	DeleteModel(ctx context.Context, key string) error

	GetQueryIDs(ctx context.Context, queryKey string) ([]int64, error)
	SetQueryIDs(ctx context.Context, queryKey string, ids []int64, expiration time.Duration) error
	DeleteQueryIDs(ctx context.Context, queryKey string) error

	AcquireLock(ctx context.Context, lockKey string, expiration time.Duration) (bool, error)
	ReleaseLock(ctx context.Context, lockKey string) error

	GetCacheStats(ctx context.Context) CacheStats
}

CacheClient defines the interface for cache drivers.

var DefaultLocalCache CacheClient = &localCache{}

DefaultLocalCache is the default in-memory cache client for Thing ORM.

type CacheKeyLockManagerInternal

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

CacheKeyLockManagerInternal manages a map of mutexes, one for each cache key. It uses sync.Map for efficient concurrent access.

func NewCacheKeyLockManagerInternal

func NewCacheKeyLockManagerInternal() *CacheKeyLockManagerInternal

NewCacheKeyLockManagerInternal creates a new lock manager.

func (*CacheKeyLockManagerInternal) Lock

func (m *CacheKeyLockManagerInternal) Lock(key string)

Lock acquires the mutex associated with the given cache key. If the mutex does not exist, it is created. This operation blocks until the lock is acquired.

func (*CacheKeyLockManagerInternal) Unlock

func (m *CacheKeyLockManagerInternal) Unlock(key string)

Unlock releases the mutex associated with the given cache key.

type CacheStats

type CacheStats struct {
	Counters map[string]int // Operation name to count
}

CacheStats holds cache operation counters for monitoring.

type CachedResult

type CachedResult[T Model] struct {
	Err error // New: holds error if Query failed to initialize
	// contains filtered or unexported fields
}

CachedResult represents a cached query result with lazy loading capabilities. It allows for efficient querying with pagination and caching.

func (*CachedResult[T]) All

func (cr *CachedResult[T]) All() ([]T, error)

All retrieves all records matching the query. It first gets the total count and then fetches all records using Fetch.

func (*CachedResult[T]) Count

func (cr *CachedResult[T]) Count() (int64, error)

Count returns the total number of records matching the query. It utilizes caching to avoid redundant database calls.

func (*CachedResult[T]) Fetch

func (cr *CachedResult[T]) Fetch(offset, limit int) ([]T, error)

Fetch returns a subset of records starting from the given offset with the specified limit. It filters out soft-deleted items and triggers cache updates if inconsistencies are found. This implementation closely follows the CachedResult.fetch() logic: - It iteratively fetches batches from cache or DB - It filters items using KeepItem() - It dynamically calculates how many more items to fetch based on filtering results

func (*CachedResult[T]) First

func (cr *CachedResult[T]) First() (T, error)

func (*CachedResult[T]) Order added in v0.1.2

func (cr *CachedResult[T]) Order(order string) *CachedResult[T]

Order on CachedResult: returns a new instance with updated Order

func (*CachedResult[T]) Preload added in v0.1.2

func (cr *CachedResult[T]) Preload(preloads ...string) *CachedResult[T]

Preload on CachedResult: returns a new instance with updated Preloads

func (*CachedResult[T]) Where added in v0.1.2

func (cr *CachedResult[T]) Where(where string, args ...interface{}) *CachedResult[T]

Where on CachedResult: returns a new instance with updated Where/Args

func (*CachedResult[T]) WithDeleted

func (cr *CachedResult[T]) WithDeleted() *CachedResult[T]

WithDeleted returns a new CachedResult instance that will include soft-deleted records in its results.

type Config

type Config struct {
	DB    DBAdapter   // User must initialize and provide
	Cache CacheClient // User must initialize and provide
	TTL   time.Duration
}

Config holds configuration for the Thing ORM.

type DBAdapter

type DBAdapter interface {
	Get(ctx context.Context, dest interface{}, query string, args ...interface{}) error
	Select(ctx context.Context, dest interface{}, query string, args ...interface{}) error
	Exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
	GetCount(ctx context.Context, tableName string, where string, args []interface{}) (int64, error)
	BeginTx(ctx context.Context, opts *sql.TxOptions) (Tx, error)
	Close() error
	DB() *sql.DB
	Builder() SQLBuilder
	DialectName() string
}

DBAdapter defines the interface for database drivers.

func GlobalDB

func GlobalDB() DBAdapter

GlobalDB returns the global DBAdapter (for internal use, e.g., AutoMigrate)

type Dialector

type Dialector interface {
	Quote(identifier string) string // Quote a SQL identifier (table/column name)
	Placeholder(index int) string   // Bind variable placeholder (e.g. ?, $1)
}

Dialector defines how to quote identifiers and bind variables for a specific SQL dialect.

type EventListener

type EventListener func(ctx context.Context, eventType EventType, model interface{}, eventData interface{}) error

EventListener defines the signature for functions that can listen to events.

type EventType

type EventType string

EventType defines the type for lifecycle events.

const (
	EventTypeBeforeSave       EventType = "BeforeSave"
	EventTypeAfterSave        EventType = "AfterSave"
	EventTypeBeforeCreate     EventType = "BeforeCreate"
	EventTypeAfterCreate      EventType = "AfterCreate"
	EventTypeBeforeDelete     EventType = "BeforeDelete"
	EventTypeAfterDelete      EventType = "AfterDelete"
	EventTypeBeforeSoftDelete EventType = "BeforeSoftDelete"
	EventTypeAfterSoftDelete  EventType = "AfterSoftDelete"
)

Standard lifecycle event types

type FieldRule

type FieldRule struct {
	Name   string
	Nested *JSONOptions // Nested options for this specific field
}

FieldRule represents a single included field and its potential nested options.

type IntrospectorFactory

type IntrospectorFactory func(DBAdapter) schema.Introspector

IntrospectorFactory is a function that returns a schema.Introspector for a given DBAdapter.

type JSONOption

type JSONOption func(*JSONOptions)

JSONOption defines the function signature for JSON serialization options.

func Exclude

func Exclude(fields ...string) JSONOption

Exclude specifies fields to exclude from the JSON output (now supports nested DSL via WithFields logic).

func Include

func Include(fields ...string) JSONOption

Include specifies fields to include in the JSON output (now supports nested DSL via WithFields logic).

func WithFields

func WithFields(dsl string) JSONOption

WithFields specifies fields to include/exclude using a DSL string (supports nested, exclude, etc.).

type JSONOptions

type JSONOptions struct {
	OrderedInclude []*FieldRule            // Ordered list of fields to include (with possible nested rules)
	OrderedExclude []string                // Ordered list of fields to exclude
	NestedRules    map[string]*JSONOptions // Stores nested rules for any field, regardless of include/exclude status
}

JSONOptions holds the options for JSON serialization.

func ParseFieldsDSL

func ParseFieldsDSL(dsl string) (*JSONOptions, error)

ParseFieldsDSL parses a DSL string and returns populated jsonOptions representing the rules.

type Model

type Model interface {
	KeepItem() bool
	GetID() int64
}

Model is the base interface for all ORM models.

type QueryParams

type QueryParams struct {
	Where          string
	Args           []interface{}
	Order          string
	Preloads       []string
	IncludeDeleted bool
}

QueryParams is the public query parameter type for Thing ORM queries.

type RelationshipOpts

type RelationshipOpts struct {
	RelationType   string // "belongsTo", "hasMany", "manyToMany"
	ForeignKey     string // FK field name in the *owning* struct (for belongsTo) or *related* struct (for hasMany)
	LocalKey       string // PK field name in the *owning* struct (defaults to info.pkName)
	RelatedModel   string // Optional: Specify related model name if different from field type
	JoinTable      string // For manyToMany: join table name
	JoinLocalKey   string // For manyToMany: join table column for local model
	JoinRelatedKey string // For manyToMany: join table column for related model
}

RelationshipOpts defines the configuration for a relationship based on struct tags.

type SQLBuilder

type SQLBuilder interface {
	BuildSelectSQL(tableName string, columns []string) string
	BuildSelectIDsSQL(tableName string, pkName string, where string, args []interface{}, order string) (string, []interface{})
	BuildInsertSQL(tableName string, columns []string) string
	BuildUpdateSQL(tableName string, setClauses []string, pkName string) string
	BuildDeleteSQL(tableName, pkName string) string
	BuildCountSQL(tableName string, whereClause string) string
	Rebind(query string) string
}

SQLBuilder defines the contract for SQL generation with dialect-specific identifier quoting.

func NewSQLBuilder

func NewSQLBuilder(d Dialector) SQLBuilder

--- SQLBuilder Factory ---

type Thing

type Thing[T Model] struct {
	// contains filtered or unexported fields
}

Thing is the central access point for ORM operations, analogous to gorm.DB. It holds database/cache clients and the context for operations.

func New

func New[T Model](db DBAdapter, cache CacheClient) (*Thing[T], error)

New creates a new Thing instance with default context.Background(). Accepts one or more CacheClient; if none provided, uses defaultLocalCache.

func Use

func Use[T Model]() (*Thing[T], error)

Use returns a Thing instance for the specified type T, using the globally configured DBAdapter and CacheClient. The package MUST be configured using Configure() before calling Use[T].

func (*Thing[T]) All added in v0.1.3

func (t *Thing[T]) All() ([]T, error)

All is a convenience method to query and fetch all records matching the default QueryParams. It's equivalent to calling thingInstance.Query(QueryParams{}).All().

func (*Thing[T]) ByID

func (t *Thing[T]) ByID(id int64) (T, error)

ByID fetches a single model by its ID.

func (*Thing[T]) ByIDs

func (t *Thing[T]) ByIDs(ids []int64, preloads ...string) (map[int64]T, error)

ByIDs retrieves multiple records by their primary keys and optionally preloads relations.

func (*Thing[T]) Cache

func (t *Thing[T]) Cache() CacheClient

Cache returns the underlying CacheClient associated with this Thing instance.

func (*Thing[T]) CacheStats

func (t *Thing[T]) CacheStats(ctx context.Context) CacheStats

CacheStats returns cache operation statistics for monitoring and hit/miss analysis.

func (*Thing[T]) ClearCacheByID

func (t *Thing[T]) ClearCacheByID(ctx context.Context, id int64) error

ClearCacheByID removes the cache entry for a specific model instance by its ID. Note: This is now a Thing[T] method.

func (*Thing[T]) DB

func (t *Thing[T]) DB() *sql.DB

DB returns the underlying *sql.DB for advanced/raw SQL use cases.

func (*Thing[T]) Delete

func (t *Thing[T]) Delete(value T) error

Delete performs a hard delete on the record from the database.

func (*Thing[T]) Load

func (t *Thing[T]) Load(model T, relations ...string) error

Load eagerly loads specified relationships for a given model instance.

func (*Thing[T]) Order added in v0.1.2

func (t *Thing[T]) Order(order string) *CachedResult[T]

Order on Thing: starts a new query chain

func (*Thing[T]) Preload added in v0.1.2

func (t *Thing[T]) Preload(preloads ...string) *CachedResult[T]

Preload on Thing: starts a new query chain

func (*Thing[T]) Query

func (t *Thing[T]) Query(params QueryParams) *CachedResult[T]

Query prepares a query based on QueryParams and returns a *CachedResult[T] for lazy execution. The actual database query happens when Count() or Fetch() is called on the result. Error handling for query execution is done within CachedResult methods.

func (*Thing[T]) Save

func (t *Thing[T]) Save(value T) error

Save creates or updates a record in the database.

func (*Thing[T]) SoftDelete

func (t *Thing[T]) SoftDelete(value T) error

SoftDelete performs a soft delete on the record by setting the 'deleted' flag to true and updating the 'updated_at' timestamp. It uses saveInternal to persist only these changes.

func (*Thing[T]) ToJSON

func (t *Thing[T]) ToJSON(model interface{}, opts ...JSONOption) ([]byte, error)

ToJSON serializes the provided model instance (single or collection) to JSON based on the given options.

func (*Thing[T]) Where added in v0.1.2

func (t *Thing[T]) Where(where string, args ...interface{}) *CachedResult[T]

Where on Thing: starts a new query chain

func (*Thing[T]) WithContext

func (t *Thing[T]) WithContext(ctx context.Context) *Thing[T]

WithContext returns a shallow copy of Thing with the context replaced. This is used to set the context for a specific chain of operations.

type Tx

type Tx interface {
	Get(ctx context.Context, dest interface{}, query string, args ...interface{}) error
	Select(ctx context.Context, dest interface{}, query string, args ...interface{}) error
	Exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
	Commit() error
	Rollback() error
}

Tx defines the interface for transaction operations.

Directories

Path Synopsis
drivers
examples
01_basic_crud command
Standalone example: Basic CRUD operations with Thing ORM Note: Only one main.go can be run at a time in the examples directory.
Standalone example: Basic CRUD operations with Thing ORM Note: Only one main.go can be run at a time in the examples directory.
04_hooks command
internal

Jump to

Keyboard shortcuts

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