qqm

package module
v0.1.2 Latest Latest
Warning

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

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

README

qqm — Quick Query Maker

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

Возможности

  • Типизированные таблицыTable[ROW] параметризуется вашей структурой.
  • Multi-table запросыQuery[QROW] для SELECT с JOIN по ref-связям.
  • Автогенерация SQL — INSERT, UPDATE, SELECT, DELETE строятся по метаданным структуры.
  • Поддержка диалектов — SQLite (?) и PostgreSQL ($1, $2, …).
  • CRUD-интерфейс — Insert, Update, GetByKey, Delete, List.
  • Гибкая фильтрация — And/Or-комбинации, операторы Eq, Gt, Lt, Gte, Lte, Between, In.
  • Квалифицированные имена — фильтры по полям присоединённых таблиц ("Order.Amount").
  • LEFT JOIN с nil*ROW-поля автоматически становятся nil при отсутствии строки.
  • Теги полей — колонка, первичный ключ, внешний ключ, readonly, auto, omit, join, on, table, primary.
  • 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=...;join=TYPE;on=...;table=...;primary"

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

Тег 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

Multi-table запросы (JOIN)

Query[QROW] — типизированный SELECT с JOIN. Поля QROW — существующие ROW-структуры. JOIN-условия выводятся автоматически из тегов ref=.

Определение Query-структуры
// ROW-структуры
type User struct {
    ID    int64  `qqm:"pk"`
    Name  string
    Email string
}

type Order struct {
    ID     int64   `qqm:"pk;auto"`
    UserID int64   `qqm:"ref=users.id"`
    Amount float64
}

// Query-структура
type UserWithOrder struct {
    User  User    // INNER JOIN
    Order *Order  // LEFT JOIN (указатель → nil при отсутствии строки)
}
Использование
q, err := qqm.NewQuery[UserWithOrder](qqm.SQLiteDialect)
// err != nil если не найден FK для JOIN

results, err := q.List(ctx, ex,
    qqm.AndFilter(
        qqm.Field("User.Name", qqm.And, qqm.Eq("Alice")),
        qqm.Field("Order.Amount", qqm.And, qqm.Gt(100.0)),
    ),
)

for _, r := range results {
    fmt.Println(r.User.Name, r.Order) // Order == nil для LEFT JOIN без строки
}
Правила вывода JOIN
Тип поля JOIN по умолчанию
Order (value) INNER
*Order (pointer) LEFT

JOIN-условие ON строится по тегу ref=users.id на поле UserID структуры Order: orders.user_id = users.id.

Явное управление JOIN
type CustomQuery struct {
    User  User   `qqm:"table=app_users;primary"`  // переопределение имени + primary
    Order *Order `qqm:"join=LEFT"`                 // явный тип JOIN
}

type WithExplicitON struct {
    User User
    Ref  RefData `qqm:"on=ref.user_id=users.id"`   // явное условие
}
LEFT JOIN и nil

Если строки в присоединённой таблице нет, поле-указатель устанавливается в nil:

type UserWithOrderPtr struct {
    User  User
    Order *Order
}

results, _ := q.List(ctx, ex)
for _, r := range results {
    if r.Order == nil {
        fmt.Println(r.User.Name, "has no orders")
    }
}
Фильтры с квалифицированными именами

Имена полей в фильтрах: "TableName.FieldName":

qqm.AndFilter(
    qqm.Field("User.Name", qqm.And, qqm.Eq("Alice")),
    qqm.Field("Order.Amount", qqm.And, qqm.Gt(200.0)),
)

Примеры

Составной ключ
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 NewQuery added in v0.1.2

func NewQuery[QROW any](d dialect.DialectProvider) (*table.Query[QROW], error)

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