qqm

package module
v0.2.4 Latest Latest
Warning

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

Go to latest
Published: Jul 11, 2026 License: MIT Imports: 13 Imported by: 0

README

qqm — Quick Query Maker

English version

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

Возможности

  • Типизированные таблицыTable[ROW] параметризуется вашей структурой.
  • Multi-table запросыQuery[QROW] для SELECT с JOIN по ref-связям.
  • Автогенерация SQL — INSERT, UPDATE, SELECT, DELETE строятся по метаданным структуры.
  • Поддержка диалектов — SQLite (?) и PostgreSQL ($1, $2, …).
  • CRUD-интерфейсIns, Upd, One, Del, Many.
  • Гибкая фильтрация — дерево условий: And/Or/Not-группы, операторы Eq, Gt, Lt, Like, ILike, In, IsNull.
  • LEFT JOIN с обнулением — поля присоединённых таблиц обнуляются при отсутствии строки.
  • Теги tbl — PK, FK, read-only, auto-генерация, префиксы, сортировка.
  • Вложенные структуры — embedded и именованные поля-структуры с префиксами.
  • Составные ключи — произвольное количество PK-полей.
  • Кеширование SQL — запросы генерируются один раз в NewTable/NewQuery.
  • Без рефлексии в рантайме — метаданные собираются лениво и кешируются.

Установка

go get github.com/mirrorru/qqm

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

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

func (u *User) SQLName() string { return "users" }

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

  • Имя таблицы — SQLName(), если реализован, иначе snake_case от имени структуры.
  • Имя колонки — snake_case от имени поля: name, email, age.
Полный CRUD
import (
    "context"
    "database/sql"
    "github.com/mirrorru/qqm"
    "github.com/mirrorru/qqm/txproc"
)

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

    ex := txproc.NewDBAdapterVal(db)
    ctx := context.Background()
    tbl := qqm.NewTable[User](qqm.SQLiteDialect)

    // Create — возвращает вставленную строку (RETURNING)
    inserted, _, err := tbl.Ins(ctx, ex, &User{Name: "Alice", Email: "alice@test.com"})

    // Read — по PK
    alice, err := tbl.One(ctx, ex, inserted.ID)

    // Update — возвращает обновлённую строку (RETURNING)
    alice.Name = "Alice Updated"
    returned, _, err := tbl.Upd(ctx, ex, alice)

    // Delete — по PK
    delResult, err := tbl.Del(ctx, ex, alice.ID)

    // Many — SELECT с фильтром и сортировкой
    filter := &qqm.Filter{
        Range: qqm.And(qqm.Cond(1, qqm.CmdGt, 20)),
    }
    results, err := tbl.Many(ctx, ex, filter)
}

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

Формат тега: tbl:"pk;ro;auto;embed;omit;ins;upd;rskip;col=name;prefix=...;ref=...;sort=<pos>[:dir]"

Опция Описание
pk Поле — первичный ключ
ro Read-only (только SELECT, исключается из INSERT/UPDATE)
auto Автогенерируемое поле (исключается из INSERT, если нет ins)
embed Принудительная распаковка вложенной структуры
omit Полное игнорирование поля
ins Принудительное включение в INSERT (даже для auto)
upd Принудительное включение в UPDATE (даже для ro/auto)
rskip Исключение из SELECT (read skip)
col=name Имя колонки в БД (по умолчанию: snake_case от имени поля)
prefix=... Префикс колонок для embedded или именованной структуры
ref=table:col Внешний ключ
sort=<pos>[:dir] Позиция в ORDER BY (1-based), направление ASC/DESC
Префикс для именованных полей-структур

Тег prefix работает для embedded и именованных полей-структур:

type Address struct {
    City   string
    Street string
    Zip    string
}

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

Флаги наследуются от родительских полей-структур: ro, auto, ins, upd, rskip, prefix, sort.

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

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

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

func (u *User) SQLName() string { return "users" }

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

func (o *Order) SQLName() string { return "orders" }

// Query-структура
type UserWithOrder struct {
    User  User  `tbl:"from"`       // FROM users (первичная таблица)
    Order Order `tbl:"join=left"`  // LEFT JOIN orders ON orders.user_id = users.id
}
Использование Query
query := qqm.NewQuery[UserWithOrder](qqm.SQLiteDialect)

// Many — SELECT с JOIN и фильтром
results, err := query.Many(ctx, ex, &qqm.Filter{
    Range: qqm.And(
        qqm.Cond(1, qqm.CmdEq, "Alice"),      // users.name = ?
        qqm.Cond(5, qqm.CmdGt, 200.0),         // orders.amount > ?
    ),
})

// One — SELECT с JOIN по PK первичной таблицы
row, err := query.One(ctx, ex, int64(1))
Теги Query-полей

Формат: tbl:"from;join=left;alias=...;map=k1:v1;pk;omit;sort=<pos>"

Опция Описание
from Первичная таблица (FROM). Должна быть ровно одна.
join=left|right|inner Тип JOIN. По умолчанию: inner.
alias=... Алиас таблицы в SQL
map=k1:v1,k2:v2 Маппинг имён ref-таблиц для JOIN ON
pk Использовать PK этой таблицы в WHERE для Query.One
omit Полностью исключить таблицу из Query
sort=<pos> Приоритет сортировки таблицы в ORDER BY
LEFT JOIN и обнуление

Если в LEFT JOIN нет совпадений, все поля присоединённой структуры обнуляются (zero value):

// Для пользователя без заказов
row, _ := query.One(ctx, ex, userWithoutOrdersID)
// row.Order.ID == 0, row.Order.Amount == 0.0

Фильтрация

Фильтры строятся как дерево узлов: And/Or/Not-группы с ConditionNode-листьями.

type Filter struct {
    Offset uint32      // OFFSET
    Limit  uint32      // LIMIT
    Range  FilterNode  // дерево условий
}
Конструкторы
// Условие: Cond(fieldIdx, CommandOp, value)
nameEq := qqm.Cond(1, qqm.CmdEq, "Alice")

// Группы
qqm.And(nameEq, qqm.Cond(2, qqm.CmdGt, 18))   // AND
qqm.Or(qqm.Cond(3, qqm.CmdEq, "admin"), ...)   // OR
qqm.Not(qqm.Cond(1, qqm.CmdIsNull))             // NOT
Операторы
Константа SQL
CmdEq = ?
CmdNotEq <> ?
CmdGt > ?
CmdGte >= ?
CmdLt < ?
CmdLte <= ?
CmdLike LIKE ?
CmdILike ILIKE ? (PG) / LOWER() LIKE LOWER() (SQLite)
CmdIn IN (?, ?, ...)
CmdIsNull IS NULL
CmdIsNotNull IS NOT NULL
Индексы полей

Индексы для Cond() — позиция поля в плоском списке TableDefinition.Fields или Query.FlatFields(). Порядок соответствует порядку полей в структуре (с учётом распаковки embedded и пропуска omit/rskip).

Примеры
// Простой фильтр: name = "Alice" AND age > 18
filter := &qqm.Filter{
    Range: qqm.And(
        qqm.Cond(1, qqm.CmdEq, "Alice"),
        qqm.Cond(2, qqm.CmdGt, 18),
    ),
}

// OR с BETWEEN: role = "admin" OR role = "moderator"
filter := &qqm.Filter{
    Range: qqm.Or(
        qqm.Cond(3, qqm.CmdEq, "admin"),
        qqm.Cond(3, qqm.CmdEq, "moderator"),
    ),
}

// NOT: email IS NOT NULL
filter := &qqm.Filter{
    Range: qqm.Not(qqm.Cond(2, qqm.CmdIsNull)),
}

// IN: name IN ("Alice", "Bob", "Charlie")
filter := &qqm.Filter{
    Range: qqm.And(qqm.Cond(1, qqm.CmdIn, []any{"Alice", "Bob", "Charlie"})),
}

// Пагинация: OFFSET 10 LIMIT 20
filter := &qqm.Filter{
    Offset: 10,
    Limit:  20,
}

Диалекты

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

Адаптеры БД

Адаптеры в пакете txproc:

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

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

Все CRUD-методы (Ins, Upd, One, Del, Many) работают с любым txproc.TxProcessor.

Примеры

Составной ключ
type OrgUser struct {
    OrgID  int64 `tbl:"pk"`
    UserID int64 `tbl:"pk"`
    Role   string
}

func (o *OrgUser) SQLName() string { return "org_users" }

// Использование
tbl := qqm.NewTable[OrgUser](qqm.SQLiteDialect)
row, err := tbl.One(ctx, ex, int64(1), int64(42))
Кастомное имя таблицы
func (u *OrgUser) SQLName() string { return "org_members" }
Embedded структуры с префиксом
type Audit struct {
    CreatedAt string `tbl:"col=created_at;auto"`
    UpdatedAt string `tbl:"col=updated_at;auto;upd"`
}

type Post struct {
    ID    int64 `tbl:"pk"`
    Title string
    Audit `tbl:"prefix=audit_"`
}
// Колонки: id, title, audit_created_at, audit_updated_at
Сортировка
type UserWithSort struct {
    ID    int64  `tbl:"pk;auto"`
    Name  string `tbl:"sort=1"`       // ORDER BY name ASC
    Email string `tbl:"sort=2:desc"`  // затем email DESC
    Age   int
}
Auto-поля
type Timestamps struct {
    CreatedAt string `tbl:"col=created_at;auto"`      // не в INSERT
    UpdatedAt string `tbl:"col=updated_at;auto;upd"`  // только в UPDATE
}

Интерфейс TxProcessor

type TxProcessor 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 — для запросов, возвращающих одну строку (Ins/Upd с RETURNING, One).
  • QueryContext — для Many (несколько строк).
  • ExecContext — для Del и Ins/Upd без RETURNING.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// SQLiteDialect — предопределённый диалект SQLite.
	// EN: SQLiteDialect — predefined SQLite dialect.
	SQLiteDialect = dialect.SQLiteDialect{}
	// PostgreSQLDialect — предопределённый диалект PostgreSQL.
	// EN: PostgreSQLDialect — predefined PostgreSQL dialect.
	PostgreSQLDialect = dialect.PostgreSQLDialect{}
)
View Source
var JoinModeNames = map[string]JoinMode{
	"left":  JoinModeLeft,
	"right": JoinModeRight,
	"inner": JoinModeInner,
}

Functions

This section is empty.

Types

type CommandOp added in v0.2.0

type CommandOp int32
const (
	CmdEq CommandOp = iota
	CmdNotEq
	CmdGt
	CmdGte
	CmdLt
	CmdLte
	CmdIsNull
	CmdIsNotNull
	CmdLike
	CmdILike
	CmdIn
)

type ConditionNode added in v0.2.0

type ConditionNode struct {
	FieldIdx int
	Op       CommandOp
	Value    any
}

ConditionNode — лист дерева фильтра: одно условие над полем. EN: ConditionNode — filter tree leaf: a single condition on a field.

func Cond added in v0.2.0

func Cond(fieldIdx int, op CommandOp, value any) *ConditionNode

Cond создаёт узел условия: fieldIdx — индекс поля в TableFields, op — оператор, value — значение. EN: Cond creates a condition node: fieldIdx — field index in TableFields, op — operator, value — value.

func (ConditionNode) Build added in v0.2.0

func (cn ConditionNode) Build(tf TableFields, d dialect.DialectProvider, argIdx *int) (string, []any, error)

type FieldFlags added in v0.2.0

type FieldFlags struct {
	IsPK         bool // keyPK
	ReadOnly     bool // keyRO
	AutoGen      bool // keyAuto
	Embed        bool
	ForceUpdate  bool
	ForceInsert  bool
	SkipReading  bool
	ColName      string // keyColName
	Prefix       string // keyPrefix
	Ref          string
	SortPos      int
	SortBackward bool
}

func (*FieldFlags) Merge added in v0.2.0

func (f *FieldFlags) Merge(parent FieldFlags)

type Filter

type Filter struct {
	Offset uint32
	Limit  uint32
	Range  FilterNode
}

Filter — структура фильтра для Many(). Offset и Limit задают пагинацию, Range — дерево условий. EN: Filter — filter structure for Many(). Offset and Limit set pagination, Range — condition tree.

func (*Filter) BuildOffsetAndLimit added in v0.2.0

func (f *Filter) BuildOffsetAndLimit(d dialect.DialectProvider) string

func (*Filter) BuildWhere added in v0.2.0

func (f *Filter) BuildWhere(tf TableFields, d dialect.DialectProvider) (query string, args []any, err error)

type FilterNode added in v0.2.0

type FilterNode interface {
	Build(tf TableFields, d dialect.DialectProvider, argIdx *int) (clause string, args []any, err error)
}

FilterNode — интерфейс узла дерева фильтра. Реализации: ConditionNode (условие), GroupNode (And/Or/Not-группа). EN: FilterNode — filter tree node interface. Implementations: ConditionNode (condition), GroupNode (And/Or/Not group).

type GroupNode added in v0.2.0

type GroupNode struct {
	Logic    LogicOp
	Children []FilterNode
}

GroupNode — группа условий с логическим оператором. EN: GroupNode — group of conditions with a logical operator.

func And

func And(children ...FilterNode) *GroupNode

And создаёт группу с логическим AND. EN: And creates a logical AND group.

func Not added in v0.2.0

func Not(child FilterNode) *GroupNode

Not создаёт группу с логическим NOT (ровно один ребёнок). EN: Not creates a logical NOT group (exactly one child).

func Or

func Or(children ...FilterNode) *GroupNode

Or создаёт группу с логическим OR. EN: Or creates a logical OR group.

func (GroupNode) Build added in v0.2.0

func (gn GroupNode) Build(tf TableFields, d dialect.DialectProvider, argIdx *int) (string, []any, error)

type JoinMode added in v0.2.0

type JoinMode int
const (
	JoinModeNone JoinMode = iota
	JoinModeLeft
	JoinModeRight
	JoinModeInner
)

type LogicOp added in v0.2.0

type LogicOp int32
const (
	LogicAnd LogicOp = iota
	LogicOr
	LogicNot
)

type Query added in v0.1.4

type Query[QROW any] struct {
	// contains filtered or unexported fields
}

Query — типизированный multi-table SELECT с JOIN. QROW — структура, поля которой — ROW-типы таблиц. JOIN-условия выводятся автоматически из тегов ref= на полях ROW-структур. EN: Query — typed multi-table SELECT with JOIN. QROW — struct whose fields are ROW table types. JOIN conditions are auto-inferred from ref= tags on ROW struct fields.

func NewQuery added in v0.1.2

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

NewQuery создаёт типизированный multi-table запрос для типа QROW. Собирает метаданные всех таблиц, строит JOIN-условия и генерирует SQL. EN: NewQuery creates a typed multi-table query for the QROW type. Collects metadata for all tables, builds JOIN conditions and generates SQL.

func NewQueryVal added in v0.1.15

func NewQueryVal[QROW any](d dialect.DialectProvider) Query[QROW]

func (*Query[QROW]) FlatFields added in v0.2.0

func (q *Query[QROW]) FlatFields() TableFields

FlatFields возвращает плоский список всех selectable полей всех таблиц. Используется для определения индексов полей в Cond(). EN: FlatFields returns the flat list of all selectable fields from all tables. Used to determine field indices in Cond().

func (*Query[QROW]) Many added in v0.2.0

func (q *Query[QROW]) Many(ctx context.Context, tx TxProcessor, filter *Filter) (result []*QROW, err error)

Many возвращает срез строк Query с JOIN, фильтрацией и сортировкой. filter может быть nil — тогда возвращаются все строки с ORDER BY из sort-тегов. Для LEFT JOIN без совпадений поля присоединённых таблиц обнуляются. EN: Many returns a slice of Query rows with JOIN, filtering and sorting. filter may be nil — then all rows are returned with ORDER BY from sort tags. For LEFT JOIN with no match, joined table fields are zeroed.

func (*Query[QROW]) One added in v0.1.14

func (q *Query[QROW]) One(ctx context.Context, tx TxProcessor, keys ...any) (*QROW, error)

One возвращает одну строку Query по PK первичной таблицы (и таблиц с тегом pk). Для LEFT JOIN без совпадений поля присоединённых таблиц обнуляются. EN: One returns a single Query row by PK of the primary table (and tables with pk tag). For LEFT JOIN with no match, joined table fields are zeroed.

func (*Query[QROW]) SQLs added in v0.2.0

func (q *Query[QROW]) SQLs() sqlTexts

SQLs возвращает сгенерированные SQL-запросы (GetOneCmd, ListCmdStart, ListSortString). EN: SQLs returns generated SQL queries (GetOneCmd, ListCmdStart, ListSortString).

type Result added in v0.1.6

type Result interface {
	LastInsertId() (int64, error)
	RowsAffected() (int64, error)
}

Result представляет результат выполнения ExecContext. EN: Result represents the result of ExecContext execution.

type Row added in v0.1.6

type Row interface {
	Scan(dest ...any) error
}

Row представляет одну строку результата запроса. EN: Row represents a single query result row.

type Rows added in v0.1.6

type Rows interface {
	Next() bool
	Scan(dest ...any) error
	Close() error
	Err() error
}

Rows представляет курсор результатов запроса. EN: Rows represents a query result cursor.

type SQLNamer added in v0.1.4

type SQLNamer interface {
	SQLName() string
}

type Table added in v0.1.4

type Table[ROW any] struct {
	// contains filtered or unexported fields
}

Table — типизированная таблица, параметризованная типом строки ROW. Методы: Ins (вставка), Upd (обновление), One (SELECT по PK), Del (удаление), Many (SELECT с фильтром). EN: Table — a typed table parameterized by row type ROW. Methods: Ins (insert), Upd (update), One (SELECT by PK), Del (delete), Many (SELECT with filter).

func NewTable

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

NewTable создаёт типизированную таблицу для типа ROW. ROW должен быть value-типом (структура), не указателем. Собирает метаданные полей и генерирует SQL-запросы (кешируются). EN: NewTable creates a typed table for the ROW type. ROW must be a value type (struct), not a pointer. Collects field metadata and generates SQL queries (cached).

func NewTableVal added in v0.1.9

func NewTableVal[ROW any](dialect dialect.DialectProvider) Table[ROW]

func (*Table[ROW]) Defs added in v0.2.0

func (t *Table[ROW]) Defs() TableDefinition

Defs возвращает метаданные таблицы (поля, колонки, индексы). EN: Defs returns table metadata (fields, columns, indexes).

func (*Table[ROW]) Del added in v0.2.0

func (t *Table[ROW]) Del(ctx context.Context, tx TxProcessor, keys ...any) (Result, error)

Del удаляет строку по первичному ключу. EN: Del deletes a row by primary key.

func (*Table[ROW]) Ins added in v0.2.0

func (t *Table[ROW]) Ins(ctx context.Context, tx TxProcessor, row *ROW) (*ROW, Result, error)

Ins вставляет строку. Если диалект поддерживает RETURNING — возвращает вставленную строку. EN: Ins inserts a row. If the dialect supports RETURNING — returns the inserted row.

func (*Table[ROW]) Many added in v0.2.0

func (t *Table[ROW]) Many(ctx context.Context, tx TxProcessor, filter *Filter) (result []*ROW, err error)

Many возвращает срез строк с фильтрацией и сортировкой. filter может быть nil — тогда возвращаются все строки с ORDER BY из sort-тегов. EN: Many returns a slice of rows with filtering and sorting. filter may be nil — then all rows are returned with ORDER BY from sort tags.

func (*Table[ROW]) One added in v0.2.0

func (t *Table[ROW]) One(ctx context.Context, tx TxProcessor, keys ...any) (*ROW, error)

One возвращает одну строку по первичному ключу. EN: One returns a single row by primary key.

func (*Table[ROW]) SQLs added in v0.2.0

func (t *Table[ROW]) SQLs() sqlTexts

SQLs возвращает сгенерированные SQL-запросы (InsertCmd, UpdateCmd, DeleteCmd, GetOneCmd, ListCmdStart, ListSortString). EN: SQLs returns generated SQL queries (InsertCmd, UpdateCmd, DeleteCmd, GetOneCmd, ListCmdStart, ListSortString).

func (*Table[ROW]) Upd added in v0.2.0

func (t *Table[ROW]) Upd(ctx context.Context, tx TxProcessor, row *ROW) (*ROW, Result, error)

Upd обновляет строку по PK. Если диалект поддерживает RETURNING — возвращает обновлённую строку. EN: Upd updates a row by PK. If the dialect supports RETURNING — returns the updated row.

type TableDefinition added in v0.2.0

type TableDefinition struct {
	TableName  string
	Fields     TableFields
	FieldNames map[string]int
	Indexes    fieldsIndexes
}

type TableField added in v0.2.0

type TableField struct {
	Index   []int
	Path    []string
	SQLName string
	Flags   FieldFlags
}

type TableFields added in v0.2.0

type TableFields []TableField

func CollectTableFields added in v0.2.0

func CollectTableFields(t reflect.Type) (TableFields, error)

CollectTableFields -

type TableFlags added in v0.2.0

type TableFlags struct {
	IsFrom    bool
	JoinMode  JoinMode
	Alias     string
	RefMap    map[string]string
	UsePk     bool
	SortOrder int
}

type TxProcessor added in v0.2.1

type TxProcessor 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
}

TxProcessor описывает интерфейс выполнения SQL-запросов. Абстрагирует database/sql.DB, database/sql.Tx, pgx.Conn, pgx.Tx. EN: TxProcessor describes the SQL execution interface. Abstracts database/sql.DB, database/sql.Tx, pgx.Conn, pgx.Tx.

Directories

Path Synopsis
test

Jump to

Keyboard shortcuts

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