qqm

package module
v0.1.7 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2026 License: MIT Imports: 10 Imported by: 0

README

qqm — Quick Query Maker

English version

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, GetByPK, 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"
)

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

    ex := qqm.NewDBAdapterVal(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.GetByPK(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;sort=<pos>[,dir];create=..."

Опция Описание
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
sort=<pos>[,dir] Позиция в ORDER BY для List() (1-based), направление ASC/DESC
create=... Строка для колонки в CREATE TABLE (DEFAULT, UNIQUE и т.д.)
Префикс для именованных полей-структур

Тег 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
}
Кастомное имя таблицы

Реализуйте интерфейс qqm.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-методы используйте адаптеры из корневого пакета qqm:

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

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

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

Интерфейс Executor

Пакет qqm определяет интерфейс для абстракции 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, GetByPK).

Documentation

Overview

Example (CompositeKey)

Example_compositeKey demonstrates usage with a composite key

package main

import (
	"fmt"

	"github.com/mirrorru/qqm"
	"github.com/mirrorru/qqm/dialect"
)

// OrgUser — структура с составным ключом
type OrgUser struct {
	OrgID  int64 `qqm:"pk"`
	UserID int64 `qqm:"pk"`
	Name   string
	Email  string
}

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

// Example_compositeKey demonstrates usage with a composite key
func main() {
	orgUserTable := qqm.NewTable[OrgUser](dialect.SQLiteDialect{})

	fmt.Println("INSERT:", orgUserTable.Internals().InsertSQL())
	fmt.Println("UPDATE:", orgUserTable.Internals().UpdateSQL())
	fmt.Println("SELECT:", orgUserTable.Internals().SelectSQL())
	fmt.Println("DELETE:", orgUserTable.Internals().DeleteSQL())

	meta := orgUserTable.Internals().Meta()
	fmt.Println("Table:", meta.TableName)
	fmt.Println("Columns:", meta.Columns)
	fmt.Println("PK count:", len(meta.PKFields))
	for _, pk := range meta.PKFields {
		fmt.Printf("  PK: %s (order: %d)\n", pk.Column, pk.PkOrder)
	}

}
Output:
INSERT: INSERT INTO org_users (org_id, user_id, name, email) VALUES (?, ?, ?, ?) RETURNING org_id, user_id, name, email
UPDATE: UPDATE org_users SET name = ?, email = ? WHERE org_id = ? AND user_id = ?
SELECT: SELECT org_id, user_id, name, email FROM org_users WHERE org_id = ? AND user_id = ?
DELETE: DELETE FROM org_users WHERE org_id = ? AND user_id = ?
Table: org_users
Columns: [org_id user_id name email]
PK count: 2
  PK: org_id (order: 1)
  PK: user_id (order: 2)
Example (SimpleKey)

Example_simpleKey demonstrates usage with a simple key

package main

import (
	"fmt"

	"github.com/mirrorru/qqm"
	"github.com/mirrorru/qqm/dialect"
)

// User — структура с простым ключом
type User struct {
	ID    int64 `qqm:"pk"`
	Name  string
	Email string
	Age   int
}

// Example_simpleKey demonstrates usage with a simple key
func main() {
	userTable := qqm.NewTable[User](dialect.SQLiteDialect{})

	fmt.Println("INSERT:", userTable.Internals().InsertSQL())
	fmt.Println("UPDATE:", userTable.Internals().UpdateSQL())
	fmt.Println("SELECT:", userTable.Internals().SelectSQL())
	fmt.Println("DELETE:", userTable.Internals().DeleteSQL())

	meta := userTable.Internals().Meta()
	fmt.Println("Table:", meta.TableName)
	fmt.Println("Columns:", meta.Columns)

}
Output:
INSERT: INSERT INTO user (id, name, email, age) VALUES (?, ?, ?, ?) RETURNING id, name, email, age
UPDATE: UPDATE user SET name = ?, email = ?, age = ? WHERE id = ?
SELECT: SELECT id, name, email, age FROM user WHERE id = ?
DELETE: DELETE FROM user WHERE id = ?
Table: user
Columns: [id name email age]

Index

Examples

Constants

This section is empty.

Variables

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

Functions

func SetTagName added in v0.1.7

func SetTagName(newTagVal string) string

SetTagName устанавливает имя тега для парсинга метаданных. EN: SetTagName sets the tag name for metadata parsing.

Types

type CRUD added in v0.1.4

type CRUD[ROW any] interface {
	Internals() *tableInternals

	Insert(ctx context.Context, ex Executor, src *ROW) (*ROW, error)
	Update(ctx context.Context, ex Executor, src *ROW) error
	GetByPK(ctx context.Context, ex Executor, keys ...any) (*ROW, error)
	Delete(ctx context.Context, ex Executor, keys ...any) error
	List(ctx context.Context, ex Executor, filters ...Filter) ([]*ROW, error)
}

type Condition

type Condition struct {
	Op    ConditionOp
	Value any
}

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 int
const (
	OpEq ConditionOp = iota
	OpGt
	OpLt
	OpGte
	OpLte
	OpBetween
	OpIn
)

type DBAdapter added in v0.1.6

type DBAdapter struct {
	// contains filtered or unexported fields
}

DBAdapter адаптирует *sql.DB к интерфейсу Executor. EN: DBAdapter adapts *sql.DB to the Executor interface.

func NewDBAdapterVal added in v0.1.6

func NewDBAdapterVal(db *sql.DB) DBAdapter

NewDBAdapterVal создаёт адаптер для *sql.DB к интерфейсу Executor. EN: NewDBAdapterVal creates an adapter from *sql.DB to the Executor interface.

func (DBAdapter) ExecContext added in v0.1.6

func (a DBAdapter) ExecContext(ctx context.Context, query string, args ...any) (Result, error)

ExecContext выполняет запрос, не возвращающий строк. EN: ExecContext executes a query that does not return rows.

func (DBAdapter) QueryContext added in v0.1.6

func (a DBAdapter) QueryContext(ctx context.Context, query string, args ...any) (Rows, error)

QueryContext выполняет запрос, возвращающий строки. EN: QueryContext executes a query that returns rows.

func (DBAdapter) QueryRowContext added in v0.1.6

func (a DBAdapter) QueryRowContext(ctx context.Context, query string, args ...any) Row

QueryRowContext выполняет запрос, возвращающий одну строку. EN: QueryRowContext executes a query that returns a single row.

type Executor added in v0.1.6

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
}

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

type FieldFilter

type FieldFilter struct {
	Field      string
	Conditions []Condition
	Op         FilterOp
}

func Field

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

type Filter

type Filter struct {
	Fields []FieldFilter
	Op     FilterOp
}

func AndFilter

func AndFilter(fields ...FieldFilter) Filter

func OrFilter

func OrFilter(fields ...FieldFilter) Filter

type FilterOp

type FilterOp int
const (
	And FilterOp = iota
	Or
)

type PGXAdapter added in v0.1.6

type PGXAdapter struct {
	// contains filtered or unexported fields
}

PGXAdapter адаптирует *pgx.Conn к интерфейсу Executor. EN: PGXAdapter adapts *pgx.Conn to the Executor interface.

func NewPGXAdapterVal added in v0.1.6

func NewPGXAdapterVal(conn *pgx.Conn) PGXAdapter

NewPGXAdapterVal создаёт адаптер для *pgx.Conn к интерфейсу Executor. EN: NewPGXAdapterVal creates an adapter from *pgx.Conn to the Executor interface.

func (PGXAdapter) ExecContext added in v0.1.6

func (a PGXAdapter) ExecContext(ctx context.Context, query string, args ...any) (Result, error)

func (PGXAdapter) QueryContext added in v0.1.6

func (a PGXAdapter) QueryContext(ctx context.Context, query string, args ...any) (Rows, error)

func (PGXAdapter) QueryRowContext added in v0.1.6

func (a PGXAdapter) QueryRowContext(ctx context.Context, query string, args ...any) Row

type PGXTxAdapter added in v0.1.6

type PGXTxAdapter struct {
	// contains filtered or unexported fields
}

PGXTxAdapter адаптирует pgx.Tx к интерфейсу Executor. EN: PGXTxAdapter adapts pgx.Tx to the Executor interface.

func NewPGXTxAdapterVal added in v0.1.6

func NewPGXTxAdapterVal(tx pgx.Tx) PGXTxAdapter

NewPGXTxAdapterVal создаёт адаптер для pgx.Tx к интерфейсу Executor. EN: NewPGXTxAdapterVal creates an adapter from pgx.Tx to the Executor interface.

func (PGXTxAdapter) ExecContext added in v0.1.6

func (a PGXTxAdapter) ExecContext(ctx context.Context, query string, args ...any) (Result, error)

func (PGXTxAdapter) QueryContext added in v0.1.6

func (a PGXTxAdapter) QueryContext(ctx context.Context, query string, args ...any) (Rows, error)

func (PGXTxAdapter) QueryRowContext added in v0.1.6

func (a PGXTxAdapter) QueryRowContext(ctx context.Context, query string, args ...any) Row

type PgxResult added in v0.1.6

type PgxResult struct {
	// contains filtered or unexported fields
}

PgxResult адаптирует pgconn.CommandTag к интерфейсу Result. EN: PgxResult adapts pgconn.CommandTag to the Result interface.

func (*PgxResult) LastInsertId added in v0.1.6

func (r *PgxResult) LastInsertId() (int64, error)

func (*PgxResult) RowsAffected added in v0.1.6

func (r *PgxResult) RowsAffected() (int64, error)

type PgxRows added in v0.1.6

type PgxRows struct {
	// contains filtered or unexported fields
}

PgxRows адаптирует pgx.Rows к интерфейсу Rows. EN: PgxRows adapts pgx.Rows to the Rows interface.

func (*PgxRows) Close added in v0.1.6

func (r *PgxRows) Close() error

func (*PgxRows) Next added in v0.1.6

func (r *PgxRows) Next() bool

func (*PgxRows) Scan added in v0.1.6

func (r *PgxRows) Scan(dest ...any) error

type Query added in v0.1.4

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

func NewQuery added in v0.1.2

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

func (*Query[QROW]) List added in v0.1.4

func (q *Query[QROW]) List(ctx context.Context, ex Executor, filters ...Filter) ([]*QROW, error)

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
}

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
}

func NewTable

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

func (*Table[ROW]) Delete added in v0.1.4

func (t *Table[ROW]) Delete(ctx context.Context, ex Executor, keys ...any) error

func (*Table[ROW]) GetByPK added in v0.1.4

func (t *Table[ROW]) GetByPK(ctx context.Context, ex Executor, keys ...any) (*ROW, error)

func (*Table[ROW]) Insert added in v0.1.4

func (t *Table[ROW]) Insert(ctx context.Context, ex Executor, src *ROW) (*ROW, error)

func (*Table[ROW]) Internals added in v0.1.4

func (t *Table[ROW]) Internals() *tableInternals

func (*Table[ROW]) List added in v0.1.4

func (t *Table[ROW]) List(ctx context.Context, ex Executor, filters ...Filter) ([]*ROW, error)

func (*Table[ROW]) Update added in v0.1.4

func (t *Table[ROW]) Update(ctx context.Context, ex Executor, src *ROW) error

type TxAdapter added in v0.1.6

type TxAdapter struct {
	// contains filtered or unexported fields
}

TxAdapter адаптирует *sql.Tx к интерфейсу Executor. EN: TxAdapter adapts *sql.Tx to the Executor interface.

func NewTxAdapterVal added in v0.1.6

func NewTxAdapterVal(tx *sql.Tx) TxAdapter

NewTxAdapterVal создаёт адаптер для *sql.Tx к интерфейсу Executor. EN: NewTxAdapterVal creates an adapter from *sql.Tx to the Executor interface.

func (TxAdapter) ExecContext added in v0.1.6

func (a TxAdapter) ExecContext(ctx context.Context, query string, args ...any) (Result, error)

ExecContext выполняет запрос, не возвращающий строк. EN: ExecContext executes a query that does not return rows.

func (TxAdapter) QueryContext added in v0.1.6

func (a TxAdapter) QueryContext(ctx context.Context, query string, args ...any) (Rows, error)

QueryContext выполняет запрос, возвращающий строки. EN: QueryContext executes a query that returns rows.

func (TxAdapter) QueryRowContext added in v0.1.6

func (a TxAdapter) QueryRowContext(ctx context.Context, query string, args ...any) Row

QueryRowContext выполняет запрос, возвращающий одну строку. EN: QueryRowContext executes a query that returns a single row.

Directories

Path Synopsis
test

Jump to

Keyboard shortcuts

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