github.com/mailru/activerecord@v1.12.2/docs/manual.md (about)

     1  # ActiveRecord для `Tarantool/Octopus/Postgres`
     2  
     3  `cmd/argen/main.go` - генератор кода моделей для БД
     4  
     5  Генерирует схему пакеты в соответствии с декларативным описанием БД. Используется паттерн `Active Record`, как подход доступа к данным в БД. Таблицы/спейсы базы данных или представление описанные в файлах оборачивается в пакеты.
     6  
     7  Аргументы командной строки:
     8  
     9  - --path - путь к модели (описание модели должно быть вложено внутрь этого пути), путь не может начинаться с `.`
    10  - --fixture_path - путь к папке в которую попадут сгенерированные файлы доступа к тестовым фикстурам, путь не может начинаться с `.`
    11  - --declaration - путь к папке где находятся файлы с декларативным описанием схемы БД, по умолчанию `declaration`
    12  - --destination - путь к папке в которую попадут сгенерированные пакеты, по умолчанию `generated`
    13  - --module - имя модуля(приложения) внутри которого генерируются пакеты, по умолчанию берётся из `go.mod`
    14  
    15  !**Важно**
    16  
    17  - Имя пакета в описании модели должно быть `repository`.
    18  - Все файлы .go по указанному пути будут удалены при перегенерации
    19  - Внутри папки со сгенерированными пакетами находится файл `.argen`, который нельзя удалять, его наличие проверяется при следующей перегенерации.
    20  
    21  ## Описание модели
    22  
    23  ```golang
    24  package model
    25  
    26  import "......./model/serializer"
    27  
    28  //ar:serverHost:127.0.0.1;serverPort:11011;serverTimeout:500
    29  //ar:namespace:5
    30  //ar:backend:octopus
    31  type FieldsFoo struct {
    32      Id        int32  `ar:"primary_key"`
    33      Name      string `ar:"serializer:Json;size:256"`
    34      AnotherId int32  `ar:""`
    35      Type      string `ar:"selector:SelectByType;size:64"`
    36      Product   uint64 `ar:"serializer:Product"`
    37      Flags     uint32 `ar:"mutators:set_bit,clear_bit"`
    38  }
    39  
    40  type (
    41      IndexesFoo struct {
    42          TypeId bool `ar:"fields:Type,Id;unique"`
    43      }
    44      IndexPartsFoo struct {
    45          TypePart bool `ar:"index:TypeId;fieldnum:1;selector:SelectByTypePart"`
    46      }
    47  )
    48  
    49  type SerializersFoo struct {
    50      Json    map[string]interface{} `ar:""`
    51      Product *serializer.Product    `ar:"pkg:......./model/serializer;object:ProductS"`
    52  }
    53  
    54  type TriggersFoo struct {
    55      RepairTuple bool `ar:"pkg:......../test/model/repair;func:RepairTuple"`
    56  }
    57  ```
    58  
    59  Все данные для доступа к БД хранятся в комментариях и тегах.
    60  
    61  ## Комментарии к структуре `Fields*`
    62  
    63  Все данные для доступа к БД, а так же тип БД хранится в комментариях к структуре `Fields*`
    64  Все комментарии должны начинаться с префикса `ar:`
    65  
    66  Параметры можно группировать в одну строку или каждый писать на своей строке. При группировке в одной строке параметры разделяются между собой символом `;`
    67  
    68  ### serverHost
    69  
    70  IP адрес или имя хоста, где запущена БД
    71  
    72  ### serverPort
    73  
    74  Порт для подключения к БД
    75  
    76  ### serverTimeout
    77  
    78  Таймаут при работе с БД
    79  
    80  ### namespace
    81  
    82  Номер спейса если используется `octopus` (`tarantool 1.5`). При вызове функции/процедуры содержит имя процедуры
    83  
    84  ### backend
    85  
    86  Тип базы данных где храниться данная модель. В будущем можно будет указать много `бекендов` для переезда с одного хранилища в другое.
    87  
    88  ### shard_by
    89  
    90  Определят функцию выбора `шарда`. Используется для новых записей для получения если запрос делается по ключу указанному в `shard_by`
    91  
    92  !Не реализовано
    93  
    94  ### serverConf
    95  
    96  Вся конфигурация хранилищ построена вокруг `шардов`. У каждого `шарда` есть мастера и реплики.
    97  Более подробно см. в разделе использование конфига
    98  
    99  ## Структуры для описания модели
   100  
   101  ### Fields* (поля модели)
   102  
   103  Перечисление всех полей в таблице/спейсе. В тегах у каждого поля возможно указать дополнительные опции:
   104  
   105  - `primary_key` - является ли поле первичным ключом, всегда уникальный, флаг `unique` можно не указывать;
   106  - `unique` - уникальный ли индекс (по умолчанию - неуникальный);
   107  - `selector` - имя метода-селектора, который нужно создать для соответствующего индекса;
   108  - `serializer` - позволяет навесить дополнительную сериализацию на поле; Формат: `Name[,params]`. Параметры необязательные, но если их указать то они будут переданы в функции `marshal`, `unmarshal`
   109  - `size` - длина поля в байтах (для числовых полей вычисляется автоматически). Используется при десериализации и при прогнозировании потребляемого объёма.
   110  - `mutators` - список методов-модификаторов, которые нужно создать для поля для выполнения спец-операций. Предопределены типы мутаторов: `inc`, `dec`, `and`, `or`, `xor`, `set_bit`, `clear_bit`. Но также возможно описать пользовательский тип мутатора. У каждого поля может быть несколько преопределенных мутаторов, но не более одного пользовательского
   111  !Внимание! По умолчанию поля с сериализацией не попадают в список на обновление при изменении внутреннего состояния десериализованной структуры и как следствие не уходит в БД при Update. Но можно описать подобное поведение с помощью пользовательского типа мутатора
   112  
   113  #### Для octopus
   114  
   115  Порядок, в котором перечисляются поля важен и должен строго соответствовать порядку полей в тупле:
   116  
   117  - номер поля в тупле вычисляется автоматически на основании порядка объявления полей в модуле;
   118  - тип поля связан с типом данных полей структуры
   119  - если в тупле оказывается полей больше, чем описано, то они попадают в специальное поле extraFields
   120  - если в тупле полей меньше, чем описано, то вызывается `repairTrigger`
   121  
   122  ### FieldsObject*
   123  
   124  Позволяет связать модели между собой, например id пользователя с объектом пользователя. В структуре перечисляются все поля, которые являются ссылками на другие модели. Каждое поле имеет теги. В тегах можно использовать следующие дополнительные параметры:
   125  
   126  - `key` - имя поля в привязываемом объекте
   127  - `object` - имя связанного объекта
   128  - `field` - имя поля в текущем объекте
   129  - `shard_by` - функция, по которой можно определить в каком шарде храниться запись; Если указать `*` то поиск будет производиться по всем шардам. (!Не реализовано)
   130  
   131  ### Indexes*
   132  
   133  Применяется для описания индексов (как правило, мульти-колоночных, так как одно-колоночные проще описывать прямо в `Fields*`). Доступны следующие опции:
   134  
   135  - `fields` - список участвующих в индексе полей;
   136  - `unique` - уникальный ли индекс (по умолчанию: неуникальный);
   137  - `primary_key` - индекс является первичным ключом;
   138  - `selector` - имя метода-селектора, который нужно создать для индекса;
   139  - `orderdesc` - поля отсортированные в индексе в обратном направлении. Необходимо только для генерации конфига для `octopus`;
   140  - `shard_by` - функция (или имя метода), используемая для вычисления шарда на основании данных полей индекса (!Не реализовано);
   141  
   142  Для octopus-а:
   143  
   144  - номер индекса, автоматически вычисляется на основании порядка объявления индексов;
   145  
   146  Порядок в котором перечисляются индексы (в том числе неявно в `has_field`) важен и должен совпадать с порядком индексов в конфиге octopus-а.
   147  
   148  ### IndexParts*
   149  
   150  Применяется для создания возможности делать выборки по части мульти-колоночного индекса. Допустимые параметры:
   151  
   152  - `index` - имя мульти-колоночного индекса;
   153  - `fieldnum` - количество используемых полей мульти-колоночного индекса (по умолчанию: 1);
   154  - `selector` - имя метода-селектора, который нужно создать для индекса;
   155  
   156  ### ProcFields* (описание модели вызова функции/процедуры)
   157  
   158  Перечисление входных и выходных параметров сигнатуры функции или хранимой процедуры БД. В тегах у каждого поля возможно указать следующие опции:
   159  
   160  - `input` - обязательный для всех входных параметров в сигнатуре вызова;
   161  - `output` - обязательный для всех выходных параметров в сигнатуре;  Для БД поддерживающих inout параметры возможно комбинировать с `input`. Формат: `output[:orderNum]`.
   162  Где [orderNum] - необязательный порядковый номер параметра, по умолчанию параметры следуют в порядке перечисления 
   163  - `serializer` - позволяет навесить дополнительную сериализацию на поле; Формат: `Name[,params]`. Параметры необязательные, но если их указать то они будут переданы в функции `marshal`, `unmarshal`
   164  - `size` - длина поля в байтах для возможности валидации (в реализации для octopus не используется)
   165  
   166  Корректное описание требует как минимум одного поля с опцией `output`. 
   167  Порядок, в котором перечисляются поля важен и должен строго соответствовать порядку следования в сигнатуре вызова
   168  
   169  #### Для octopus
   170  Порядок, в котором перечисляются поля для значений выходных параметров важен и должен строго соответствовать порядку полей в тупле.
   171  
   172  - входные параметры могут быть только строкового типа
   173  - входные параметры для списка строк должны иметь сериализатор в строку
   174  - сериализаторы для входных параметров в методе marshal должны возвращать типы string или []string 
   175  - номер поля выходного параметра в тупле вычисляется автоматически на основании порядка объявления полей выходных параметров в модуле;
   176  - если кол-во полей в тупле оказывается меньше чем кол-во перечисленных выходных параметров, то оставшиеся поля останутся пустыми
   177  
   178  Для вызова процедур у octopus входные параметры могут быть только строкового типа. Поэтому в качестве типа входного параметра можно указывать только строку (включая []byte)
   179  Но можно объединять входные параметры в структуры используя сериализатор (см. [примеры](https://github.com/mailru/activerecord-cookbook/blob/proc-example/example/model/repository/declaration/foo.go).
   180  Или список при наличии у параметра сериализатора. В этом случае при вызове процедуры будут передаваться строковые параметры в последовательности которую вернет сериализатор
   181  
   182  ### Serializers*
   183  
   184  Объявление дополнительных сериализаторов для полей. Когда не хватает обычных типов и необходимо работать, например, со словарями, то можно объявить сериализатор, который будет применяться для определённого поля. Тип сериализатора переопределяет тип поля внутри объекта. Допустимые параметры в тегах:
   185  
   186  - `pkg` - указывает на пакет в котором находится определение сериализатора. По умолчанию `github.com/mailru/activerecord/pkg/serializer`. Пакет не обязательно импортировать, импорт добавиться автоматически.
   187  - `marshaler` - функция сериализации данных, на вход функция принимает параметры указанные при объявлении сериализатора и переменную с типом поля к которому привязывается сериализатор, на выход ожидается тип указанный в сериализаторе. Имя по умолчанию `Name + "Marshal"`
   188  - `unmarshaler` - функция десериализации данных, на вход функция принимает параметры указанные при объявлении сериализатора и переменную с типом сериализатора, на выход ожидается тип поля к которому привязывается сериализатор. Имя по умолчанию `Name + "Unmarshal"`
   189  
   190  В пакете `go-activerecord` есть встроенные сериализаторы:
   191  
   192  - `Json` - позволяет хранить в БД строку и десериализовывать ее в кастомный тип пользователя, под капотом использует стандартный пакет encoding/json
   193  - `Printf` - позволяет хранить в БД строку в определённом формате подобном `printf`, обязательно указывать формат в определении поля, см. `serializer` в структуре `Fields`
   194  - `Mapstructure` - позволяет хранить в БД строку и десериализовывать ее в кастомный тип пользователя с возможностями библиотеки mapstructure см. https://pkg.go.dev/github.com/mitchellh/mapstructure
   195  
   196  ### Mutators*
   197  
   198  Объявление пользовательских мутаторов для полей. Когда необходима особая логика модификации полей, которую нельзя реализовать с помощью стандартных операций, например при изменении внутреннего состояния десериализованной структуры
   199  Данная логика модификации реализуется на стороне БД с помощью функций/процедур. Сигнатура вызова процедуры следующая:
   200  
   201  - значение первичного ключа
   202  - список значений возвращаемых функцией преобразования (см. ниже) или в случае отсутствии функции преобразования - значение модифицированного поля 
   203  
   204  При указании мутатора для сериализуемого (составного) поля, для каждого поля сериализуемой структуры генерируется дополнительный метод установки значения.
   205  Для формирования параметров вызова функций/процедур нужно реализовать функцию преобразования. Функция принимает на вход 2 параметра: значение поля до модификации и набор модифицированных значений полей составной структуры
   206  В случае сериализуемого второй второй параметр содержит значения полей сериализуемой структуры внутри `map[string]any`
   207  
   208  - `pkg` - Для сериализаемых(составных) полей указывает на пакет в котором находится функция преобразования в параметры. В `octopus` для простых(несериализуемых) полей не требуется.
   209  - `update` - имя функции/процедуры БД которая будет использоваться модификации данных.
   210  - `replace` - имя функции/процедуры БД которая будет использоваться модификации данных. В `octopus` не реализована
   211  
   212  
   213  Пример описания мутатора для сериализуемого поля
   214  ```golang
   215  package ds
   216  
   217  type Bar struct {
   218    Name      string
   219  }
   220  
   221  package model
   222  
   223  type FieldsFoo struct {
   224      Bar   string `ar:"serializer:Bar;mutators:BarPart;"`
   225  }
   226  
   227  type SerializersFoo struct {
   228    Bar *ds.Bar    `ar:"pkg:......./model/serializer;object:BarS"`
   229  }
   230  
   231  type MutatorsFoo struct {
   232    BarPart bool `ar:"update:updateBar;pkg:......./model/conv;"`
   233  }
   234  
   235  package conv
   236  
   237  func FooBarPart(bar *ds.Bar, partBar map[string]any) ([]string, error) {
   238    values, err := serializer.Marshal(partBar)
   239    if err != nil {
   240      return nil, err
   241    }
   242      
   243    return values
   244  }
   245  ```
   246  
   247  
   248  ### Triggers*
   249  
   250  Триггеры срабатывают на определённые исключительные случаи при запаковке/распаковке данных и/или при ошибках работы с БД. В каждом конкретном случае в триггер приходят свой набор данных.
   251  
   252  Для инициализации триггера необходимо указать параметры в тегах. В данный момент поддерживаются следующие параметры:
   253  
   254  - `pkg` - имя пакета в котором находится функция обработчик
   255  - `func` - функция обработчик, которая будет вызвана в случае наступления данного события
   256  
   257  Доступные триггеры:
   258  
   259  Название | Сигнатура функции | Описание
   260  --|--|--
   261  TupleRepair | `func(tuple *octopus.TupleData) error` | Вызывается в случае проблем с десериализацией данных полученных из БД. Например, неверное число полей в тупле по отношению к описанию или неверный формат поля.
   262  
   263  В случае если запись была исправлена то поле `Repaired` у структуры принимает значение `true`.
   264  
   265  ## Использование конфига
   266  
   267  Параметры конфигурации строятся относительно `serverConf`. Дерево конфигурации выглядит так:
   268  
   269  - `max-shard` (Количество `шардов`. Автоматический решардинг не предусмотрен. Этот параметр менять с крайней осторожностью! НЕ РЕАЛИЗОВАННО!)
   270  - `Timeout` (Таймаут по умолчанию для всех подключений в этом кластере)
   271  - `PoolSize` (Размер пула соединений по умолчанию для этого кластера )
   272  - `1` (Конфигурация для первого `шарда`. Аналогично указывается для всех остальных)
   273    - `Timeout` (Таймаут для конкретного `шарда`)
   274    - `PoolSize` (Размер пула соединений для этого `шарда`)
   275    - `master` (Список серверов являющихся мастерами (`rw`), разделённые запятой)
   276    - `replica` (Список серверов являющихся репликами (`ro`), разделённые запятой)
   277  
   278  В случае когда указано несколько мастер серверов, запись будет производиться во все мастера, которые меду собой ни как не связаны и при ошибках записи могут расходиться, понятия транзакционной целостности между мастерами не предусмотрено. Можно использовать когда организовывается кеш, данные кладутся во все мастера, когда нет репликации от слова совсем, например для баз у которых репликация не предусмотрена вообще (мемкеш).
   279  
   280  В случае, когда для `шарда` надо указать только мастера, то конфигурацию можно упростить:
   281  
   282  - `Timeout` (Таймаут по умолчанию для всех подключений в этом кластере)
   283  - `PoolSize` (Размер пула соединений по умолчанию для этого кластера )
   284  - `1` (Список серверов `rw` указывается непосредственно тут)
   285    - `Timeout` (Таймаут для конкретного `шарда`)
   286    - `PoolSize` (Размер пула соединений для этого `шарда`)
   287  
   288  В случае если `шард` только один то можно упростить еще больше:
   289  
   290  - `Timeout` (Таймаут по умолчанию для всех подключений в этом кластере)
   291  - `PoolSize` (Размер пула соединений по умолчанию для этого кластера )
   292  - `master` (Список серверов являющихся мастерами (`rw`), разделённые запятой)
   293  - `replica` (Список серверов являющихся репликами (`ro`), разделённые запятой)
   294  
   295  В случае когда только мастер и только для одного `шарда`, то `ip:port` складывается непосредственно в serverConf:
   296  
   297  - `Timeout` (Таймаут по умолчанию для всех подключений в этом кластере)
   298  - `PoolSize` (Размер пула соединений по умолчанию для этого кластера )
   299  
   300  `Timeout` и `PoolSize` - это опциональные параметры на всех уровнях, чем больше уровень вложенности тем выше приоритет параметра.
   301  
   302  ## Хелперы для конфигурирования коробки
   303  
   304  !Не реализовано
   305  
   306  Для того, чтобы упростить написание конфигурации неймспейса и автоматизировать подсчет необходимой под него памяти, присутствует утилита `storage_config` работающая в 2-х режимах, которые задаются параметром `-mode`.
   307  
   308  и `print_approximate_storage_size`.
   309  
   310  ### `print`
   311  
   312  Для этого режима можно задать форматирование `-format`, поддерживается `text` (по умолчанию), `json` - подходит для `puppet`-а
   313  
   314  ```bash
   315  storage_config -mode print
   316  ```
   317  
   318  Печатает в `STDOUT` конфиг. Можно скопировать as is.
   319  
   320  ### `approximate_size`
   321  
   322  Печатает в `STDOUT` примерный размер хранилища. Учитывает размер памяти под хранение туплов, их фрагментацию, индексы и их заполнение. В качестве параметра принимает прогнозируемое количество записей. Если среди полей встречаются строки, то используется параметр `size`.
   323  
   324  ```bash
   325  storage_config -mode approximate_size
   326  ```
   327  
   328  ## Генерация пакета
   329  
   330  Для каждого файла формируется структура со всеми полями из БД, плюс дополнительные поля:
   331  
   332  - `UpdateOps` - список изменений в объекте, которые будут отправлены при `Update`;
   333  - `ShardNum` - номер шарда, из которого была получена запись; (Пока не реализовано!)
   334  - `IsReplica` - была ли запись получена из реплики
   335  - `Exists` - `true` - когда запись получена из коробки, `false` - когда запись создана при помощи `New`
   336  - `Repaired` - истина если тупл был скорректирован триггером `RepairTuple` влияет на поведение функции `Update`, она в этом случае будет работать как `Replace`
   337  - `Readonly` - запрещены ли над записью операции модификации (кроме update и delete; если справедливо `replica`, то будет справедливо и `readonly`)
   338  
   339  Для каждого индекса формируются селекторы
   340  
   341  Для всех составных ключей формируются отдельные тип, которые содержат в себе все поля ключа. Эти типы используются в селекторах.
   342  
   343  Для каждого поля формируются аксессоры `Get...` и `Set...`, доступа к полям напрямую нет.
   344  
   345  ### Accessors
   346  
   347  Для каждого описанного поля в БД формируется пара аксессоров. Геттер с префиксом `Get`, сеттер с префиксом `Set`. Для полей которые участвуют в первичном ключе формируется защита от его изменения, такие поля менять нельзя.
   348  
   349  ### Selectors
   350  
   351  Для каждого индекса формируется набор селекторов. Префикс у селектора - `SelectBy`. Суффикс - используется указанный при описании индекса в поле selector. Если имя селектора не указано, то он является именем индекса или поля если индекс не составной. В итоге получается SelectBy{SelectorName}.
   352  
   353  Формируется 2 вида селекторов:
   354  
   355  - по одному ключу
   356  - по набору ключей (имеет дополнительный суффикс `s`)
   357  
   358  Не путать с уникальными или неуникальными индексами.
   359  
   360  При селекте по одному ключу, уникальный индекс возвращает `0` или `1` запись.
   361  При селекте по набору ключей, уникальный индекс возвращает от `0` до `n` записей, где `n` - это количество переданных ключей в селектор.
   362  
   363  Для составных ключей параметром используется специальный тип данных (структура) с именем индекса, у этого типа данных будут все поля участвующие в индексе.
   364  
   365  Для всех неуникальных ключей у селектора присутствует дополнительный параметр `limiter` с интерфейсом `activerecord.SelectorLimiter` который ограничивает выборку по неуникальному ключу, и даёт возможность установить `offset`. При селекте по нескольким ключам или по неуникальному полю важно проверять достигли лимита или нет, если это используется для словарей, когда всё надо достать за один поход и важно не пропустить момент, когда лимит достигнут, то необходимо выставить FullfillWarn в true. (!Не реализовано Если селект идёт по ключу в разные шарды то лимит действует на каждый шард! Возвращено может быть limit * shardCount записей!)
   366  
   367  ```golang
   368  type SelectorLimiter interface {
   369    Limit() uint32
   370    Offset() uint32
   371    FullfillWarn() bool
   372    fmt.Stringer
   373  }
   374  ```
   375  
   376  Готовая реализация под этот интерфейс:
   377  
   378  ```golang
   379  type Limiter struct {
   380    limit, offset uint32
   381    fullfillWarn  bool
   382  }
   383  
   384  func NewLimiter(limit uint32) Limiter {
   385    return Limiter{limit: limit}
   386  }
   387  
   388  func NewLimitOffset(limit uint32, offset uint32) Limiter {
   389    return Limiter{limit: limit, offset: offset}
   390  }
   391  
   392  func NewThreshold(limit uint32) Limiter {
   393    return Limiter{limit: limit, fullfillWarn: true}
   394  }
   395  ```
   396  
   397  Важно! Если в момент селекта по уникальному ключу вернётся больше чем одно значение (такое может случиться при селекте из разных шардов), то будет возвращена ошибка. (!Не реализовано, будет вызван хук в который будут отданы все поднятые объекты, что бы эту ситуацию можно было поправить).
   398  
   399  ### Mutators (Мутаторы)
   400  
   401  При описании мутаторов у полей, формируются дополнительные методы, которые позволяют делать атомарные операции в БД, например инкремент или декремент. Важно, что при обращении в БД будет выполнена именно такая операция, которая увеличит/уменьшит/... значение на дельту, а не выставит то значение которое сейчас у объекта. Происходит это в момент вызова метода `Update`, после его вызова данные из БД будут и в обратную сторону синхронизированы с объектом.
   402  
   403  Генерируемые методы:
   404  
   405  Название | Операция
   406  ---|---
   407  `SetBit*` | `|= arg`
   408  `ClearBit*` | `&= ^arg`
   409  `Inc*`  | `+= arg`
   410  `Dec*` | `-= arg`
   411  `Or*` | `|= arg`
   412  `Xor*` | `^= arg`
   413  `And*` | `&= arg`
   414  
   415  ### Создание структуры
   416  
   417  При генерации формируется функция `New` создающая новую структуру для модели, используется в случае когда надо создать новую запись с возможностью потом сохранить её в БД.
   418  
   419  ### Методы управления
   420  
   421  `Update` - обновляет представление сущности в БД, важно понимать, что обновляются только поля изменённые в объекте. Нельзя обновить сущность у которой не установлен флаг Exists. (!Не реализовано! После обновления значения полей могут поменяться в зависимости от того, что есть в БД!)
   422  
   423  `Insert` - добавление записи в БД, нельзя добавить сущность у которой стоит флаг `Exists`. Если произойдёт пересечение по первичному ключу то метод отдаст ошибку и сущность не будет сохранена в БД.
   424  
   425  `Replace` - перезапись всех полей сущности в БД, не только изменённые. Нельзя вызвать у сущности у которой не выставлен флаг `Exists`. Возвращает ошибку если у сущности выставлен флаг ReadOnly.
   426  
   427  `InsertOrReplace` - атомарная операция которая позволяет добавить сущность в БД или перезаписать её, если есть пересечение по первичному ключу! Если пересечение не по первичному уникальному ключу, то будет возвращена ошибка.
   428  
   429  `Delete` - операция удаления сущности из БД, нельзя удалить сущность у которой не выставлен флаг `Exists`.
   430  
   431  ### Статистика
   432  
   433  Сбора статистики происходит посредством использования интерфейса `activerecord.MetricInterface`.
   434  В процессе работы сгенерированных пакетов, собирается статистика по метрикам:
   435  
   436  - временным
   437    - `select_pack`
   438    - `select_box`
   439    - `select_process`
   440    - `select_newobj`
   441    - `delete_box`
   442    - `update_box`
   443    - `insertreplace_packtuple`
   444    - `insertreplace_pack`
   445    - `insertreplace_box`
   446    - `call_proc`
   447  - статистическим
   448    - `insert_success`
   449    - `insertorreplace_success`
   450    - `replace_success`
   451    - `update_repaired`
   452    - `delete_request`
   453    - `delete_success`
   454    - `insert_request`
   455    - `insertorreplace_request`
   456    - `replace_request`
   457    - `select_keys`
   458    - `select_tuples_res`
   459    - `update_request`
   460    - `update_success`
   461    - `update_empty`
   462  - ошибочным
   463    - `compare_packfield`
   464    - `delete_box`
   465    - `delete_pack`
   466    - `delete_preparebox`
   467    - `delete_resp`
   468    - `insert_exists`
   469    - `insertreplace_box`
   470    - `insertreplace_obj`
   471    - `insertreplace_packfield`
   472    - `insertreplace_preparebox`
   473    - `insertreplace_prespreparebox`
   474    - `replace_notexists`
   475    - `select_box`
   476    - `select_preparebox`
   477    - `select_resp`
   478    - `update_box`
   479    - `update_notexists`
   480    - `update_packpk`
   481    - `update_preparebox`
   482    - `update_resp`
   483    - `call_proc`
   484    - `call_proc_preparebox`
   485  
   486  ## Пример
   487  
   488  ### Файл
   489  
   490  `model/repository/decl/foo.go`
   491  
   492  ```golang
   493  package repository
   494  
   495  //ar:serverHost:127.0.0.1;serverPort:11011;serverTimeout:500
   496  //ar:namespace:6
   497  //ar:backend:octopus
   498  type FieldsFoo struct {
   499      Id       string `ar:"primary_key;size:36"`
   500      Code     string `ar:"size:128;selector:SelectByCode"`
   501      Email    string `ar:"size:256;selector:SelectByEmail"`
   502      Start    uint32 `ar:""`
   503      Finish   uint32 `ar:""`
   504      Action   uint32 `ar:""`
   505      Platform string `ar:"size:64"`
   506  }
   507  
   508  type (
   509      IndexesFoo struct {
   510          EmailCode   bool `ar:"fields:Email,Code;unique"`
   511          EmailAction bool `ar:"fields:Email,Action;unique"`
   512      }
   513      IndexPartsFoo struct {
   514          EmailPart bool `ar:"index:EmailCode;fieldnum:1"`
   515      }
   516  )
   517  ```
   518  
   519  
   520  ### Запуск генератора
   521  
   522  `argen --path 'model/repository' --declaration "decl" --destination 'cmpl'`
   523  
   524  В результате его работы будет сгенерирован файл `model/repository/cmpl/foo/octopus.go`
   525  
   526  #### Функции
   527  
   528  Создание нового пустого объекта - `foo.New`
   529  
   530  Пары селекторов (по набору ключей и по единичному ключу, соответственно) по всем индексам
   531  
   532  ```txt
   533  SelectByIds          SelectById
   534  SelectByCodes        SelectByCode
   535  SelectByEmails       SelectByEmail
   536  SelectByEmailCodes   SelectByEmailCode
   537  SelectByEmailActions SelectByEmailAction
   538  ```
   539  
   540  #### Методы
   541  
   542  Для каждого поля пара методов
   543  
   544  ```txt
   545  GetId       SetId
   546  GetCode     SetCode
   547  GetEmail    SetEmail
   548  GetStart    SetStart
   549  GetFinish   SetFinish
   550  GetAction   SetAction
   551  GetPlatform SetPlatform
   552  ```
   553  
   554  `Delete` - удаление объекта
   555  
   556  `Update` - обновление объекта
   557  
   558  `Insert` - вставка объекта
   559  
   560  `Replace` - перезапись объекта
   561  
   562  `InsertOrReplace` - вставка или перезапись
   563  
   564  #### Структуры
   565  
   566  Для всех многоколоночных индексов будут созданы свои структуры, которые используются соответствующими селекторами.
   567  
   568  `EmailCodeIndexType` с полями `Email` и `Code`
   569  
   570  `EmailActionIndexType` с полями `Email` и `Action`
   571  
   572  ## Поток преобразования данных
   573  
   574  ```txt
   575  БД
   576  ---              []bytes
   577                   []tuples
   578                   [][]fields
   579                   [][]deserializedFields
   580  Приложение       []structs
   581                   [][]serializedFields
   582                   [][]packedFields
   583                   []PackedTuples
   584  ---              []bytes
   585  БД
   586  ```
   587  
   588  ## ToDo
   589  
   590  ### Описание пакета pkg/octopus
   591  
   592  Необходимо добавить документацию к пакету!
   593  
   594  ### Запрет на поднятие связанных объектов
   595  
   596  Есть случаи когда ходить в базу уже недопустимо, например из хелперов подготавливающих объект для АПИ. Или из слоя у которого не должно быть доступа к репозиторию.
   597  Можно завязаться на переменную контекста.
   598  
   599  ### Описание атрибутов
   600  
   601  Добавить атрибуты к описанию
   602  
   603  - `sequence` - является ли поле автоинкрементным;
   604  - `sequence_id` - номер сиквенса, по умолчанию совпадает с номером неймспейса;
   605  - `sequence_iproto` - кластер `iproto`, используемый для хранения неймспейса с сиквенсами;
   606  - `sequence_namespace` - номер неймспейса с сиквенсами;
   607  (используется только для прогнозирования объема хранилища);
   608  
   609  Поддержать `enum` поля
   610  
   611  ### Sequence для octopus
   612  
   613  Зачастую возникает необходимость сделать поле автоинкрементным. Для этого можно завести (можно даже в отдельно) `tarantool/Octopus` с неймспейсом, в котором будут храниться текущие значения всех сиквенсов. При этом удобно, чтобы для автоинкрементных первичных ключей номер сиквенса (значение первичного ключа в этом неймсейсе) совпадал с номером неймспейса. Все прочие сиквенсы лучше нумеровать откуда-нибудь от миллиона. Для автоматического использования этой возможности достаточно указать при объявлении поля параметр `sequence => 1`. Но, разумеется, если настройки кластера `iproto` и номер неймспейса для коробочки не указаны глобально, то придется указать и их. Чтобы задать эти настройки глобально можно (но не рекомендуется) сделать примерно следующее:
   614  
   615  Для Octopus перед первым применением сиквенс необходимо инициализировать в хранилище сиквенсов.
   616  
   617  ### Тестовая среда
   618  
   619  //cloud-58  посмотреть и начать использовать
   620  
   621  ### Ссылочные данные и UpdateOps
   622  
   623  Если используются сериализаторы, которые превращают данные из БД в ссылочный тип в `golang` то при изменении внутренностей этого ссылочного типа поле не будет обновлено при вызове метода `Update`. Можно всегда такие поля обновлять не думая обновляли их или нет. Можно оставить это на усмотрение разработчика. а можно в сериализатор пропихивать функцию, при вызове которой поле будет помечаться грязным и пусть каждый живёт с этим как может.
   624  
   625  # Тестовые фикстуры для сгенерированной модели ActiveRecord
   626  Нужны для создания объектов данных в интеграционных тестах.
   627  
   628  `cmd/argen/main.go` при наличии ключа fixture_path генерирует в папку по этому пути в пакете `fixture` файлы доступа к фикстурам данных, 
   629  описываемых в одноименных с именами сущностей файлах, которые должны находится в папке `{fixture_path}/fixture/data`
   630  Доступ к фикстурам осуществляется по первичному ключу описанному в структуре модели
   631  
   632  Если файлы не были созданы предварительно генератор создает пустые yaml файлы для всех сущностей описанной модели.
   633  Именование полей модели в yaml файле в формате snake case. Подсмотреть на [примере](https://github.com/mailru/activerecord-cookbook/tree/main/example/testutil/fixture)
   634  
   635  ## Примеры использования фикстур
   636  ## Update
   637  
   638  ```golang
   639  package test
   640  
   641  import (
   642    "context"
   643    "github.com/mailru/activerecord-cookbook/example/testutil/fixture"
   644    "github.com/mailru/activerecord/pkg/octopus"
   645  )
   646  
   647  func sometest() {
   648    triggerFunc := func(fixtures []octopus.FixtureType) []octopus.FixtureType {
   649      /* any trigger logic */
   650      return fixtures
   651    }
   652  
   653    // 1) hand-made fixture
   654    updateHandMadeFixture, isUsed1 := fixture.UpdateRewardFixture("primary-code-1").
   655      WithUpdatedPartner("some-partner").
   656      /* another updated fields ... */
   657      OnUpdate(triggerFunc).
   658      Build(context.TODO())
   659  
   660    // 2) fixture from reward_update.yaml:
   661    //	  - code: primary-code-2
   662    //        update_options:
   663    //         - partner:
   664    //              set_value: sobakamiloru
   665    //         - description:
   666    //              set_value: null
   667  
   668    updateFixtureFromYaml, isUsed2 := fixture.GetUpdateRewardFixtureByCode(context.TODO(), "primary-code-2", triggerFunc)
   669  
   670    //... run test
   671  
   672    // проверка того что фистура была использована
   673    assertTrue(isUsed1(), "fixture must be applied")
   674    assertTrue(isUsed2(), "fixture must be applied")
   675  }
   676  ```
   677  
   678  # Insert \ InsertOrReplace \ Replace
   679  ```golang
   680  package test
   681  
   682  import (
   683    "context"
   684    "github.com/mailru/activerecord-cookbook/example/testutil/fixture"
   685    "github.com/mailru/activerecord-cookbook/example/model/repository/generated/reward"
   686    "github.com/mailru/activerecord/pkg/octopus"
   687  )
   688  
   689  func sometest() {
   690      triggerFunc := func(fixtures []octopus.FixtureType) []octopus.FixtureType {
   691        /* any trigger logic */
   692        return fixtures
   693      }
   694  	
   695      // 1) hand-made fixtures
   696      model := reward.New(context.TODO())
   697      model.SetPartner("some-partner")
   698      /* another setFields fields ... */
   699  	
   700      insertHandMadeFixture, isUsed1 := fixture.GetInsertRewardFixtureByModel(context.TODO(), model, triggerFunc)
   701      replaceHandMadeFixture, isUsed2 := fixture.GetReplaceRewardFixtureByModel(context.TODO(), model, triggerFunc)
   702      insertOrReplaceHandMadeFixture, isUsed3 := fixture.GetInsertOrReplaceRewardFixtureByModel(context.TODO(), model, triggerFunc)
   703  
   704      // 2) fixture from reward_insert_replace.yaml:
   705      //- code: 64G_android
   706      //  services:
   707      //    flags:
   708      //      UFLAG_PAID_ACCOUNT: true
   709      //      UFLAG_PAID_UPLOAD: true
   710      //    quota: 6.8719476736e+10
   711      //  partner: android
   712      //  extra:
   713      //    prepaid: true
   714      insertFixtureFromYaml, isUsed4 := fixture.GetInsertRewardFixtureByCode(context.TODO(), "primary-code-1", triggerFunc)
   715      replaceFixtureFromYaml, isUsed5 := fixture.GetReplaceRewardFixtureByCode(context.TODO(), "primary-code-1", triggerFunc)
   716      insertOrReplaceFixtureFromYaml, isUsed6 := fixture.GetInsertOrReplaceRewardFixtureByCode(context.TODO(), "primary-code-1", triggerFunc)
   717    
   718      //... run test
   719      
   720      // проверка того что фистура была использована
   721      assertTrue(isUsed1(), "fixture must be applied")
   722  	// ...
   723      assertTrue(isUsed6(), "fixture must be applied")
   724  }
   725  ```
   726  
   727