Перейти к содержанию

Связи и процедуры

Структуры FieldsObject* и ProcFields* описывают связи между моделями и хранимые процедуры.

FieldsObject* — Связи между моделями

Обзор

FieldsObject* позволяет автоматически загружать связанные объекты:

graph LR
    A[Order] -->|GetUser()| B[User]
    A -->|GetProduct()| C[Product]

Объявление

// Модель заказа
type FieldsOrder struct {
    Id        int64  `ar:"primary_key"`
    UserId    int64  `ar:""`
    ProductId int64  `ar:""`
    Quantity  uint32 `ar:""`
}

type FieldsObjectOrder struct {
    User    int64 `ar:"key:Id;object:User;field:UserId"`
    Product int64 `ar:"key:Id;object:Product;field:ProductId"`
}

Теги FieldsObject*

Тег Описание Пример
key Поле в связанном объекте key:Id
object Имя связанной модели object:User
field Поле в текущем объекте field:UserId
shard_by Функция определения шарда shard_by:*

Генерируемые методы

// Получение связанного объекта
user, err := order.GetUser(ctx)
if err != nil {
    return err
}

product, err := order.GetProduct(ctx)

Ленивая загрузка

Связанные объекты загружаются лениво — только при вызове геттера:

order, _ := order.SelectById(ctx, 123)

// Первый вызов — запрос к БД
user1, _ := order.GetUser(ctx)

// Повторный вызов — может использовать кэш
user2, _ := order.GetUser(ctx)

Шардирование

Для поиска по всем шардам используйте shard_by:*:

type FieldsObjectOrder struct {
    User int64 `ar:"key:Id;object:User;field:UserId;shard_by:*"`
}

Не реализовано

Функция определения шарда через shard_by в текущей версии не полностью реализована.

ProcFields* — Хранимые процедуры

Обзор

ProcFields* позволяет вызывать хранимые процедуры/функции БД:

graph LR
    A[Код] -->|CallProc| B[(БД)]
    B -->|Результат| A

Объявление

//ar:serverConf:octconf
//ar:namespace:calculate_stats  // Имя процедуры
//ar:backend:octopus
type ProcFieldsStats struct {
    // Входные параметры
    UserId    string `ar:"input;size:32"`
    StartDate string `ar:"input;size:10"`
    EndDate   string `ar:"input;size:10"`

    // Выходные параметры
    TotalOrders uint32 `ar:"output"`
    TotalAmount int64  `ar:"output"`
    AvgAmount   int64  `ar:"output"`
}

Теги ProcFields*

Тег Описание Пример
input Входной параметр input
output Выходной параметр output
output:N Выходной параметр с порядковым номером output:2
serializer Сериализатор для параметра serializer:Json
size Размер параметра size:256

Порядок параметров

Порядок объявления полей должен соответствовать сигнатуре процедуры:

type ProcFieldsMyProc struct {
    // Входные параметры в порядке сигнатуры
    Param1 string `ar:"input"`
    Param2 string `ar:"input"`

    // Выходные параметры в порядке возврата
    Result1 int64  `ar:"output"`
    Result2 string `ar:"output"`
}

Использование

// Создание объекта для вызова
stats := stats.New(ctx)

// Установка входных параметров
stats.SetUserId("123")
stats.SetStartDate("2024-01-01")
stats.SetEndDate("2024-12-31")

// Вызов процедуры
if err := stats.Call(ctx); err != nil {
    return err
}

// Чтение результатов
fmt.Printf("Total orders: %d\n", stats.GetTotalOrders())
fmt.Printf("Total amount: %d\n", stats.GetTotalAmount())

Octopus: особенности ProcFields*

Типы входных параметров

Для Octopus входные параметры могут быть только строкового типа:

type ProcFieldsOctopus struct {
    // Только string или []byte
    UserId string `ar:"input;size:32"`
    Data   []byte `ar:"input;size:1024"`

    // Для сложных типов — сериализатор
    Params string `ar:"input;serializer:Json;size:2048"`
}

Сериализаторы для входных параметров

type ProcFieldsOctopus struct {
    // Список строк через сериализатор
    Tags []string `ar:"input;serializer:StringList"`
}

type SerializersOctopus struct {
    Tags []string `ar:"pkg:github.com/myapp/serializers;object:TagsS"`
}

Сериализатор должен возвращать string или []string:

func TagsSMarshal(tags []string) ([]string, error) {
    return tags, nil  // Каждый элемент станет отдельным параметром
}

Выходные параметры

Выходные параметры соответствуют полям tuple:

type ProcFieldsOctopus struct {
    Input1 string `ar:"input"`

    // Порядок output полей = порядок в tuple результата
    Field1 int64  `ar:"output"`  // tuple[0]
    Field2 string `ar:"output"`  // tuple[1]
    Field3 uint32 `ar:"output"`  // tuple[2]
}

Полный пример

// model/repository/declaration/order.go
package repository

//ar:serverConf:pgconf
//ar:namespace:orders
//ar:backend:postgres
type FieldsOrder struct {
    Id        int64  `ar:"primary_key;init_by_db"`
    UserId    int64  `ar:""`
    ProductId int64  `ar:""`
    Quantity  uint32 `ar:""`
    Status    string `ar:"size:32"`
    CreatedAt uint32 `ar:""`
}

// Связи с другими моделями
type FieldsObjectOrder struct {
    User    int64 `ar:"key:Id;object:User;field:UserId"`
    Product int64 `ar:"key:Id;object:Product;field:ProductId"`
}

type IndexesOrder struct {
    UserCreated bool `ar:"fields:UserId,CreatedAt;order:CreatedAt desc"`
}
// model/repository/declaration/stats.go
package repository

//ar:serverConf:octconf
//ar:namespace:calculate_user_stats
//ar:backend:octopus
type ProcFieldsUserStats struct {
    // Входные параметры
    UserId    string `ar:"input;size:32"`
    StartDate string `ar:"input;size:10"`
    EndDate   string `ar:"input;size:10"`

    // Выходные параметры
    OrderCount   uint32 `ar:"output"`
    TotalAmount  int64  `ar:"output"`
    AverageOrder int64  `ar:"output"`
}
// Использование
func GetOrderWithDetails(ctx context.Context, orderId int64) error {
    // Получаем заказ
    ord, err := order.SelectById(ctx, orderId)
    if err != nil {
        return err
    }

    // Получаем связанного пользователя
    user, err := ord.GetUser(ctx)
    if err != nil {
        return err
    }

    // Получаем товар
    product, err := ord.GetProduct(ctx)
    if err != nil {
        return err
    }

    fmt.Printf("Order #%d: %s bought %s\n",
        ord.GetId(),
        user.GetName(),
        product.GetName(),
    )

    return nil
}

func GetUserStats(ctx context.Context, userId string) error {
    stats := userstats.New(ctx)
    stats.SetUserId(userId)
    stats.SetStartDate("2024-01-01")
    stats.SetEndDate("2024-12-31")

    if err := stats.Call(ctx); err != nil {
        return err
    }

    fmt.Printf("User %s: %d orders, total %d\n",
        userId,
        stats.GetOrderCount(),
        stats.GetTotalAmount(),
    )

    return nil
}

Ограничения

Циклические зависимости

Циклические зависимости между моделями через FieldsObject* приводят к некомпилируемому коду. Планируется добавить проверку на этапе парсинга.

Ссылка на себя

Ссылка модели на саму себя через FieldsObject* пока не поддерживается.

Следующие шаги