optimistic

package module
v0.3.8 Latest Latest
Warning

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

Go to latest
Published: Dec 3, 2025 License: MIT Imports: 12 Imported by: 0

README

GORM Optimistic Locking Plugin

Go Reference Go Report Card

Introduction

This plugin adds optimistic locking support to GORM. It works by tracking version changes in your models using a version field, preventing concurrent updates from overwriting each other's changes. The plugin supports both UUID and ULID version field types.

Installation

    var db *gorm.DB

    // Use default options: TagName = `Version`
    db.Use(optimistic.NewOptimisticLock())

Model Integration

This plugin works by inspecting your model gorm tags. When it finds a struct field with a gorm tag matching Config.TagName, that field will be marked as the Version field for that model.

Any non-unscoped, non-dryrun, targeted modifications of the model will require a valid version value in order for the change to persist to the underlying database. A targeted modification is one where the primary key(s) are included in the modification. This means that multi-row updates (for example UPDATE "users" SET "active" = false WHERE "active" = true AND "idle_time" > 300) where the ID of the model is not specified in the request.

Examples
Number-based versioning

This model will be configured with a uint64 flavor of versioning. This means every optimistic lock supported update to the model will increment the version by + 1.

Example model:

    type User struct {
        ID          uint64  `gorm:"<-:create;autoIncrement;primaryKey"`
        Name        string  `gorm:"type:text;"`
        Code        uint64  `gorm:"type:numeric;"`
        Enabled     bool    `gorm:"type:bool;"`
        Version     uint64  `gorm:"type:numeric;not null;version"`
    }
UUID-based versioning

Example model:

    type User struct {
        ID          uint64      `gorm:"<-:create;autoIncrement;primaryKey"`
        Name        string      `gorm:"type:text;"`
        Code        uint64      `gorm:"type:numeric;"`
        Enabled     bool        `gorm:"type:bool;"`
        Version     uuid.UUID   `gorm:"not null;version"`
    }

This model will be configured with a UUID flavor of versioning. This means every optimistic lock supported update to the model will set the version to a new UUID. Note: this versioning strategy will work with any uuid-type that is a type alias to [16]byte. Under the hood, github.com/google/uuid is used to generate a new uuidv4 but the value persisted to the database is either []byte or string depending on the underlying database driver.

ULID-based versioning

Example model:

    type User struct {
        ID          uint64      `gorm:"<-:create;autoIncrement;primaryKey"`
        Name        string      `gorm:"type:text;"`
        Code        uint64      `gorm:"type:numeric;"`
        Enabled     bool        `gorm:"type:bool;"`
        Version     ulid.ULID   `gorm:"not null;version"`
    }

This model will be configured with a ULID flavor of versioning. This means every optimistic lock supported update to the model will set the version to a new ULID. Note: this versioning strategy will work with any ulid-type that is a type alias to [16]byte. Under the hood, github.com/oklog/ulid/v2 is used to generate a new ulid but the value persisted to the database is either []byte or string depending on the underlying database driver.

Time-based versioning

Example model:

    type User struct {
        ID          uint64      `gorm:"<-:create;autoIncrement;primaryKey"`
        Name        string      `gorm:"type:text;"`
        Code        uint64      `gorm:"type:numeric;"`
        Enabled     bool        `gorm:"type:bool;"`
        Version     time.Time   `gorm:"not null;version"`
    }

This model will be configured with a Timestamp flavor of versioning. This means every optimistic lock supported update to the model will set the version to a new Timestamp. Your mileage may vary with this particular version type. Different databases have different mappings for time.Time. Some are more coarse-grained than others and may not yield desirable optimistic locking results.

Issues

If you have issues please open a PR

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrOptimisticLock = errors.New("optimistic lock conflict")
)

Functions

func NewOptimisticLock

func NewOptimisticLock(options ...ConfigOption) gorm.Plugin

NewOptimisticLock returns the plugin for db.Use(...)

Types

type Change added in v0.2.3

type Change struct {
	From any `json:"from" mapstructure:"from"`
	To   any `json:"to" mapstructure:"to"`
}

func (Change) MarshalJSON added in v0.2.3

func (c Change) MarshalJSON() ([]byte, error)

func (Change) String added in v0.2.3

func (c Change) String() string

func (*Change) UnmarshalJSON added in v0.2.3

func (c *Change) UnmarshalJSON(b []byte) error

type Config added in v0.3.1

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

type ConfigOption added in v0.3.1

type ConfigOption func(*Config)

func WithConfig added in v0.3.1

func WithConfig(cfg Config) ConfigOption

func WithDisableReturning added in v0.3.1

func WithDisableReturning() ConfigOption

func WithTagName added in v0.3.1

func WithTagName(tagName string) ConfigOption

type Conflict added in v0.2.3

type Conflict struct {
	OnVersionMismatch func(current any, diff map[string]Change) any
}

Conflict lets users hook into version mismatches to merge or cancel.

func (Conflict) Build added in v0.2.3

func (x Conflict) Build(clause.Builder)

func (Conflict) MergeClause added in v0.2.3

func (x Conflict) MergeClause(c *clause.Clause)

func (Conflict) Name added in v0.2.3

func (x Conflict) Name() string

type Plugin added in v0.2.3

type Plugin struct {
	*Config
}

Plugin wires up optimistic‐locking callbacks.

func (*Plugin) Initialize added in v0.2.3

func (p *Plugin) Initialize(db *gorm.DB) error

func (Plugin) Name added in v0.2.3

func (Plugin) Name() string

Jump to

Keyboard shortcuts

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