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