Skip to content

Commit

Permalink
Merge pull request #8 from mailru/storage-proc
Browse files Browse the repository at this point in the history
Storage proc
  • Loading branch information
Nikolo authored May 2, 2023
2 parents ce4a28f + b55a06f commit 335aa93
Show file tree
Hide file tree
Showing 30 changed files with 1,682 additions and 245 deletions.
32 changes: 31 additions & 1 deletion docs/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ IP адрес или имя хоста, где запущена БД

### namespace

Номер спейса если используется `octopus` (`tarantool 1.5`)
Номер спейса если используется `octopus` (`tarantool 1.5`). При вызове функции/процедуры содержит имя процедуры

### backend

Expand Down Expand Up @@ -154,6 +154,32 @@ IP адрес или имя хоста, где запущена БД
- `fieldnum` - количество используемых полей мульти-колоночного индекса (по умолчанию: 1);
- `selector` - имя метода-селектора, который нужно создать для индекса;

### ProcFields* (описание модели вызова функции/процедуры)

Перечисление входных и выходных параметров сигнатуры функции или хранимой процедуры БД. В тегах у каждого поля возможно указать следующие опции:

- `input` - обязательный для всех входных параметров в сигнатуре вызова;
- `output` - обязательный для всех выходных параметров в сигнатуре; Для БД поддерживающих inout параметры возможно комбинировать с `input`. Формат: `output[:orderNum]`.
Где [orderNum] - необязательный порядковый номер параметра, по умолчанию параметры следуют в порядке перечисления
- `serializer` - позволяет навесить дополнительную сериализацию на поле; Формат: `Name[,params]`. Параметры необязательные, но если их указать то они будут переданы в функции `marshal`, `unmarshal`
- `size` - длина поля в байтах для возможности валидации (в реализации для octopus не используется)

Корректное описание требует как минимум одного поля с опцией `output`.
Порядок, в котором перечисляются поля важен и должен строго соответствовать порядку следования в сигнатуре вызова

#### Для octopus
Порядок, в котором перечисляются поля для значений выходных параметров важен и должен строго соответствовать порядку полей в тупле.

- входные параметры могут быть только строкового типа
- входные параметры для списка строк должны иметь сериализатор в строку
- сериализаторы для входных параметров в методе marshal должны возвращать типы string или []string
- номер поля выходного параметра в тупле вычисляется автоматически на основании порядка объявления полей выходных параметров в модуле;
- если кол-во полей в тупле оказывается меньше чем кол-во перечисленных выходных параметров, то оставшиеся поля останутся пустыми

Для вызова процедур у octopus входные параметры могут быть только строкового типа. Поэтому в качестве типа входного параметра можно указывать только строку (включая []byte)
Но можно объединять входные параметры в структуры используя сериализатор (см. [примеры](https://github.com/mailru/activerecord-cookbook/blob/proc-example/example/model/repository/declaration/foo.go).
Или список при наличии у параметра сериализатора. В этом случае при вызове процедуры будут передаваться строковые параметры в последовательности которую вернет сериализатор

### Serializers*

Объявление дополнительных сериализаторов для полей. Когда не хватает обычных типов и необходимо работать, например, со словарями, то можно объявить сериализатор, который будет применяться для определённого поля. Тип сериализатора переопределяет тип поля внутри объекта. Допустимые параметры в тегах:
Expand Down Expand Up @@ -366,6 +392,7 @@ func NewThreshold(limit uint32) Limiter {
- `insertreplace_packtuple`
- `insertreplace_pack`
- `insertreplace_box`
- `call_proc`
- статистическим
- `insert_success`
- `insertorreplace_success`
Expand Down Expand Up @@ -402,6 +429,8 @@ func NewThreshold(limit uint32) Limiter {
- `update_packpk`
- `update_preparebox`
- `update_resp`
- `call_proc`
- `call_proc_preparebox`

## Пример

Expand Down Expand Up @@ -436,6 +465,7 @@ type (
)
```


### Запуск генератора

`argen --path 'model/repository' --declaration "decl" --destination 'cmpl'`
Expand Down
9 changes: 7 additions & 2 deletions internal/app/argen_b_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ func TestArGen_Run(t *testing.T) {
go 1.19
require (
github.com/mailru/activerecord v0.9.3
github.com/mailru/activerecord v1.5.4
)
replace github.com/mailru/activerecord => ` + srcPath
Expand Down Expand Up @@ -319,7 +319,12 @@ import (
func main() {
ctx := context.Background()
log.Printf("Start")
activerecord.InitActiveRecord()
activerecord.InitActiveRecord(
activerecord.WithConfig(activerecord.NewDefaultConfigFromMap(map[string]interface{}{
"arcfg/master": "127.0.0.1:11111",
"arcfg/replica": "127.0.0.1:11111",
})),
)
` + gr.testGoMain + `
//activerecord.ConnectionCacher().CloseConnection(ctx)
fmt.Print("OK")
Expand Down
8 changes: 5 additions & 3 deletions internal/app/argen_w_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ func TestArGen_preparePackage(t *testing.T) {
rpFoo := ds.NewRecordPackage()
rpFoo.Backends = []string{"octopus"}
rpFoo.Server = ds.ServerDeclaration{Host: "127.0.0.1", Port: "11011"}
rpFoo.Namespace = ds.NamespaceDeclaration{Num: 0, PackageName: "foo", PublicName: "Foo"}
rpFoo.Namespace = ds.NamespaceDeclaration{ObjectName: "0", PackageName: "foo", PublicName: "Foo"}

err := rpFoo.AddField(ds.FieldDeclaration{
Name: "ID",
Expand Down Expand Up @@ -401,7 +401,7 @@ func TestArGen_preparePackage(t *testing.T) {

rpBar := ds.NewRecordPackage()
rpBar.Backends = []string{"octopus"}
rpBar.Namespace = ds.NamespaceDeclaration{Num: 1, PackageName: "bar", PublicName: "Bar"}
rpBar.Namespace = ds.NamespaceDeclaration{ObjectName: "1", PackageName: "bar", PublicName: "Bar"}

err = rpBar.AddField(ds.FieldDeclaration{
Name: "ID",
Expand Down Expand Up @@ -671,14 +671,16 @@ type TriggersFoo struct {
wantErr: false,
want: map[string]*ds.RecordPackage{
"foo": {
Namespace: ds.NamespaceDeclaration{Num: 2, PublicName: "Foo", PackageName: "foo"},
Namespace: ds.NamespaceDeclaration{ObjectName: "2", PublicName: "Foo", PackageName: "foo"},
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{}},
},
FieldsMap: map[string]int{"Field1": 0, "Field2": 1},
FieldsObjectMap: map[string]ds.FieldObject{},
ProcOutFields: map[int]ds.ProcFieldDeclaration{},
ProcFieldsMap: map[string]int{},
Indexes: []ds.IndexDeclaration{
{
Name: "Field1Field2",
Expand Down
5 changes: 5 additions & 0 deletions internal/pkg/arerror/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var ErrCheckBackendUnknown = errors.New("backend unknown")
var ErrCheckEmptyNamespace = errors.New("empty namespace")
var ErrCheckPkgBackendToMatch = errors.New("many backends for one class not supported yet")
var ErrCheckFieldSerializerNotFound = errors.New("serializer not found")
var ErrCheckFieldSerializerNotSupported = errors.New("serializer not supported")
var ErrCheckFieldInvalidFormat = errors.New("invalid format")
var ErrCheckFieldMutatorConflictPK = errors.New("conflict mutators with primary_key")
var ErrCheckFieldMutatorConflictSerializer = errors.New("conflict mutators with serializer")
Expand All @@ -19,6 +20,10 @@ var ErrCheckPortEmpty = errors.New("serverPort is empty")
var ErrCheckServerConflict = errors.New("conflict ServerHost and serverConf params")
var ErrCheckFieldIndexEmpty = errors.New("field for index is empty")
var ErrCheckObjectNotFound = errors.New("linked object not found")
var ErrCheckFieldTypeNotFound = errors.New("procedure field type not found")
var ErrCheckFieldsEmpty = errors.New("empty required field declaration")
var ErrCheckFieldsManyDecl = errors.New("few declarations of fields not supported")
var ErrCheckFieldsOrderDecl = errors.New("incorrect order of fields")

// Описание ошибки декларации пакета
type ErrCheckPackageDecl struct {
Expand Down
2 changes: 2 additions & 0 deletions internal/pkg/arerror/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var ErrIndexNotExist = errors.New("index not exists")
var ErrParseNodeNameUnknown = errors.New("unknown node name")
var ErrParseNodeNameInvalid = errors.New("invalid struct name")
var ErrParseFuncDeclNotSupported = errors.New("func declaration not implemented")
var ErrProcFieldDuplicateOrderIndex = errors.New("field order index is duplicate")

// Описание ошибки парсинга
type ErrParseGenDecl struct {
Expand Down Expand Up @@ -122,6 +123,7 @@ func (e *ErrParseTypeFieldTagDecl) Error() string {
}

var ErrParseFieldArrayOfNotByte = errors.New("support only array of byte")
var ErrParseProcFieldArraySlice = errors.New("support array|slice of byte|string")
var ErrParseFieldArrayNotSlice = errors.New("only array of byte not a slice")
var ErrParseFieldBinary = errors.New("binary format not implemented")
var ErrParseFieldMutatorInvalid = errors.New("invalid mutator")
Expand Down
77 changes: 76 additions & 1 deletion internal/pkg/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package checker

import (
"log"
"strconv"

"github.com/mailru/activerecord/internal/pkg/arerror"
"github.com/mailru/activerecord/internal/pkg/ds"
Expand Down Expand Up @@ -65,7 +66,17 @@ func checkNamespace(ns *ds.NamespaceDeclaration) error {
// - сериализуемые поля не могут быть ссылками на другие сущности
// - есть первичный ключ
// - имена сущностей на которые ссылаемся на могут пересекаться с именами полей
//
//nolint:gocognit,gocyclo
func checkFields(cl *ds.RecordPackage) error {
if len(cl.Fields) > 0 && len(cl.ProcOutFields) > 0 {
return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Err: arerror.ErrCheckFieldsManyDecl}
}

if !cl.ProcOutFields.Validate() {
return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Err: arerror.ErrCheckFieldsOrderDecl}
}

primaryFound := false

octopusAvailFormat := map[octopus.Format]bool{}
Expand Down Expand Up @@ -111,7 +122,48 @@ func checkFields(cl *ds.RecordPackage) error {
}
}

if !primaryFound {
for _, fld := range cl.ProcOutFields.List() {
if _, ex := octopusAvailFormat[fld.Format]; !ex {
return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldInvalidFormat}
}

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

if fld.Type == 0 {
return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldTypeNotFound}
}
}

octopusProcAvailFormat := map[octopus.Format]bool{}
for _, form := range octopus.AllProcFormat {
octopusProcAvailFormat[form] = true
}

for _, fld := range cl.ProcInFields {
if _, ex := octopusProcAvailFormat[fld.Format]; !ex {
return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldInvalidFormat}
}

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

if fld.Type == 0 {
return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldTypeNotFound}
}
}

if len(cl.Fields) == 0 && len(cl.ProcOutFields) == 0 {
return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Err: arerror.ErrCheckFieldsEmpty}
}

if len(cl.Fields) > 0 && !primaryFound {
return &arerror.ErrCheckPackageIndexDecl{Pkg: cl.Namespace.PackageName, Index: "primary", Err: arerror.ErrIndexNotExist}
}

Expand Down Expand Up @@ -154,6 +206,7 @@ func Check(files map[string]*ds.RecordPackage, linkedObjects map[string]string)
return nil
}

//nolint:gocognit,gocyclo
func checkOctopus(cl *ds.RecordPackage) error {
if cl.Server.Host == "" && cl.Server.Conf == "" {
return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Err: arerror.ErrCheckServerEmpty}
Expand All @@ -179,5 +232,27 @@ func checkOctopus(cl *ds.RecordPackage) error {
}
}

if len(cl.Fields) > 0 {
_, err := strconv.ParseInt(cl.Namespace.ObjectName, 10, 64)
if err != nil {
return &arerror.ErrCheckPackageNamespaceDecl{Pkg: cl.Namespace.PackageName, Name: cl.Namespace.ObjectName, Err: arerror.ErrCheckFieldInvalidFormat}
}
}

octopusProcAvailFormat := map[octopus.Format]bool{}
for _, form := range []octopus.Format{octopus.String, octopus.StringArray, octopus.ByteArray} {
octopusProcAvailFormat[form] = true
}

for _, fld := range cl.ProcInFields {
if _, ex := octopusProcAvailFormat[fld.Format]; !ex {
return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldInvalidFormat}
}

if fld.Format != octopus.String && len(fld.Serializer) == 0 {
return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldSerializerNotFound}
}
}

return nil
}
33 changes: 30 additions & 3 deletions internal/pkg/checker/checker_b_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
func TestCheck(t *testing.T) {
rpFoo := ds.NewRecordPackage()
rpFoo.Backends = []string{"octopus"}
rpFoo.Namespace = ds.NamespaceDeclaration{Num: 0, PackageName: "foo", PublicName: "Foo"}
rpFoo.Namespace = ds.NamespaceDeclaration{ObjectName: "0", PackageName: "foo", PublicName: "Foo"}
rpFoo.Server = ds.ServerDeclaration{Host: "127.0.0.1", Port: "11011"}

err := rpFoo.AddField(ds.FieldDeclaration{
Expand Down Expand Up @@ -56,12 +56,31 @@ func TestCheck(t *testing.T) {

rpInvalidFormat := ds.NewRecordPackage()
rpInvalidFormat.Backends = []string{"octopus"}
rpInvalidFormat.Namespace = ds.NamespaceDeclaration{Num: 0, PackageName: "invform", PublicName: "InvalidFormat"}
rpInvalidFormat.Namespace = ds.NamespaceDeclaration{ObjectName: "0", PackageName: "invform", PublicName: "InvalidFormat"}
rpInvalidFormat.Server = ds.ServerDeclaration{Host: "127.0.0.1", Port: "11011"}

err = rpInvalidFormat.AddField(ds.FieldDeclaration{
Name: "ID",
Format: octopus.Format("byte"),
Format: "byte",
PrimaryKey: true,
Mutators: []ds.FieldMutator{},
Size: 0,
Serializer: []string{},
ObjectLink: "",
})
if err != nil {
t.Errorf("can't prepare test data: %s", err)
return
}

onInvalidFormat := ds.NewRecordPackage()
onInvalidFormat.Backends = []string{"octopus"}
onInvalidFormat.Namespace = ds.NamespaceDeclaration{ObjectName: "invalid", PackageName: "invform", PublicName: "InvalidFormat"}
onInvalidFormat.Server = ds.ServerDeclaration{Host: "127.0.0.1", Port: "11011", Conf: "box"}

err = onInvalidFormat.AddField(ds.FieldDeclaration{
Name: "ID",
Format: "byte",
PrimaryKey: true,
Mutators: []ds.FieldMutator{},
Size: 0,
Expand Down Expand Up @@ -106,6 +125,14 @@ func TestCheck(t *testing.T) {
},
wantErr: true,
},
{
name: "wrong octopus namespace objectname format",
args: args{
files: map[string]*ds.RecordPackage{"invalid": onInvalidFormat},
linkedObjects: map[string]string{},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
Loading

0 comments on commit 335aa93

Please sign in to comment.