Skip to content

Instantly share code, notes, and snippets.

@afa
Forked from dmitry-osin/gorm-cheatsheet-rus.md
Created August 10, 2026 10:17
Show Gist options
  • Select an option

  • Save afa/a47e4b7e33e21cd64d0d61cdfc7e339e to your computer and use it in GitHub Desktop.

Select an option

Save afa/a47e4b7e33e21cd64d0d61cdfc7e339e to your computer and use it in GitHub Desktop.
Шпаргалка GORM (Go ORM)

Подробная шпаргалка по GORM (Go ORM) на примере блога с сущностями User, Post, Tag + доп. пример полиморфизма (Comment).


1. Установка и базовая инициализация

go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres
package main

import (
	"log"
	"os"
	"time"

	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/schema"
)

func setupDB() *gorm.DB {
	dsn := "host=localhost user=blog password=blog dbname=blog port=5432 sslmode=disable TimeZone=Europe/Moscow"
	newLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags),
		logger.Config{
			SlowThreshold:             time.Second,
			LogLevel:                  logger.Info,
			IgnoreRecordNotFoundError: true,
			Colorful:                  true,
		},
	)
	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
		Logger: newLogger,
		NamingStrategy: schema.NamingStrategy{
			SingularTable: false, // posts, users, tags ...
		},
		DisableForeignKeyConstraintWhenMigrating: false,
		SkipDefaultTransaction:                   true, // оптимизация
	})
	if err != nil {
		panic(err)
	}

	// Настройка пула (через sql.DB)
	sqlDB, _ := db.DB()
	sqlDB.SetMaxOpenConns(50)
	sqlDB.SetMaxIdleConns(10)
	sqlDB.SetConnMaxLifetime(time.Hour)

	return db
}

2. Модели (Structs) с тегами GORM

package models

import (
	"time"
	"gorm.io/gorm"
)

// Пользователь
type User struct {
	ID        uint           `gorm:"primaryKey"`
	Login     string         `gorm:"size:50;uniqueIndex;not null"`
	FirstName string         `gorm:"size:100"`
	Surname   string         `gorm:"size:100"`
	Bio       string         `gorm:"type:text"`
	AvatarPath string        `gorm:"size:255"`
	Password  string         `gorm:"size:255;not null"`
	Email     string         `gorm:"size:120;uniqueIndex;not null"`
	IsActive  bool           `gorm:"index;default:true"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt gorm.DeletedAt `gorm:"index"`

	Posts []Post `gorm:"foreignKey:AuthorID"`
	Tags  []Tag  `gorm:"foreignKey:AuthorID"`
}

// Пост
type Post struct {
	ID          uint           `gorm:"primaryKey"`
	Slug        string         `gorm:"size:180;uniqueIndex;not null"`
	Title       string         `gorm:"size:255;index:,opclass:gin"` // пример (но gin реальнее через SQL)
	Description string         `gorm:"type:text"`
	Content     string         `gorm:"type:text"`
	IsPublished bool           `gorm:"index;default:false"`
	AuthorID    uint           `gorm:"index;not null"`
	Author      User           `gorm:"constraint:OnUpdate:CASCADE,OnDelete:RESTRICT"`
	Tags        []Tag          `gorm:"many2many:post_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
	CreatedAt   time.Time
	UpdatedAt   time.Time
	DeletedAt   gorm.DeletedAt `gorm:"index"`
}

// Тег
type Tag struct {
	ID          uint           `gorm:"primaryKey"`
	Title       string         `gorm:"size:120;not null"`
	Slug        string         `gorm:"size:160;uniqueIndex;not null"`
	Description string         `gorm:"type:text"`
	AuthorID    uint           `gorm:"index"`
	Author      User           `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
	Posts       []Post         `gorm:"many2many:post_tags"`
	CreatedAt   time.Time
	UpdatedAt   time.Time
	DeletedAt   gorm.DeletedAt `gorm:"index"`
}

// Связующая таблица (кастомизация) - не обязательно, если дефолта хватает
type PostTag struct {
	PostID uint `gorm:"primaryKey"`
	TagID  uint `gorm:"primaryKey"`
	// Доп. поля, например порядок
	Order int `gorm:"default:0"`
}

// Полиморфизм: Комментарий может относиться к Post или Tag (пример)
type Comment struct {
	ID              uint           `gorm:"primaryKey"`
	Body            string         `gorm:"type:text;not null"`
	AuthorID        uint           `gorm:"index"`
	Author          User
	CommentableID   uint           `gorm:"index"`
	CommentableType string         `gorm:"index"` // "posts" или "tags"
	CreatedAt       time.Time
	UpdatedAt       time.Time
	DeletedAt       gorm.DeletedAt `gorm:"index"`
}

// Привязка полиморфная с помощью тегов в целевых моделях:
type PostWithComments struct {
	Post
	Comments []Comment `gorm:"polymorphic:Commentable;"`
}

type TagWithComments struct {
	Tag
	Comments []Comment `gorm:"polymorphic:Commentable;"`
}

3. Авто-миграции и настройка

func migrate(db *gorm.DB) error {
	return db.AutoMigrate(
		&User{},
		&Post{},
		&Tag{},
		&PostTag{},
		&Comment{},
	)
}

Если нужны кастомные индексы (пример partial index, полнотекст, GIN и пр.) — делаем через Raw SQL после миграции:

db.Exec(`CREATE INDEX IF NOT EXISTS idx_posts_content_tsv ON posts USING GIN (to_tsvector('russian', content));`)

4. CRUD Примеры

Создание

user := User{Login:"john", Email:"john@example.com", Password:"hash"}
db.Create(&user)

post := Post{
	Slug: "first-post",
	Title: "Первый пост",
	Content: "Привет мир",
	AuthorID: user.ID,
}
db.Create(&post)

tags := []Tag{
	{Title:"Golang", Slug:"golang", AuthorID:user.ID},
	{Title:"ORM", Slug:"orm", AuthorID:user.ID},
}
db.Create(&tags)

// Привязка тегов
db.Model(&post).Association("Tags").Append(&tags)

Чтение (Select, Where)

var p Post
db.First(&p, 1)                  // по первичному ключу
db.First(&p, "slug = ?", "first-post")
db.Take(&p, "is_published = ?", true)
db.Last(&p)                      // последний по PK
var posts []Post
db.Where("is_published = ?", true).Find(&posts)
db.Where(&Post{IsPublished:true, AuthorID:user.ID}).Find(&posts) // struct условие (нулевые поля игнорятся)
db.Where(map[string]interface{}{"author_id": user.ID}).Find(&posts)

Выбор конкретных колонок

type MiniPost struct {
	ID    uint
	Title string
}
var minis []MiniPost
db.Model(&Post{}).Select("id", "title").Where("is_published").Scan(&minis)

Обновление

db.Model(&p).Update("title", "Новый заголовок")
db.Model(&Post{}).Where("id = ?", p.ID).Updates(map[string]interface{}{
	"description": "Описание",
	"is_published": true,
})
// Обновление всех изменённых полей
p.Content = "Изменён контент"
db.Save(&p)

Удаление (мягкое)

db.Delete(&p) // sets deleted_at
// Жёсткое удаление
db.Unscoped().Delete(&p)

5. JOIN / Joins API

Явное JOIN

type PostWithAuthor struct {
	ID        uint
	Title     string
	AuthorLogin string
}
var rows []PostWithAuthor

db.Model(&Post{}).
	Select("posts.id, posts.title, users.login AS author_login").
	Joins("JOIN users ON users.id = posts.author_id").
	Where("users.is_active").
	Scan(&rows)

LEFT JOIN + условие

db.Model(&Post{}).
	Joins("LEFT JOIN post_tags ON post_tags.post_id = posts.id").
	Joins("LEFT JOIN tags ON tags.id = post_tags.tag_id").
	Where("tags.slug = ?", "golang").
	Find(&posts)

6. Предзагрузка (Eager Loading / Preload)

var fullPost Post
db.Preload("Author").
	Preload("Tags").
	First(&fullPost, "slug = ?", "first-post")

// Условная предзагрузка
db.Preload("Tags", "slug IN ?", []string{"golang","orm"}).Find(&posts)

// Вложенная предзагрузка (Author каждого Tag)
db.Preload("Tags.Author").Find(&posts)

// Полиморфные (если используете PostWithComments)
var postsWithComments []PostWithComments
db.Preload("Comments.Author").
	Where("is_published").
	Find(&postsWithComments)

7. Ассоциации (Association Mode)

// Append
db.Model(&post).Association("Tags").Append(&Tag{Title:"DB", Slug:"db"})
// Replace
db.Model(&post).Association("Tags").Replace(&tags)
// Clear
db.Model(&post).Association("Tags").Clear()
// Delete (удаляет связь из join-таблицы)
db.Model(&post).Association("Tags").Delete(&tags[0])
// Count
count := db.Model(&post).Association("Tags").Count()

8. Полиморфизм (подробнее)

GORM использует паттерн: в дочерних структурах тег gorm:"polymorphic:Commentable;".
Comment содержит поля CommentableID, CommentableType.
Запись:

post := Post{Slug:"p2", Title:"Post2", AuthorID:user.ID}
db.Create(&post)

comment := Comment{
	Body: "Отлично!",
	AuthorID: user.ID,
	CommentableID: post.ID,
	CommentableType: "posts", // имя таблицы
}
db.Create(&comment)

// Получить комментарии поста
var comments []Comment
db.Where("commentable_id = ? AND commentable_type = ?", post.ID, "posts").Find(&comments)

(Можно обернуть в методы-репозитории.)


9. Транзакции

err := db.Transaction(func(tx *gorm.DB) error {
	u := User{Login:"tx_user", Email:"tx@example.com", Password:"hash"}
	if err := tx.Create(&u).Error; err != nil {
		return err
	}
	p := Post{Slug:"tx-post", Title:"Tx", AuthorID:u.ID}
	if err := tx.Create(&p).Error; err != nil {
		return err
	}
	// Пример отката
	// return errors.New("force rollback")

	return nil
})
if err != nil {
	log.Println("Transaction rolled back:", err)
}

Ручной контроль:

tx := db.Begin()
if err := tx.Error; err != nil { ... }
if err := tx.Create(&User{Login:"abc", Email:"a@b.c", Password:"pw"}).Error; err != nil {
	tx.Rollback()
} else {
	tx.Commit()
}

10. Сессии (Session) и опции

sess := db.Session(&gorm.Session{
	NewDB: true,             // чистая копия без кэшей
	DryRun: true,            // генерирует SQL, но не исполняет
	PrepareStmt: true,
	SkipDefaultTransaction: true,
})
sql := sess.ToSQL(func(tx *gorm.DB) *gorm.DB {
	return tx.Model(&Post{}).Where("is_published").Limit(10).Find(&[]Post{})
})
log.Println("Generated SQL:", sql)

11. Логгер

(Уже показан при инициализации).
Уровни: logger.Silent, Error, Warn, Info.
Смена на лету:

db = db.Session(&gorm.Session{
	Logger: db.Logger.LogMode(logger.Warn),
})

12. Индексы и ограничения

Примеры в тегах:

  • uniqueIndex
  • index
  • Составной: gorm:"index:idx_posts_author_slug,unique,priority:1"
  • Для второго поля: gorm:"index:idx_posts_author_slug,priority:2"
type Example struct {
	A int `gorm:"index:idx_a_b,priority:1"`
	B int `gorm:"index:idx_a_b,priority:2"`
}

Частичные / выражения — через Raw SQL после миграции:

db.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_users_login_active ON users (login) WHERE is_active = true;`)

Foreign key поведение — через constraint:

Author   User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`

13. Soft Delete

Использование gorm.DeletedAt + Unscoped():

db.Delete(&post)               // мягко
db.Unscoped().Find(&posts)     // включает удалённые
db.Unscoped().Delete(&post)    // физическое удаление

14. Scopes (повторно используемые условия)

func Published(db *gorm.DB) *gorm.DB {
	return db.Where("is_published = ?", true)
}

func WithTag(slug string) func(*gorm.DB) *gorm.DB {
	return func(tx *gorm.DB) *gorm.DB {
		return tx.Joins("JOIN post_tags pt ON pt.post_id = posts.id").
			Joins("JOIN tags t ON t.id = pt.tag_id").
			Where("t.slug = ?", slug)
	}
}

var posts []Post
db.Scopes(Published, WithTag("golang")).Find(&posts)

15. Hooks (хуки)

func (p *Post) BeforeCreate(tx *gorm.DB) (err error) {
	if p.Slug == "" {
		p.Slug = generateSlug(p.Title)
	}
	return
}

func (u *User) BeforeSave(tx *gorm.DB) (err error) {
	if len(u.Password) < 60 { // условно: если не хэш
		u.Password = hash(u.Password)
	}
	return
}

16. Upsert / On Conflict (Postgres)

db.Clauses(
	clause.OnConflict{
		Columns:   []clause.Column{{Name:"slug"}},
		DoUpdates: clause.AssignmentColumns([]string{"title","updated_at"}),
	},
).Create(&Post{Slug:"first-post", Title:"Обновлённый"})

17. Raw SQL

type Stat struct {
	AuthorID uint
	Count    int
}

var stats []Stat
db.Raw(`
	SELECT author_id, COUNT(*) AS count
	FROM posts
	WHERE is_published = ?
	GROUP BY author_id
`, true).Scan(&stats)

// Exec для безрезультатных
db.Exec("UPDATE posts SET is_published = TRUE WHERE id = ?", 10)

18. Пагинация

func paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
	return func(tx *gorm.DB) *gorm.DB {
		if page < 1 { page = 1 }
		switch {
		case pageSize > 100: pageSize = 100
		case pageSize <= 0: pageSize = 10
		}
		offset := (page - 1) * pageSize
		return tx.Offset(offset).Limit(pageSize)
	}
}

var pagePosts []Post
db.Scopes(paginate(2, 20), Published).Preload("Author").Find(&pagePosts)

19. Контекст (Context)

ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
db.WithContext(ctx).Find(&posts)

20. Ручное управление ассоциациями при создании

p := Post{
	Slug:"inline",
	Title:"Inline create",
	AuthorID:user.ID,
	Tags: []Tag{
		{Title:"NewTag", Slug:"new-tag", AuthorID:user.ID},
	},
}
// Создаст Post и Tag и связь
db.Create(&p)

21. Select / Omit

db.Select("id", "title").Where("id = ?", 1).Find(&posts)
db.Omit("content", "description").Find(&posts)

22. GORM Naming Strategy (кастом)

&gorm.Config{
	NamingStrategy: schema.NamingStrategy{
		TablePrefix:   "blog_",   // blog_posts
		SingularTable: false,
	},
}

23. Работа с ошибкой RecordNotFound

res := db.First(&p, 999)
if errors.Is(res.Error, gorm.ErrRecordNotFound) {
	// обработать
}

(Если IgnoreRecordNotFoundError: true — логгер не будет шуметь.)


24. Асинхронные операции / подготовленные стейтменты

PrepareStmt в Session — кэширует prepared statements (в долгоживущем сервисе ⇧ производительность).
Не забывать закрыть sqlDB при выключении приложения.


25. Измерение производительности (DryRun + Explain)

sql := db.Session(&gorm.Session{DryRun:true}).Model(&Post{}).
	Where("is_published").Limit(5).Find(&[]Post{}).Statement.SQL.String()
log.Println("Generated SQL:", sql)

// EXPLAIN
var plan []map[string]interface{}
db.Raw("EXPLAIN ANALYZE " + sql).Scan(&plan)

(Иногда нужно вручную подставить параметры.)


26. Генерация временных значений

GORM сам ставит CreatedAt / UpdatedAt если поля названы именно так (или через hooks).
Для кастомных имен используйте callback или тег autoCreateTime / autoUpdateTime:

CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`

27. Batch Insert / CreateInBatches

var bulkPosts []Post
for i := 0; i < 1000; i++ {
	bulkPosts = append(bulkPosts, Post{
		Slug: fmt.Sprintf("slug-%d", i),
		Title: fmt.Sprintf("Title %d", i),
		AuthorID: user.ID,
	})
}
db.CreateInBatches(bulkPosts, 200) // пачки по 200

28. Ассоциации: Preload vs Joins

  • Preload делает отдельные запросы (N+1 → 1+1), безопаснее, проще.
  • Joins в один запрос — хорошо для фильтрации по связанным таблицам, но нужно следить за дубликатами/Distinct.
db.Model(&Post{}).
	Joins("Author").
	Where("users.is_active").
	Where("posts.is_published").
	Select("posts.*").
	Find(&posts)

29. Distinct

db.Model(&Post{}).
	Distinct("posts.id","posts.title").
	Joins("JOIN post_tags pt ON pt.post_id = posts.id").
	Joins("JOIN tags t ON t.id = pt.tag_id").
	Where("t.slug IN ?", []string{"golang","orm"}).
	Find(&posts)

30. Обработка Many2Many вручную

Если нужна доп. логика (например поле Order в PostTag):

// Добавить связь с параметрами
pt := PostTag{PostID: post.ID, TagID: tag.ID, Order: 10}
db.Create(&pt)

// Получить теги поста (ручной JOIN)
var tagsForPost []Tag
db.Joins("JOIN post_tags pt ON pt.tag_id = tags.id").
	Where("pt.post_id = ?", post.ID).
	Order("pt.order ASC").
	Find(&tagsForPost)

31. Конструктор репозитория (паттерн)

type PostRepo struct {
	db *gorm.DB
}

func NewPostRepo(db *gorm.DB) *PostRepo {
	return &PostRepo{db:db}
}

func (r *PostRepo) FindBySlug(slug string) (*Post, error) {
	var p Post
	err := r.db.Preload("Author").Preload("Tags").
		First(&p, "slug = ?", slug).Error
	return &p, err
}

32. Unit-тесты (in-memory / транзакции)

Для тестов можно:

func withRollback(db *gorm.DB, test func(tx *gorm.DB)) {
	tx := db.Begin()
	defer tx.Rollback()
	test(tx)
}

33. Безопасность: Не передавайте необработанные пользовательские строки в Raw / Where без плейсхолдеров. Используйте ? или @named:

db.Where("slug = @slug AND is_published = @pub",
	sql.Named("slug","first-post"),
	sql.Named("pub", true),
).Find(&posts)

34. JSON поля (Postgres)

type PostMeta struct {
	ID     uint `gorm:"primaryKey"`
	PostID uint `gorm:"index"`
	Data   datatypes.JSONMap `gorm:"type:jsonb"`
}

(Нужно: go get gorm.io/datatypes)


35. Важные флаги/ошибки

  • ErrRecordNotFound
  • ErrInvalidTransaction
  • RowsAffected: res := db.Delete(&post); res.RowsAffected

36. Кастомизация таблиц

func (PostTag) TableName() string { return "post_tags" }

37. Ленивая загрузка (Manual)

var p2 Post
db.First(&p2, 5)
db.Model(&p2).Association("Tags").Find(&p2.Tags)

38. Soft Delete в условиях

db.Unscoped().Where("deleted_at IS NOT NULL").Find(&posts) // только удалённые

39. Batch Update

db.Model(&Post{}).
	Where("is_published = ?", false).
	Updates(map[string]interface{}{
		"is_published": true,
		"updated_at":   time.Now(),
	})

40. ORDER / GROUP / HAVING

type CountByAuthor struct {
	AuthorID uint
	Cnt      int
}
var cb []CountByAuthor

db.Model(&Post{}).
	Select("author_id, COUNT(*) as cnt").
	Where("is_published").
	Group("author_id").
	Having("COUNT(*) > ?", 5).
	Order("cnt DESC").
	Scan(&cb)

41. Полезные фичи

  • Clauses (clause.Locking{Strength:"UPDATE"}) для FOR UPDATE
  • Optimistic Lock (добавить поле Version uint и BeforeUpdate + проверку)
  • Embedded structs для аудита (BaseModel)
type Audit struct {
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt gorm.DeletedAt `gorm:"index"`
}

type Post2 struct {
	Audit
	ID uint
	// ...
}

42. Конвертация в DTO

Используйте Scan в кастомные структуры (чтобы не грузить лишнее):

type PostListItem struct {
	ID    uint
	Slug  string
	Title string
	Tags  []string `gorm:"-"`
}

var raw []struct {
	ID   uint
	Slug string
	Title string
	TagTitle string
}

db.Table("posts p").
	Select("p.id, p.slug, p.title, t.title as tag_title").
	Joins("LEFT JOIN post_tags pt ON pt.post_id = p.id").
	Joins("LEFT JOIN tags t ON t.id = pt.tag_id").
	Where("p.is_published").
	Scan(&raw)

// Сгруппировать вручную
m := map[uint]*PostListItem{}
for _, r := range raw {
	item, ok := m[r.ID]
	if !ok {
		item = &PostListItem{ID:r.ID, Slug:r.Slug, Title:r.Title}
		m[r.ID] = item
	}
	if r.TagTitle != "" {
		item.Tags = append(item.Tags, r.TagTitle)
	}
}
var list []PostListItem
for _, v := range m { list = append(list, *v) }

43. Ошибка Duplicate Key → обработка

err = db.Create(&Post{Slug:"first-post"}).Error
if err != nil {
	// распарсить err.Error() или использовать errors.As для *pgconn.PgError
}

44. Настройки Many2Many имени join-таблицы

gorm:"many2many:post_tags" — иначе имя строится по алфавиту: post_tags. Можно кастомизировать через joinForeignKey, joinReferences.

Tags []Tag `gorm:"many2many:post_tags;joinForeignKey:PostID;JoinReferences:TagID"`

45. Locking (SELECT FOR UPDATE)

import "gorm.io/gorm/clause"

var lockedPost Post
db.Clauses(clause.Locking{Strength:"UPDATE"}).
	First(&lockedPost, "id = ?", 10)
// далее безопасно менять в транзакции

46. Пример комплексного запроса

Получить опубликованные посты автора с фильтрацией по тегам и пагинацией:

func FindPublishedPostsByAuthorWithTags(db *gorm.DB, authorID uint, tags []string, page, size int) ([]Post, error) {
	var posts []Post
	tx := db.Model(&Post{}).
		Where("author_id = ? AND is_published = ?", authorID, true).
		Scopes(paginate(page, size)).
		Preload("Author").
		Preload("Tags")

	if len(tags) > 0 {
		tx = tx.Joins("JOIN post_tags pt ON pt.post_id = posts.id").
			Joins("JOIN tags t ON t.id = pt.tag_id").
			Where("t.slug IN ?", tags).
			Group("posts.id")
	}

	err := tx.Order("posts.created_at DESC").Find(&posts).Error
	return posts, err
}

47. Генерация SQL без выполнения (DryRun) + Bind Vars

stmt := db.Session(&gorm.Session{DryRun:true}).Model(&Post{}).
	Where("slug = ?", "first-post").Find(&Post{}).Statement
log.Println(stmt.SQL.String(), stmt.Vars)

48. Маппинг ошибок в доменные

Создайте функцию:

func mapError(err error) error {
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return ErrNotFound
	}
	// parse duplicate
	return err
}

49. Управление временем (таймзона)

Используйте DSN TimeZone=Europe/Moscow, или вручную парсинг.
Лучше хранить UTC, отображать в локальной TZ.


50. Краткая памятка

  • Модели: поля + связи (BelongsTo через ForeignKey, Many2Many через many2many).
  • Предзагрузка: Preload (условия, вложенные).
  • Joins: для фильтрации по связям + Select.
  • Transactions: db.Transaction(fn) или Begin/Commit/Rollback.
  • Scopes: переисп. логика условий.
  • Soft Delete: gorm.DeletedAt + Unscoped.
  • Индексы: index, uniqueIndex, составные через имя.
  • Upsert: Clauses(OnConflict).
  • Associations API: Append/Replace/Clear/Delete/Count.
  • Полиморфизм: polymorphic:Commentable.
  • Logger: logger.New + уровни.
  • Session: DryRun, NewDB, PrepareStmt.
  • Raw SQL: db.Raw / db.Exec.
  • Pagination: Offset / Limit / Scope.
  • Locking: clause.Locking.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment