Селекторы¶
Селекторы — методы для поиска записей по индексам.
Типы селекторов¶
По уникальному индексу¶
Возвращает один объект:
По неуникальному индексу¶
Требует лимитер, возвращает слайс:
limiter := activerecord.NewLimiter(100)
users, err := user.SelectByStatus(ctx, "active", limiter)
if err != nil {
return err
}
Множественная выборка¶
Суффикс s — выборка по нескольким ключам:
// Несколько ID
ids := []int64{1, 2, 3, 4, 5}
users, err := user.SelectByIds(ctx, ids)
// Несколько email
emails := []string{"a@b.com", "c@d.com"}
users, err := user.SelectByEmails(ctx, emails)
Лимитеры¶
NewLimiter¶
Ограничение количества записей:
NewLimitOffset¶
Пагинация с offset:
// Страница 2 (записи 100-199)
limiter := activerecord.NewLimitOffset(100, 100)
users, err := user.SelectByStatus(ctx, "active", limiter)
NewThreshold¶
С предупреждением при достижении лимита:
limiter := activerecord.NewThreshold(1000)
users, err := user.SelectByStatus(ctx, "active", limiter)
// Если найдено 1000 записей — будет warning в логах
Интерфейс SelectorLimiter¶
type SelectorLimiter interface {
Limit() uint32 // Максимум записей
Offset() uint32 // Смещение
FulfillWarn() bool // Предупреждать при достижении
String() string
}
Составные индексы¶
Для составных индексов генерируются структуры типов:
Использование:
key := user.StatusCreatedIndexType{
Status: "active",
CreatedAt: 1234567890,
}
limiter := activerecord.NewLimiter(100)
users, err := user.SelectByStatusCreated(ctx, key, limiter)
Множественная выборка по составному индексу¶
keys := []user.StatusCreatedIndexType{
{Status: "active", CreatedAt: 1234567890},
{Status: "pending", CreatedAt: 1234567891},
}
users, err := user.SelectByStatusCreateds(ctx, keys, limiter)
SelectAll¶
Выборка всех записей (если включено):
// Выборка всех с лимитом
limiter := activerecord.NewLimiter(10000)
items, err := dict.SelectAll(ctx, limiter)
SelectAllOrdered¶
Выборка всех записей с указанием направления сортировки:
// По убыванию (DESC)
items, err := dict.SelectAllOrdered(ctx, 1000, postgres.CursorPosition{}, postgres.DESC)
// По возрастанию (ASC) — аналогично SelectAll
items, err := dict.SelectAllOrdered(ctx, 1000, postgres.CursorPosition{}, postgres.ASC)
// Пагинация с курсором в обратном порядке
items, err := dict.SelectAllOrdered(ctx, 100, postgres.CursorPosition{Values: []any{lastID}}, postgres.DESC)
Осторожно
SelectAll и SelectAllOrdered могут вернуть много данных. Максимальный лимит — 20000.
Обработка результатов¶
Уникальный индекс¶
user, err := user.SelectByEmail(ctx, "john@example.com")
if err != nil {
if errors.Is(err, activerecord.ErrNotFound) {
return nil, fmt.Errorf("user not found")
}
return nil, err
}
// user гарантированно не nil
fmt.Println(user.GetName())
Неуникальный индекс¶
limiter := activerecord.NewLimiter(100)
users, err := user.SelectByStatus(ctx, "active", limiter)
if err != nil {
return err
}
// users может быть пустым слайсом
if len(users) == 0 {
fmt.Println("No active users")
return nil
}
for _, u := range users {
fmt.Println(u.GetName())
}
Частичные индексы (IndexParts)¶
Выборка по части составного индекса:
type IndexesUser struct {
StatusCreatedId bool `ar:"fields:Status,CreatedAt,Id"`
}
type IndexPartsUser struct {
StatusPart bool `ar:"index:StatusCreatedId;fieldnum:1;selector:SelectByStatusOnly"`
}
Условные индексы¶
Условия автоматически добавляются в запрос:
type IndexesUser struct {
ActiveUsers bool `ar:"fields:CreatedAt;condition:Status[=]active&&DeletedAt[is null]"`
}
// Условие Status='active' AND DeletedAt IS NULL добавится автоматически
users, err := user.SelectByActiveUsers(ctx,
user.ActiveUsersIndexType{CreatedAt: 1234567890},
limiter,
)
Производительность¶
Количество ключей¶
// ✅ Хорошо — разумное количество
ids := []int64{1, 2, 3, 4, 5}
users, _ := user.SelectByIds(ctx, ids)
// ⚠️ Плохо — слишком много ключей
ids := make([]int64, 10000)
users, _ := user.SelectByIds(ctx, ids) // IN с 10000 значений
Пагинация¶
// Offset-based пагинация
func GetPage(ctx context.Context, page, pageSize int) ([]*user.User, error) {
offset := (page - 1) * pageSize
limiter := activerecord.NewLimitOffset(uint32(pageSize), uint32(offset))
return user.SelectByStatus(ctx, "active", limiter)
}
Best Practices¶
1. Всегда используйте лимитеры¶
// ❌ Плохо — неуникальный индекс без лимита
users, _ := user.SelectByStatus(ctx, "active", nil)
// ✅ Хорошо
users, _ := user.SelectByStatus(ctx, "active", activerecord.NewLimiter(100))
2. Проверяйте ошибки¶
3. Используйте правильный селектор¶
// ❌ Выборка всех + фильтрация в коде
users, _ := user.SelectAll(ctx, limiter)
active := filterActive(users)
// ✅ Выборка по индексу
users, _ := user.SelectByStatus(ctx, "active", limiter)
4. Контекст с таймаутом¶
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
users, err := user.SelectByStatus(ctx, "active", limiter)
Следующие шаги¶
- Атомарные операции — мутаторы
- Индексы — декларация индексов