Skip to content

Commit

Permalink
#22 Custom mutators (#23)
Browse files Browse the repository at this point in the history
* #22 #22

---------

Co-authored-by: e.birukov <[email protected]>
  • Loading branch information
ebirukov and e.birukov authored Sep 15, 2023
1 parent e17c811 commit 8f81db4
Show file tree
Hide file tree
Showing 37 changed files with 1,258 additions and 307 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ LAST_COMMIT_HASH = $(shell git rev-parse HEAD | cut -c -8)
# Таймаут для тестов
TEST_TIMEOUT?=30s
# Тег golang-ci
GOLANGCI_TAG:=1.52.2
GOLANGCI_TAG:=1.54.2
# Путь до бинарников
LOCAL_BIN:=$(CURDIR)/bin
# Путь до бинарника golang-ci
Expand Down
57 changes: 54 additions & 3 deletions docs/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,8 @@ IP адрес или имя хоста, где запущена БД
- `selector` - имя метода-селектора, который нужно создать для соответствующего индекса;
- `serializer` - позволяет навесить дополнительную сериализацию на поле; Формат: `Name[,params]`. Параметры необязательные, но если их указать то они будут переданы в функции `marshal`, `unmarshal`
- `size` - длина поля в байтах (для числовых полей вычисляется автоматически). Используется при десериализации и при прогнозировании потребляемого объёма.
- `mutators` - список методов-модификаторов, которые нужно создать для поля для выполнения спец-операций, допустимо: `inc`, `dec`, `and`, `or`, `xor`, `set_bit`, `clear_bit`;

!Внимание! Поля с сериализацией не попадают в список на обновление при изменении внутреннего состояния десериализованной структуры и как следствие не уходит в БД при Update.
- `mutators` - список методов-модификаторов, которые нужно создать для поля для выполнения спец-операций. Предопределены типы мутаторов: `inc`, `dec`, `and`, `or`, `xor`, `set_bit`, `clear_bit`. Но также возможно описать пользовательский тип мутатора. У каждого поля может быть несколько преопределенных мутаторов, но не более одного пользовательского
!Внимание! По умолчанию поля с сериализацией не попадают в список на обновление при изменении внутреннего состояния десериализованной структуры и как следствие не уходит в БД при Update. Но можно описать подобное поведение с помощью пользовательского типа мутатора

#### Для octopus

Expand Down Expand Up @@ -194,6 +193,58 @@ IP адрес или имя хоста, где запущена БД
- `Printf` - позволяет хранить в БД строку в определённом формате подобном `printf`, обязательно указывать формат в определении поля, см. `serializer` в структуре `Fields`
- `Mapstructure` - позволяет хранить в БД строку и десериализовывать ее в кастомный тип пользователя с возможностями библиотеки mapstructure см. https://pkg.go.dev/github.com/mitchellh/mapstructure

### Mutators*

Объявление пользовательских мутаторов для полей. Когда необходима особая логика модификации полей, которую нельзя реализовать с помощью стандартных операций, например при изменении внутреннего состояния десериализованной структуры
Данная логика модификации реализуется на стороне БД с помощью функций/процедур. Сигнатура вызова процедуры следующая:

- значение первичного ключа
- список значений возвращаемых функцией преобразования (см. ниже) или в случае отсутствии функции преобразования - значение модифицированного поля

При указании мутатора для сериализуемого (составного) поля, для каждого поля сериализуемой структуры генерируется дополнительный метод установки значения.
Для формирования параметров вызова функций/процедур нужно реализовать функцию преобразования. Функция принимает на вход 2 параметра: значение поля до модификации и набор модифицированных значений полей составной структуры
В случае сериализуемого второй второй параметр содержит значения полей сериализуемой структуры внутри `map[string]any`

- `pkg` - Для сериализаемых(составных) полей указывает на пакет в котором находится функция преобразования в параметры. В `octopus` для простых(несериализуемых) полей не требуется.
- `update` - имя функции/процедуры БД которая будет использоваться модификации данных.
- `replace` - имя функции/процедуры БД которая будет использоваться модификации данных. В `octopus` не реализована


Пример описания мутатора для сериализуемого поля
```golang
package ds

type Bar struct {
Name string
}

package model

type FieldsFoo struct {
Bar string `ar:"serializer:Bar;mutators:BarPart;"`
}

type SerializersFoo struct {
Bar *ds.Bar `ar:"pkg:......./model/serializer;object:BarS"`
}

type MutatorsFoo struct {
BarPart bool `ar:"update:updateBar;pkg:......./model/conv;"`
}

package conv

func FooBarPart(bar *ds.Bar, partBar map[string]any) ([]string, error) {
values, err := serializer.Marshal(partBar)
if err != nil {
return nil, err
}

return values
}
```


### Triggers*

Триггеры срабатывают на определённые исключительные случаи при запаковке/распаковке данных и/или при ошибках работы с БД. В каждом конкретном случае в триггер приходят свой набор данных.
Expand Down
5 changes: 4 additions & 1 deletion internal/app/argen.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ func (a *ArGen) addRecordPackage(pkgName string) (*ds.RecordPackage, error) {
return nil, &arerror.ErrParseGenDecl{Name: pkgName, Err: arerror.ErrRedefined}
}

a.packagesParsed[pkgName] = ds.NewRecordPackage()
rp := ds.NewRecordPackage()
a.packagesParsed[pkgName] = rp

return a.packagesParsed[pkgName], nil
}
Expand Down Expand Up @@ -362,6 +363,8 @@ func (a *ArGen) parse() error {
return fmt.Errorf("error model(%s) parse: %s", srcFileName, err)
}

rc.Namespace.ModuleName = a.modName

// Запускаем процесс парсинга
if err := parser.Parse(srcFileName, rc); err != nil {
return fmt.Errorf("error parse declaration: %w", err)
Expand Down
24 changes: 14 additions & 10 deletions internal/app/argen_w_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ func TestArGen_preparePackage(t *testing.T) {
Name: "ID",
Format: octopus.Int,
PrimaryKey: true,
Mutators: []ds.FieldMutator{},
Mutators: []string{},
Size: 0,
Serializer: []string{},
ObjectLink: "",
Expand All @@ -377,7 +377,7 @@ func TestArGen_preparePackage(t *testing.T) {
Name: "BarID",
Format: octopus.Int,
PrimaryKey: false,
Mutators: []ds.FieldMutator{},
Mutators: []string{},
Size: 0,
Serializer: []string{},
ObjectLink: "Bar",
Expand Down Expand Up @@ -407,7 +407,7 @@ func TestArGen_preparePackage(t *testing.T) {
Name: "ID",
Format: octopus.Int,
PrimaryKey: false,
Mutators: []ds.FieldMutator{},
Mutators: []string{},
Size: 0,
Serializer: []string{},
ObjectLink: "",
Expand Down Expand Up @@ -671,11 +671,11 @@ type TriggersFoo struct {
wantErr: false,
want: map[string]*ds.RecordPackage{
"foo": {
Namespace: ds.NamespaceDeclaration{ObjectName: "2", PublicName: "Foo", PackageName: "foo"},
Namespace: ds.NamespaceDeclaration{ObjectName: "2", PublicName: "Foo", PackageName: "foo", ModuleName: "testmod"},
Server: ds.ServerDeclaration{Timeout: 500, Host: "127.0.0.1", Port: "11111"},
Fields: []ds.FieldDeclaration{
{Name: "Field1", Format: "int", PrimaryKey: true, Mutators: []ds.FieldMutator{}, Size: 5, Serializer: []string{}},
{Name: "Field2", Format: "string", PrimaryKey: true, Mutators: []ds.FieldMutator{}, Size: 5, Serializer: []string{}},
{Name: "Field1", Format: "int", PrimaryKey: true, Mutators: []string{}, Size: 5, Serializer: []string{}},
{Name: "Field2", Format: "string", PrimaryKey: true, Mutators: []string{}, Size: 5, Serializer: []string{}},
},
FieldsMap: map[string]int{"Field1": 0, "Field2": 1},
FieldsObjectMap: map[string]ds.FieldObject{},
Expand Down Expand Up @@ -710,14 +710,14 @@ type TriggersFoo struct {
SelectorMap: map[string]int{"SelectByField1": 1, "SelectByField1Field2": 0},
Backends: []string{"octopus"},
SerializerMap: map[string]ds.SerializerDeclaration{},
Imports: []ds.ImportDeclaration{
ImportPackage: ds.ImportPackage{Imports: []ds.ImportDeclaration{
{
Path: "github.com/mailru/activerecord-cookbook.git/example/model/repository/repair",
ImportName: "triggerRepairTuple",
},
},
ImportMap: map[string]int{"github.com/mailru/activerecord-cookbook.git/example/model/repository/repair": 0},
ImportPkgMap: map[string]int{"triggerRepairTuple": 0},
ImportMap: map[string]int{"github.com/mailru/activerecord-cookbook.git/example/model/repository/repair": 0},
ImportPkgMap: map[string]int{"triggerRepairTuple": 0}},
TriggerMap: map[string]ds.TriggerDeclaration{
"RepairTuple": {
Name: "RepairTuple",
Expand All @@ -727,7 +727,11 @@ type TriggersFoo struct {
Params: map[string]bool{"Defaults": true},
},
},
FlagMap: map[string]ds.FlagDeclaration{}},
FlagMap: map[string]ds.FlagDeclaration{},
MutatorMap: map[string]ds.MutatorDeclaration{},
ImportStructFieldsMap: map[string][]ds.PartialFieldDeclaration{},
LinkedStructsMap: map[string]ds.LinkedPackageDeclaration{},
},
},
},
}
Expand Down
23 changes: 23 additions & 0 deletions internal/pkg/arerror/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ var ErrParseFieldBinary = errors.New("binary format not implemented")
var ErrParseFieldMutatorInvalid = errors.New("invalid mutator")
var ErrParseFieldSizeInvalid = errors.New("error parse size")
var ErrParseFieldNameInvalid = errors.New("invalid declaration name")
var ErrParseFieldMutatorTypeHasNotSerializer = errors.New("mutator type must have serializer")

// Описание ошибки парсинга флагов поля сущности
type ErrParseFlagTagDecl struct {
Expand Down Expand Up @@ -204,6 +205,16 @@ func (e *ErrParseSerializerDecl) Error() string {
return ErrorBase(e)
}

// ErrParseMutatorDecl Описание ошибки парсинга мутаторов
type ErrParseMutatorDecl struct {
Name string
Err error
}

func (e *ErrParseMutatorDecl) Error() string {
return ErrorBase(e)
}

// Описание ошибки парсинга тегов сериализатора
type ErrParseSerializerTagDecl struct {
Name string
Expand All @@ -216,6 +227,18 @@ func (e *ErrParseSerializerTagDecl) Error() string {
return ErrorBase(e)
}

// ErrParseMutatorTagDecl Описание ошибки парсинга тегов сериализатора
type ErrParseMutatorTagDecl struct {
Name string
TagName string
TagValue string
Err error
}

func (e *ErrParseMutatorTagDecl) Error() string {
return ErrorBase(e)
}

// Описание ошибки парсинга типов сериализатора
type ErrParseSerializerTypeDecl struct {
Name string
Expand Down
34 changes: 27 additions & 7 deletions internal/pkg/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func checkLinkedObject(cl *ds.RecordPackage, linkedObjects map[string]string) er
}

// checkNamespace проверка правильного описания неймспейса у сущности
func checkNamespace(ns *ds.NamespaceDeclaration) error {
func checkNamespace(ns ds.NamespaceDeclaration) error {
if ns.PackageName == "" || ns.PublicName == "" {
return &arerror.ErrCheckPackageNamespaceDecl{Pkg: ns.PackageName, Name: ns.PublicName, Err: arerror.ErrCheckEmptyNamespace}
}
Expand Down Expand Up @@ -90,18 +90,38 @@ func checkFields(cl *ds.RecordPackage) error {
}

if len(fld.Serializer) > 0 {
if _, ex := cl.SerializerMap[fld.Serializer[0]]; !ex {
if _, ex := cl.SerializerMap[fld.Serializer[0]]; len(cl.SerializerMap) == 0 || !ex {
return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldSerializerNotFound}
}

}

customMutCnt := 0
if len(fld.Mutators) > 0 {
if fld.PrimaryKey {
return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: string(fld.Mutators[0]), Err: arerror.ErrCheckFieldMutatorConflictPK}
fieldMutatorsChecker := ds.GetFieldMutatorsChecker()

for _, m := range fld.Mutators {
_, ex := fieldMutatorsChecker[m]

md, ok := cl.MutatorMap[m]
if ok {
customMutCnt++
if customMutCnt > 1 {
return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: m, Err: arerror.ErrParseFieldMutatorInvalid}
}
}

if !ok && !ex {
return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: m, Err: arerror.ErrParseFieldMutatorInvalid}
}

if len(md.PartialFields) > 0 && len(fld.Serializer) == 0 {
return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: m, Err: arerror.ErrParseFieldMutatorTypeHasNotSerializer}
}
}

if len(fld.Serializer) > 0 {
return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: string(fld.Mutators[0]), Err: arerror.ErrCheckFieldMutatorConflictSerializer}
if fld.PrimaryKey {
return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: string(fld.Mutators[0]), Err: arerror.ErrCheckFieldMutatorConflictPK}
}

if fld.ObjectLink != "" {
Expand Down Expand Up @@ -178,7 +198,7 @@ func Check(files map[string]*ds.RecordPackage, linkedObjects map[string]string)
return err
}

if err := checkNamespace(&cl.Namespace); err != nil {
if err := checkNamespace(cl.Namespace); err != nil {
return err
}

Expand Down
8 changes: 4 additions & 4 deletions internal/pkg/checker/checker_b_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestCheck(t *testing.T) {
Name: "ID",
Format: octopus.Int,
PrimaryKey: true,
Mutators: []ds.FieldMutator{},
Mutators: []string{},
Size: 0,
Serializer: []string{},
ObjectLink: "",
Expand All @@ -32,7 +32,7 @@ func TestCheck(t *testing.T) {
Name: "BarID",
Format: octopus.Int,
PrimaryKey: false,
Mutators: []ds.FieldMutator{},
Mutators: []string{},
Size: 0,
Serializer: []string{},
ObjectLink: "Bar",
Expand Down Expand Up @@ -63,7 +63,7 @@ func TestCheck(t *testing.T) {
Name: "ID",
Format: "byte",
PrimaryKey: true,
Mutators: []ds.FieldMutator{},
Mutators: []string{},
Size: 0,
Serializer: []string{},
ObjectLink: "",
Expand All @@ -82,7 +82,7 @@ func TestCheck(t *testing.T) {
Name: "ID",
Format: "byte",
PrimaryKey: true,
Mutators: []ds.FieldMutator{},
Mutators: []string{},
Size: 0,
Serializer: []string{},
ObjectLink: "",
Expand Down
Loading

0 comments on commit 8f81db4

Please sign in to comment.