or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

associations.mdclause.mddatabase-operations.mdhooks.mdindex.mdlogger.mdmigrations.mdquery-building.mdschema.mdtransactions.md
tile.json

migrations.mddocs/

Schema Migrations

GORM provides automatic schema migration functionality to keep your database schema in sync with your Go models.

Auto Migration

Automatically migrate your schema to keep it up to date.

func (db *DB) AutoMigrate(dst ...interface{}) error

Usage:

// Migrate single model
db.AutoMigrate(&User{})

// Migrate multiple models
db.AutoMigrate(&User{}, &Order{}, &Product{})

// Check for errors
if err := db.AutoMigrate(&User{}); err != nil {
    // Handle migration error
}

What AutoMigrate does:

  • Creates tables for models that don't exist
  • Adds missing columns
  • Adds missing indexes
  • Does NOT delete unused columns (for data safety)
  • Does NOT change existing column types (for data safety)

Migrator Interface

Get the migrator for manual schema operations.

func (db *DB) Migrator() Migrator

The Migrator interface provides comprehensive schema management methods.

type Migrator interface {
    // AutoMigrate
    AutoMigrate(dst ...interface{}) error

    // Database
    CurrentDatabase() string

    // Tables
    CreateTable(dst ...interface{}) error
    DropTable(dst ...interface{}) error
    HasTable(dst interface{}) bool
    RenameTable(oldName, newName interface{}) error
    GetTables() (tableList []string, err error)
    TableType(dst interface{}) (TableType, error)

    // Columns
    AddColumn(dst interface{}, field string) error
    DropColumn(dst interface{}, field string) error
    AlterColumn(dst interface{}, field string) error
    MigrateColumn(dst interface{}, field *schema.Field, columnType ColumnType) error
    HasColumn(dst interface{}, field string) bool
    RenameColumn(dst interface{}, oldName, newName string) error
    ColumnTypes(dst interface{}) ([]ColumnType, error)

    // Views
    CreateView(name string, option ViewOption) error
    DropView(name string) error

    // Constraints
    CreateConstraint(dst interface{}, name string) error
    DropConstraint(dst interface{}, name string) error
    HasConstraint(dst interface{}, name string) bool

    // Indexes
    CreateIndex(dst interface{}, name string) error
    DropIndex(dst interface{}, name string) error
    HasIndex(dst interface{}, name string) bool
    RenameIndex(dst interface{}, oldName, newName string) error
    GetIndexes(dst interface{}) ([]Index, error)

    // Data Types
    FullDataTypeOf(*schema.Field) clause.Expr
    GetTypeAliases(databaseTypeName string) []string
}

Table Operations

Create Table

Create tables for models.

m := db.Migrator()

// Create single table
m.CreateTable(&User{})

// Create multiple tables
m.CreateTable(&User{}, &Order{}, &Product{})

Drop Table

Drop tables.

m := db.Migrator()

// Drop single table
m.DropTable(&User{})

// Drop multiple tables
m.DropTable(&User{}, &Order{})

// Drop table by name
m.DropTable("users")

Check if Table Exists

m := db.Migrator()

// Check if table exists
if m.HasTable(&User{}) {
    // Table exists
}

// Check by name
if m.HasTable("users") {
    // Table exists
}

Rename Table

m := db.Migrator()

// Rename table
m.RenameTable(&User{}, &Customer{})

// Rename by name
m.RenameTable("users", "customers")

Get All Tables

m := db.Migrator()

tables, err := m.GetTables()
if err != nil {
    // Handle error
}

for _, table := range tables {
    fmt.Println(table)
}

Get Table Type Information

m := db.Migrator()

tableType, err := m.TableType(&User{})
if err != nil {
    // Handle error
}

fmt.Println("Schema:", tableType.Schema())
fmt.Println("Name:", tableType.Name())
fmt.Println("Type:", tableType.Type())
if comment, ok := tableType.Comment(); ok {
    fmt.Println("Comment:", comment)
}

Column Operations

Add Column

Add a new column to a table.

m := db.Migrator()

type User struct {
    ID   uint
    Name string
    Age  int  // New field
}

// Add the Age column
m.AddColumn(&User{}, "Age")

Drop Column

Drop a column from a table.

m := db.Migrator()

m.DropColumn(&User{}, "Age")

Check if Column Exists

m := db.Migrator()

if m.HasColumn(&User{}, "Age") {
    // Column exists
}

Rename Column

m := db.Migrator()

m.RenameColumn(&User{}, "Name", "FullName")

Alter Column

Change a column's type or constraints.

m := db.Migrator()

type User struct {
    ID   uint
    Name string `gorm:"size:256"` // Change size
}

// Alter the Name column to match new definition
m.AlterColumn(&User{}, "Name")

Get Column Types

Get information about all columns in a table.

m := db.Migrator()

columnTypes, err := m.ColumnTypes(&User{})
if err != nil {
    // Handle error
}

for _, col := range columnTypes {
    fmt.Println("Name:", col.Name())
    fmt.Println("Type:", col.DatabaseTypeName())

    if length, ok := col.Length(); ok {
        fmt.Println("Length:", length)
    }

    if nullable, ok := col.Nullable(); ok {
        fmt.Println("Nullable:", nullable)
    }

    if unique, ok := col.Unique(); ok {
        fmt.Println("Unique:", unique)
    }

    if primaryKey, ok := col.PrimaryKey(); ok {
        fmt.Println("Primary Key:", primaryKey)
    }
}

Index Operations

Create Index

Create an index defined in the model.

type User struct {
    gorm.Model
    Name  string `gorm:"index"`
    Email string `gorm:"uniqueIndex"`
}

m := db.Migrator()

// Create specific index
m.CreateIndex(&User{}, "Name")
m.CreateIndex(&User{}, "Email")

// Index names are generated automatically, or specify with idx_name
type User struct {
    gorm.Model
    Name  string `gorm:"index:idx_name"`
}
m.CreateIndex(&User{}, "idx_name")

Drop Index

m := db.Migrator()

m.DropIndex(&User{}, "idx_name")

Check if Index Exists

m := db.Migrator()

if m.HasIndex(&User{}, "idx_name") {
    // Index exists
}

Rename Index

m := db.Migrator()

m.RenameIndex(&User{}, "old_idx_name", "new_idx_name")

Get All Indexes

m := db.Migrator()

indexes, err := m.GetIndexes(&User{})
if err != nil {
    // Handle error
}

for _, idx := range indexes {
    fmt.Println("Name:", idx.Name())
    fmt.Println("Table:", idx.Table())
    fmt.Println("Columns:", idx.Columns())

    if primaryKey, ok := idx.PrimaryKey(); ok {
        fmt.Println("Primary Key:", primaryKey)
    }

    if unique, ok := idx.Unique(); ok {
        fmt.Println("Unique:", unique)
    }

    fmt.Println("Options:", idx.Option())
}

Constraint Operations

Create Constraint

Create a foreign key or check constraint defined in the model.

type User struct {
    gorm.Model
    CompanyID int
    Company   Company `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}

m := db.Migrator()

// Create the constraint
m.CreateConstraint(&User{}, "Company")

// Or by constraint name
m.CreateConstraint(&User{}, "fk_users_company")

Drop Constraint

m := db.Migrator()

m.DropConstraint(&User{}, "Company")
m.DropConstraint(&User{}, "fk_users_company")

Check if Constraint Exists

m := db.Migrator()

if m.HasConstraint(&User{}, "Company") {
    // Constraint exists
}

View Operations

Create View

Create a database view.

type ViewOption struct {
    Replace     bool    // Replace existing view
    CheckOption string  // Check option clause
    Query       *DB     // Query for view
}

Usage:

m := db.Migrator()

// Create view
m.CreateView("active_users", gorm.ViewOption{
    Query: db.Model(&User{}).Where("active = ?", true),
})

// Create or replace view
m.CreateView("active_users", gorm.ViewOption{
    Replace: true,
    Query:   db.Model(&User{}).Where("active = ?", true),
})

Drop View

m := db.Migrator()

m.DropView("active_users")

Migration Configuration

Configure migration behavior globally or per-session.

// Disable foreign key constraints during migration
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    DisableForeignKeyConstraintWhenMigrating: true,
})

// Ignore relationships when migrating
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    IgnoreRelationshipsWhenMigrating: true,
})

Model Tags for Migration

Use struct tags to control migration behavior.

type User struct {
    gorm.Model
    Name      string `gorm:"size:256;not null;default:'unknown'"`
    Email     string `gorm:"uniqueIndex;size:256"`
    Age       int    `gorm:"check:age >= 0"`
    Active    bool   `gorm:"default:true"`
    CompanyID int
    Company   Company `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`

    // Ignore this field in migration
    TempData string `gorm:"-:migration"`
}

// Common migration tags:
// - size:256          Set column size
// - not null          NOT NULL constraint
// - default:value     Default value
// - unique            UNIQUE constraint
// - uniqueIndex       Create unique index
// - index             Create index
// - check:expr        CHECK constraint
// - constraint:...    Foreign key constraint options
// - -:migration       Ignore field in migration
// - comment:text      Column comment

Index Definition

Define indexes using struct tags or models.

type User struct {
    gorm.Model

    // Simple index
    Name string `gorm:"index"`

    // Named index
    Email string `gorm:"index:idx_email"`

    // Unique index
    Username string `gorm:"uniqueIndex"`

    // Composite index
    FirstName string `gorm:"index:idx_name"`
    LastName  string `gorm:"index:idx_name"`

    // Index with options
    Code string `gorm:"index:,type:btree,length:10,sort:desc"`
}

Check Constraints

Define check constraints for data validation.

type User struct {
    gorm.Model
    Name string
    Age  int    `gorm:"check:age >= 0"`
    Role string `gorm:"check:role IN ('admin', 'user', 'guest')"`
}

// Multiple constraints
type User struct {
    gorm.Model
    Age    int `gorm:"check:age >= 0,age_positive;check:age <= 120,age_max"`
    Salary int `gorm:"check:salary >= 0"`
}

Current Database

Get the current database name.

m := db.Migrator()

dbName := m.CurrentDatabase()
fmt.Println("Current database:", dbName)

Custom Data Types

Implement custom data types for migration.

import "gorm.io/gorm/schema"

type GormDataTypeInterface interface {
    GormDataType() string
}

type BuildIndexOptionsInterface interface {
    BuildIndexOptions([]schema.IndexOption, *gorm.Statement) []interface{}
}

Usage:

import "gorm.io/gorm/migrator"

type JSON json.RawMessage

// GormDataType returns the database data type
func (JSON) GormDataType() string {
    return "json"
}

// Use in model
type User struct {
    gorm.Model
    Metadata JSON
}

Migration Best Practices

Safe Migrations

// Check before dropping
if db.Migrator().HasTable(&OldModel{}) {
    db.Migrator().DropTable(&OldModel{})
}

// Check before adding column
if !db.Migrator().HasColumn(&User{}, "Age") {
    db.Migrator().AddColumn(&User{}, "Age")
}

Version Control

Keep track of migration versions in your application.

type Migration struct {
    gorm.Model
    Version string `gorm:"uniqueIndex"`
    Applied time.Time
}

func migrate(db *gorm.DB) error {
    // Create migration tracking table
    db.AutoMigrate(&Migration{})

    // Check if migration already applied
    var count int64
    db.Model(&Migration{}).Where("version = ?", "v1.0.0").Count(&count)

    if count == 0 {
        // Apply migration
        if err := db.AutoMigrate(&User{}, &Order{}); err != nil {
            return err
        }

        // Record migration
        db.Create(&Migration{
            Version: "v1.0.0",
            Applied: time.Now(),
        })
    }

    return nil
}

Data Migration

Combine schema migration with data migration.

// Migrate schema
db.AutoMigrate(&User{})

// Migrate data
db.Model(&User{}).Where("old_status = ?", "pending").
    Update("status", "active")

Handling Migration Errors

if err := db.AutoMigrate(&User{}); err != nil {
    log.Printf("Migration failed: %v", err)
    // Handle error appropriately
}

m := db.Migrator()

// Check individual operations
if err := m.AddColumn(&User{}, "Age"); err != nil {
    if !m.HasColumn(&User{}, "Age") {
        // Column wasn't added
        log.Printf("Failed to add column: %v", err)
    }
}