qqm

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jun 28, 2026 License: MIT Imports: 2 Imported by: 0

README

qqm — Quick Query Maker

qqm — это ORM-подобная Go-библиотека для типизированной работы с SQL-базами данных через структуры. Она автоматически генерирует SQL-запросы на основе тегов в полях структуры и предоставляет простой CRUD-интерфейс.

Возможности

  • Типизированные таблицыTable[ROW] параметризуется вашей структурой.
  • Автогенерация SQL — INSERT, UPDATE, SELECT, DELETE строятся по метаданным структуры.
  • Поддержка диалектов — SQLite (?) и PostgreSQL ($1, $2, …).
  • CRUD-интерфейс — Insert, Update, GetByKey, Delete, List.
  • Гибкая фильтрация — And/Or-комбинации, операторы Eq, Gt, Lt, Gte, Lte, Between, In.
  • Теги полей — колонка, первичный ключ, внешний ключ, readonly, auto, omit.
  • Embedded структуры — поддержка встраивания с префиксом колонок.
  • Именованные поля-структуры — префикс для неанонимных структур (например, несколько адресов).
  • Составные ключи —任意ное количество полей в PK.
  • Кеширование SQL — запросы генерируются один раз при первом обращении.
  • Без рефлексии в рантайме — метаданные собираются лениво и кешируются.

Установка

go get github.com/mirrorru/qqm

Быстрый старт

Определение модели
type User struct {
    ID    int64  `qqm:"pk"`
    Name  string
    Email string
    Age   int
}

Правила именования по умолчанию:

  • Имя таблицы — snake_case от имени структуры: user.
  • Имя колонки — snake_case от имени поля: name, email, age.
Создание таблицы и SQL
import "github.com/mirrorru/qqm"

userTable := qqm.NewTable[*User](qqm.SQLiteDialect)

fmt.Println(userTable.Internals().InsertSQL())
// INSERT INTO user (id, name, email, age) VALUES (?, ?, ?, ?) RETURNING id, name, email, age

fmt.Println(userTable.Internals().SelectSQL())
// SELECT id, name, email, age FROM user WHERE id = ?
Полный CRUD
import (
    "context"
    "database/sql"
    "github.com/mirrorru/qqm"
    "github.com/mirrorru/qqm/executor"
)

func Example() {
    db, _ := sql.Open("sqlite", ":memory:")
    db.Exec(`CREATE TABLE users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT, email TEXT, age INTEGER
    )`)

    ex := executor.NewDBAdapter(db)
    ctx := context.Background()
    tbl := qqm.NewTable[*User](qqm.SQLiteDialect)

    // Create
    u, _ := tbl.Insert(ctx, ex, &User{Name: "Alice", Email: "alice@test.com", Age: 25})

    // Read
    alice, _ := tbl.GetByKey(ctx, ex, u.ID)

    // Update
    alice.Age = 26
    tbl.Update(ctx, ex, alice)

    // List with filter
    result, _ := tbl.List(ctx, ex, qqm.Field("Age", qqm.And, qqm.Gt(20)))

    // Delete
    tbl.Delete(ctx, ex, u.ID)
}

Настройка колонок через теги

Формат тега: qqm:"col=name;pk;ref=table.col;readonly;auto;omit;prefix=..."

Опция Описание
col=name Имя колонки в БД (по умолчанию: snake_case от имени поля)
pk Поле является первичным ключом
ref=table.col Внешний ключ
prefix=... Префикс для колонок из embedded или именованной структуры
readonly Не участвует в UPDATE
auto Не участвует в INSERT (например, SERIAL)
omit Полностью исключается из SQL
Префикс для именованных полей-структур

Тег prefix работает не только для embedded (анонимных) структур, но и для именованных полей-структур. Это позволяет переиспользовать одну и ту же Go-структуру для разных табличных колонок:

type Address struct {
    City   string
    Street string
    Zip    string
}

type Person struct {
    ID          int64   `qqm:"pk"`
    Name        string
    HomeAddress Address `qqm:"prefix=home_"`
    WorkAddress Address `qqm:"prefix=work_"`
}
// Колонки: id, name, home_city, home_street, home_zip, work_city, work_street, work_zip

Примеры

Составной ключ
type OrgUser struct {
    OrgID  int64  `qqm:"pk"`
    UserID int64  `qqm:"pk"`
    Role   string
}
Кастомное имя таблицы

Реализуйте интерфейс table.SQLNamer:

func (u *OrgUser) SQLName() string { return "org_members" }
Embedded структуры с префиксом
type Audit struct {
    CreatedAt int64 `qqm:"col=created_at"`
    UpdatedAt int64 `qqm:"col=updated_at"`
}

type Post struct {
    ID    int64 `qqm:"pk"`
    Title string
    Audit `qqm:"prefix=audit_"`
}
// Колонки: id, title, audit_created_at, audit_updated_at
// AND-условия
andFilter := qqm.AndFilter(
    qqm.Field("Age", qqm.And, qqm.Gte(18), qqm.Lte(60)),
    qqm.Field("Status", qqm.And, qqm.Eq("active")),
)

// OR-условия
orFilter := qqm.OrFilter(
    qqm.Field("Role", qqm.And, qqm.Eq("admin")),
    qqm.Field("Role", qqm.And, qqm.Eq("moderator")),
)

// Between
ageFilter := qqm.AndFilter(
    qqm.Field("Age", qqm.And, qqm.Between(18, 65)),
)

// In
nameFilter := qqm.AndFilter(
    qqm.Field("Name", qqm.And, qqm.In("Alice", "Bob", "Charlie")),
)

Диалекты

Диалект Плейсхолдер RETURNING
qqm.SQLiteDialect ? Да
qqm.PostgreSQLDialect $1, $2, … Да

Адаптеры БД

Для передачи в CRUD-методы используйте адаптеры из executor:

Адаптер Конструктор Для чего
executor.DBAdapter executor.NewDBAdapter(db) *sql.DB
executor.TxAdapter executor.NewTxAdapter(tx) *sql.Tx
executor.PGXAdapter executor.NewPGXAdapter(conn) *pgx.Conn
executor.PGXTxAdapter executor.NewPGXTxAdapter(tx) pgx.Tx
Транзакции
tx, _ := db.BeginTx(ctx, nil)
ex := executor.NewTxAdapter(tx)

inserted, err := tbl.Insert(ctx, ex, &User{Name: "Alice"})
if err != nil {
    _ = tx.Rollback()
    return err
}
_ = tx.Commit()

Все CRUD-методы (Insert, Update, GetByKey, Delete, List) работают как с DBAdapter, так и с TxAdapter.

Интерфейс Executor

Пакет executor определяет интерфейс для абстракции SQL-выполнения:

type Executor interface {
    ExecContext(ctx context.Context, query string, args ...any) (Result, error)
    QueryContext(ctx context.Context, query string, args ...any) (Rows, error)
    QueryRowContext(ctx context.Context, query string, args ...any) Row
}
  • QueryRowContext — для запросов, возвращающих одну строку (Insert RETURNING, GetByKey).

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	SQLiteDialect     = dialect.SQLiteDialect{}
	PostgreSQLDialect = dialect.PostgreSQLDialect{}
)

Functions

func NewTable

func NewTable[ROW any](d dialect.DialectProvider) *table.Table[ROW]

Types

type Condition

type Condition = table.Condition

Condition

func Between

func Between(min, max any) Condition

func Eq

func Eq(value any) Condition

func Gt

func Gt(value any) Condition

func Gte

func Gte(value any) Condition

func In

func In(values ...any) Condition

func Lt

func Lt(value any) Condition

func Lte

func Lte(value any) Condition

type ConditionOp

type ConditionOp = table.ConditionOp

ConditionOp

type FieldFilter

type FieldFilter = table.FieldFilter

FieldFilter

func Field

func Field(fieldName string, op FilterOp, conditions ...Condition) FieldFilter

type Filter

type Filter = table.Filter

Filter

func AndFilter

func AndFilter(fields ...FieldFilter) Filter

func OrFilter

func OrFilter(fields ...FieldFilter) Filter

type FilterOp

type FilterOp = table.FilterOp

FilterOp

const (
	And FilterOp = iota
	Or
)

Directories

Path Synopsis
Created at 2026-06-28
Created at 2026-06-28
Created at 2026-06-28
Created at 2026-06-28
Created at 2026-06-28
Created at 2026-06-28
test
fixtures
Created at 2026-06-28
Created at 2026-06-28

Jump to

Keyboard shortcuts

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