From 911cdd30193c58bf0a2c19091cc7c16cb5f9c3f2 Mon Sep 17 00:00:00 2001 From: Pavel Shevaev Date: Tue, 15 Nov 2022 15:38:11 +0300 Subject: [PATCH] first commit --- .gitignore | 1 + collection.go | 191 +++++++++++++++++ dbmeta.go | 25 +++ delete.go | 147 +++++++++++++ go.mod | 12 ++ go.sum | 53 +++++ load.go | 165 +++++++++++++++ save.go | 493 +++++++++++++++++++++++++++++++++++++++++++ save_test.go | 570 ++++++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 1657 insertions(+) create mode 100644 .gitignore create mode 100644 collection.go create mode 100644 dbmeta.go create mode 100644 delete.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 load.go create mode 100644 save.go create mode 100644 save_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e92f57 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tags diff --git a/collection.go b/collection.go new file mode 100644 index 0000000..f8a8735 --- /dev/null +++ b/collection.go @@ -0,0 +1,191 @@ +package dbmeta + +import ( + "context" + + "git.bit5.ru/backend/db" + "git.bit5.ru/backend/errors" + "git.bit5.ru/backend/meta" + + "go.opentelemetry.io/otel/attribute" +) + +type DataCollection struct { + Db *db.DBC + OwnerId uint32 + sqlFilter string + Items map[uint64]meta.IMetaDataItem + itemInstance meta.IMetaDataItem + ownerFieldIndex int +} + +func (coll *DataCollection) Checkin(item meta.IMetaDataItem) (bool, error) { + if coll.itemInstance.CLASS_ID() != item.CLASS_ID() { + return false, errors.Errorf("Wrong class element %d for adding to collection %d", item.CLASS_ID(), coll.itemInstance.CLASS_ID()) + } + + id := item.GetIdValue() + //NOTE: it's considered to be OK + if id == 0 { + return false, nil + } + coll.Items[item.GetIdValue()] = item + return true, nil +} + +func (coll *DataCollection) Save(ctx context.Context) error { + + ctx, span := tracer.Start(ctx, "DataCollection.Save") + defer span.End() + + fields := coll.GetDbFields() + countFields := len(fields) + if countFields == 0 { + return errors.New("Fields list is empty") + } + + countRows := len(coll.Items) + if countRows == 0 { + return nil + } + + allDataWithoutMask := true + for _, item := range coll.Items { + bitmaskItem, bitmaskable := item.(meta.IBitmasked) + if bitmaskable && bitmaskItem.IsMaskFilled() { + allDataWithoutMask = false + break + } + } + + // NOTE: performance fix: if none of rows have mask we can save all data by one sql query + if allDataWithoutMask { + return coll.saveWithoutMask(ctx) + } + + return coll.saveByMask(ctx) +} + +func (coll *DataCollection) saveByMask(ctx context.Context) error { + + ctx, span := tracer.Start(ctx, "DataCollection.saveByMask") + defer span.End() + + fields := coll.GetDbFields() + countFields := len(fields) + + tableName := coll.GetTableName() + + span.SetAttributes( + attribute.String("db.sql.table", tableName), + ) + + for _, item := range coll.Items { + data := make([]interface{}, countFields) + item.Export(data) + + // enforcing ownerId + data[coll.ownerFieldIndex] = coll.OwnerId + + itemFields := make([]string, 0, countFields) + itemData := make([]interface{}, 0, countFields) + + bitmaskItem, bitmaskable := item.(meta.IBitmasked) + + for fieldInd, fieldName := range fields { + skipAdding := bitmaskable && bitmaskItem.IsMaskFilled() && !bitmaskItem.HasValue(uint64(fieldInd)) + if skipAdding { + continue + } + + itemFields = append(itemFields, fieldName) + itemData = append(itemData, data[fieldInd]) + } + + sqlSmt := createInsertSQLForFields(ctx, tableName, itemFields, 1) + _, err := coll.Db.UpdateBySQL(sqlSmt, itemData...).Exec() + if err != nil { + log_len := len(sqlSmt) + if log_len > 200 { + log_len = 200 + } + + return errors.Errorf("%s (%s)", err.Error(), sqlSmt[0:log_len]) + } + } + return nil +} + +func (coll *DataCollection) saveWithoutMask(ctx context.Context) error { + + ctx, span := tracer.Start(ctx, "DataCollection.saveWithoutMask") + defer span.End() + + tableName := coll.GetTableName() + + fields := coll.GetDbFields() + countFields := len(fields) + countRows := len(coll.Items) + + span.SetAttributes( + attribute.String("db.sql.table", tableName), + attribute.Int("field_amount", countFields), + attribute.Int("item_amount", countRows), + ) + + offset := 0 + rows := make([]interface{}, countRows*countFields) + for _, item := range coll.Items { + start := offset * countFields + item.Export(rows[start:(start + countFields)]) + + ownerValue := rows[start+coll.ownerFieldIndex] + if ownerValue == uint32(0) { + rows[start+coll.ownerFieldIndex] = coll.OwnerId + } else if ownerValue != coll.OwnerId { + return errors.Errorf("Wrong owner_id in %s value %d (!= %d)", tableName, ownerValue, coll.OwnerId) + } + offset++ + } + + sqlSmt := createInsertSQLForFields(ctx, tableName, fields, countRows) + _, err := coll.Db.UpdateBySQL(sqlSmt, rows...).Exec() + if err != nil { + return errors.Errorf("error: %s, sql: %s", err.Error(), sqlSmt) + } + return nil +} + +func (coll *DataCollection) GetDbFields() []string { + return coll.itemInstance.GetDbFields() +} + +func (coll *DataCollection) GetTableName() string { + return coll.itemInstance.GetDbTableName() +} + +func (coll *DataCollection) init() error { + coll.Items = make(map[uint64]meta.IMetaDataItem) + + coll.ownerFieldIndex = -1 + ownerField := coll.itemInstance.GetOwnerFieldName() + for index, field := range coll.itemInstance.GetDbFields() { + if field == ownerField { + coll.ownerFieldIndex = index + break + } + } + if coll.ownerFieldIndex == -1 { + return errors.New("Owner field not found in fields list") + } + + return nil +} + +func NewDataCollection(db *db.DBC, ownerId uint32, sqlFilter string, itemInstance meta.IMetaDataItem) (*DataCollection, error) { + data := DataCollection{Db: db, OwnerId: ownerId, sqlFilter: sqlFilter, itemInstance: itemInstance} + if err := data.init(); err != nil { + return nil, err + } + return &data, nil +} diff --git a/dbmeta.go b/dbmeta.go new file mode 100644 index 0000000..3578192 --- /dev/null +++ b/dbmeta.go @@ -0,0 +1,25 @@ +package dbmeta + +import ( + "context" + "fmt" + "strings" +) + +func createInsertSQLForFields(ctx context.Context, table string, fields []string, rowsAmount int) string { + fieldsStr := strings.Join(fields, "`,`") + valuesStr := "(?" + strings.Repeat(", ?", len(fields)-1) + ")," + + sql := fmt.Sprintf("INSERT INTO `%s` (`%s`) VALUES ", table, fieldsStr) + sql += strings.TrimRight(valuesStr+strings.Repeat(valuesStr, rowsAmount-1), ",") + sql += " ON DUPLICATE KEY UPDATE " + + for i, field := range fields { + if i > 0 { + sql += "," + } + sql += fmt.Sprintf("`%s`=VALUES(`%s`)", field, field) + } + + return sql +} diff --git a/delete.go b/delete.go new file mode 100644 index 0000000..1b10228 --- /dev/null +++ b/delete.go @@ -0,0 +1,147 @@ +package dbmeta + +import ( + "reflect" + + "git.bit5.ru/backend/db" + "git.bit5.ru/backend/errors" + "git.bit5.ru/backend/meta" +) + +func DeleteMetaStruct(db *db.DBC, metaStruct interface{}, ownerId uint32) error { + metaStructValue, metaStructType, err := getValueAndType(metaStruct) + if err != nil { + return err + } + fieldsProps, err := getMetastructFieldProps(metaStructValue) + if err != nil { + return err + } + + // Iterate through struct fields to find field type struct or slice + for i := 0; i < metaStructType.NumField(); i++ { + field := metaStructType.Field(i) + + if fieldsProps != nil { + if props, ok := fieldsProps[field.Name]; ok { + if _, ok := props["db_skip_save"]; ok { + continue + } + } + } + + fieldType := field.Type + fieldValue := metaStructValue.Field(i) + + // skip unexported fields + if len(field.PkgPath) > 0 { + continue + } + + switch fieldType.Kind() { + case reflect.Slice: + // Try to get reflect.Value of slice underlying element + sliceElement := getSliceElementValue(fieldValue) + if err := DeleteMetaStruct(db, sliceElement.Addr().Interface(), ownerId); err != nil { + return err + } + + case reflect.Struct: + if err := DeleteMetaStruct(db, fieldValue.Addr().Interface(), ownerId); err != nil { + return err + } + break + case reflect.UnsafePointer: + // Now we don't use these types in meta. If we will - fix it. + return errors.Errorf("I don't know what I should do with it: %s", fieldType.Kind()) + case reflect.Map, reflect.Ptr, reflect.Array: + default: + break + } + } + + if !isMetaDataItem(metaStructValue) { + return nil + } + + table, ownerColumn, err := getTableAndOwnerColumn(metaStructValue) + + if err != nil { + return err + } + + err = deleteFromTable(db, table, ownerColumn, ownerId) + + return err +} + +func isMetaDataItem(metaStructValue reflect.Value) bool { + if !metaStructValue.CanInterface() { + return false + } + _, ok := metaStructValue.Addr().Interface().(meta.IMetaDataItem) + return ok +} + +// Get value and type of MetaStruct. Only pointer is allowed. +func getValueAndType(metaStruct interface{}) (reflect.Value, reflect.Type, error) { + metaStructValue := reflect.ValueOf(metaStruct) + metaStructType := metaStructValue.Type() + + if metaStructType.Kind() != reflect.Ptr { + return metaStructValue, metaStructType, errors.New("Value must be a pointer to a struct") + } + metaStructValue = metaStructValue.Elem() + metaStructType = metaStructType.Elem() + + return metaStructValue, metaStructType, nil +} + +// Get fieldsProps from metastruct +func getMetastructFieldProps(metaStructValue reflect.Value) (map[string]map[string]string, error) { + fieldsProps := make(map[string]map[string]string) + + // Check if value is addressable, because meta.IClassProps' method CLASS_FIELDS_PROPS() has a pointer reciever + if !metaStructValue.CanAddr() { + return fieldsProps, errors.New("value is not addressable!") + } + + if !metaStructValue.CanInterface() { + return fieldsProps, errors.New("Interface cannot used!") + } + + iMetaClassFieldProps, ok := metaStructValue.Addr().Interface().(meta.IClassProps) + if !ok { + return fieldsProps, errors.New("value is not meta.IClassProps interface!") + } + fieldsProps = *iMetaClassFieldProps.CLASS_FIELDS_PROPS() + + return fieldsProps, nil +} + +// Returns slice element value to get table data +func getSliceElementValue(slice reflect.Value) reflect.Value { + // Slice Kind here is a pointer. + // First call of Elem() returns type from pointer(?). + // Second returns type of slice element + sliceElementType := slice.Type().Elem().Elem() + // Call Elem() on new value, because type of element type is pointer(?) + sliceElementValue := reflect.New(sliceElementType).Elem() + + return sliceElementValue +} + +func getTableAndOwnerColumn(metaStructValue reflect.Value) (string, string, error) { + + iDataItem, ok := metaStructValue.Addr().Interface().(meta.IMetaDataItem) + if !ok { + return "", "", errors.New("Can't convert to interface meta.IMetaDataItem!") + } + + return iDataItem.GetDbTableName(), iDataItem.GetOwnerFieldName(), nil +} + +func deleteFromTable(db *db.DBC, table string, ownerColumnName string, ownerId uint32) error { + _, err := db.DeleteFrom(table).Where(ownerColumnName+" = ?", ownerId).Exec() + return err +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5714a03 --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module git.bit5.ru/backend/dbmeta + +go 1.13 + +require ( + git.bit5.ru/backend/db v1.2.3 + git.bit5.ru/backend/errors v1.0.0 + git.bit5.ru/backend/meta v1.0.0 + github.com/go-logr/stdr v1.2.2 + github.com/stretchr/testify v1.8.1 + go.opentelemetry.io/otel v1.11.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..36c5d17 --- /dev/null +++ b/go.sum @@ -0,0 +1,53 @@ +git.bit5.ru/backend/db v1.2.3 h1:FYGUbu7nYMny9CoTU6oLrJuQdn+xG42cAHkWI6JLGgE= +git.bit5.ru/backend/db v1.2.3/go.mod h1:2PB9QennBZaH8u5i3IvnuVaWnAK5Ck5kCrJ9+5ZnQp8= +git.bit5.ru/backend/dbr v1.2.0 h1:SQndOC9KW/jPGRSFSPYRqmMd+EHEyZHWDO9B+imEEzU= +git.bit5.ru/backend/dbr v1.2.0/go.mod h1:3iCyHEkyj2M+lx3fSlv3lC1r5cLZZz+6QLVCml7ZAy8= +git.bit5.ru/backend/errors v1.0.0 h1:WWJ0sly44q1HQjN01X75ZAGKZwwY5Ml+XVDXMjCkToA= +git.bit5.ru/backend/errors v1.0.0/go.mod h1:75faRwsnpM0Se00/Bh7fysWQXV8oMjNJFQ6f7+r9k3Y= +git.bit5.ru/backend/meta v1.0.0 h1:1mZgEoOSA/P+IrnKkoiULpFUFX3JxyxGU6OXVn7j2kY= +git.bit5.ru/backend/meta v1.0.0/go.mod h1:g45aleNyhUzd1ieXDZV8GDTxCg2B4FKH+0f3DRc3H+w= +git.bit5.ru/backend/msgpack v1.0.0 h1:D7sFCFjSN1ADUaESjrRVIWY9TGVATq5i08eKn0ep6ZE= +git.bit5.ru/backend/msgpack v1.0.0/go.mod h1:Syf8E+3pr9z3TropB/eN4PJUekRg5ZD/0sHydHH17r0= +git.bit5.ru/backend/mysql v1.0.0 h1:NKxTr5p7vti/qrig6zZtWaDeiLRRgPkMB0Nz7AGGlIY= +git.bit5.ru/backend/mysql v1.0.0/go.mod h1:iK3dzIpv9YAGN8Fy6zi1XGUX8R5nl9XQ9NJbwYM7y0w= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.3/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/vmihailenco/bufio v0.0.0-20140618134113-fe7b595919de h1:U+I4zEVstMdfNES/2UO8iqkIf214SDMRhdaFTE3A5rA= +github.com/vmihailenco/bufio v0.0.0-20140618134113-fe7b595919de/go.mod h1:ghSGoeEoFFkXNguSget72dMA0+OLq3AGZiqRohVojxI= +go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4= +go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE= +go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= +go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= +gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e h1:wGA78yza6bu/mWcc4QfBuIEHEtc06xdiU0X8sY36yUU= +gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e/go.mod h1:xsQCaysVCudhrYTfzYWe577fCe7Ceci+6qjO2Rdc0Z4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +launchpad.net/gocheck v0.0.0-20140225173054-000000000087 h1:Izowp2XBH6Ya6rv+hqbceQyw/gSGoXfH/UPoTGduL54= +launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= diff --git a/load.go b/load.go new file mode 100644 index 0000000..e322a71 --- /dev/null +++ b/load.go @@ -0,0 +1,165 @@ +package dbmeta + +import ( + "fmt" + "reflect" + "strings" + + "git.bit5.ru/backend/db" + "git.bit5.ru/backend/errors" + "git.bit5.ru/backend/meta" +) + +func LoadMetaStruct(db *db.DBC, data meta.IMetaStruct, ownerId uint32) error { + return loadStruct(db, data, ownerId) +} + +func loadStruct(db *db.DBC, itemInter interface{}, ownerId uint32) error { + refItem := reflect.ValueOf(itemInter) + mType := refItem.Type() + + var fprops map[string]map[string]string + if refItem.CanInterface() { + imeta, _ := refItem.Interface().(meta.IClassProps) + if imeta != nil { + fprops = *imeta.CLASS_FIELDS_PROPS() + } + } + + if mType.Kind() == reflect.Ptr { + refItem = refItem.Elem() + mType = mType.Elem() + } + + for i := 0; i < mType.NumField(); i++ { + field := mType.Field(i) + tfield := field.Type + if fprops != nil { + if props, ok := fprops[field.Name]; ok { + if _, ok := props["db_skip_load"]; ok { + continue + } + } + } + // skip unexported fields + if len(field.PkgPath) > 0 { + continue + } + + switch tfield.Kind() { + case reflect.Slice: + if err := loadMetaCollection(db, refItem.Field(i).Addr().Interface(), ownerId); err != nil { + return err + } + break + + case reflect.Struct: + if err := loadStruct(db, refItem.Field(i).Addr().Interface(), ownerId); err != nil { + return err + } + break + case reflect.Map: + case reflect.Ptr: + case reflect.Array: + case reflect.UnsafePointer: + // Now we don't use these types in meta. If we will - fix it. + return errors.Errorf("I don't now what I should do with it: %s", tfield.Kind()) + default: + break + } + } + + var row meta.IMetaStruct + if refItem.CanInterface() { + row = refItem.Addr().Interface().(meta.IMetaStruct) + } + if row == nil { + return errors.Errorf("Couldn't convert to IdataRow: %s", mType.Name()) + } + + props := *row.CLASS_PROPS() + if _, ok := props["POD"]; !ok { + // skip struct + return nil + } + + if ownerField, ok := props["owner"]; ok { + field := refItem.FieldByName(strings.Title(ownerField)) + if !field.IsValid() { + return errors.Errorf("Owner field \"%s\" is not found in struct \"%s\"", ownerField, mType.Name()) + } + field.Set(reflect.ValueOf(ownerId)) + } + + if pkeyField, ok := props["pkey"]; ok { + field := refItem.FieldByName(strings.Title(pkeyField)) + if !field.IsValid() { + return errors.Errorf("Pkey field \"%s\" is not found in struct \"%s\"", pkeyField, mType.Name()) + } + err := FindOne(db, row, fmt.Sprintf("WHERE `%s`=?", pkeyField), []interface{}{field.Interface()}) + if err != nil { + return err + } + refItem.Set(reflect.ValueOf(row).Elem()) + } else { + return errors.Errorf("Struct \"%s\" doesn't have pkey field", mType.Name()) + } + return nil +} + +func LoadMetaMStruct(db *db.DBC, data interface{}, ownerId uint32) error { + return loadMetaCollection(db, data, ownerId) +} + +func loadMetaCollection(db *db.DBC, dataItem interface{}, ownerId uint32) error { + var err error + + refSlice := reflect.ValueOf(dataItem) + if refSlice.Kind() == reflect.Ptr { + refSlice = refSlice.Elem() + } + if refSlice.Kind() != reflect.Slice { + return errors.Errorf("It isn't slice: %s", refSlice.Kind()) + } + + sliceItem := reflect.New(refSlice.Type().Elem().Elem()) + item := sliceItem.Interface().(meta.IMetaDataItem) + if item == nil { + return errors.Errorf("Couldn't convert to IMetaDataItem: %s", refSlice.Type().Elem()) + } + + ownerFieldIndex := -1 + ownerField := item.GetOwnerFieldName() + for index, field := range item.GetDbFields() { + if field == ownerField { + ownerFieldIndex = index + break + } + } + if ownerFieldIndex == -1 { + return errors.New("Owner field not found in fields list") + } + + sql := "SELECT `" + strings.Join(item.GetDbFields(), "`, `") + "` FROM " + "`" + item.GetDbTableName() + "`" + + " WHERE `" + ownerField + "`=" + fmt.Sprintf("%d", ownerId) + + _, err = db.SelectBySQL(sql).LoadStructs(refSlice.Addr().Interface()) + if err != nil { + return err + } + return nil +} + +func FindOne(dbc *db.DBC, dataItem meta.IMetaStruct, appendSQL string, params []interface{}) error { + info, err := makeDataRowInfo(dataItem) + if err != nil { + return err + } + sql := "SELECT `" + strings.Join(info.fields, "`, `") + "` FROM `" + info.tableName + "` " + appendSQL + err = dbc.SelectBySQL(sql, params...).LoadStruct(dataItem) + + if db.IsNotFoundError(err) { + return nil + } + return err +} diff --git a/save.go b/save.go new file mode 100644 index 0000000..751880d --- /dev/null +++ b/save.go @@ -0,0 +1,493 @@ +package dbmeta + +import ( + "context" + "fmt" + "reflect" + "strings" + + "git.bit5.ru/backend/db" + "git.bit5.ru/backend/errors" + "git.bit5.ru/backend/meta" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.12.0" +) + +const tracerName = "game/dbmeta" + +var tracer = otel.Tracer(tracerName) + +type SaveIncompatibleError struct { + s string +} + +func (e *SaveIncompatibleError) Error() string { + return e.s +} + +type dataRowInfo struct { + mType reflect.Type + data meta.IMetaStruct + pkey string + tableName string + fields []string + mapFields map[int]int +} + +type RemovedIds struct { + //class_id => []ids + ids map[uint32][]uint64 +} + +func SaveRow(ctx context.Context, db *db.DBC, dataItem meta.IMetaStruct) error { + + ctx, span := tracer.Start(ctx, "SaveRow") + defer span.End() + + info, err := makeDataRowInfo(dataItem) + if err != nil { + return err + } + + fields := make([]string, 0, len(info.fields)) + params := make([]interface{}, 0, len(info.fields)) + elem := reflect.ValueOf(dataItem).Elem() + + var pkey *reflect.Value + + bitmaskItem, bitmaskable := dataItem.(meta.IBitmasked) + + for fieldIdx, fieldNum := range info.mapFields { + value := elem.Field(fieldNum) + if info.fields[fieldIdx] == info.pkey && isEmptyField(value) { + pkey = &value + continue + } + + skipAdding := bitmaskable && bitmaskItem.IsMaskFilled() && !bitmaskItem.HasValue(uint64(fieldIdx)) + if skipAdding { + continue + } + + fields = append(fields, info.fields[fieldIdx]) + params = append(params, value.Interface()) + } + + if len(fields) == 0 { + return errors.New("Fields list is empty") + } + + tableName := info.tableName + sqlSmt := createInsertSQLForFields(ctx, tableName, fields, 1 /*one_row*/) + + span.SetAttributes( + semconv.DBSQLTableKey.String(tableName), + semconv.DBStatementKey.String(sqlSmt), + ) + + updateBuilder := db.UpdateBySQL(sqlSmt, params...) + + res, err := updateBuilder.Exec() + if err != nil { + if len(sqlSmt) > 200 { + sqlSmt = sqlSmt[0:200] + } + return errors.Errorf("%s (%s)", err.Error(), sqlSmt) + } + + insertId, _ := res.LastInsertId() + if pkey != nil && insertId != 0 { + pkey.Set(reflect.ValueOf(uint32(insertId)).Convert(pkey.Type())) + } + + return nil +} + +func SaveMetaRootStruct( + ctx context.Context, + db *db.DBC, + data meta.IMetaStruct, + ownerId uint32, + removedIds meta.IRemovedIds, + deltaSave bool, +) error { + + ctx, span := tracer.Start(ctx, "SaveMetaRootStruct") + defer span.End() + + dataItem := reflect.ValueOf(data) + mType := dataItem.Type() + if mType.Kind() == reflect.Ptr { + dataItem = dataItem.Elem() + mType = mType.Elem() + } + + var fprops map[string]map[string]string + if dataItem.Addr().CanInterface() { + imeta, _ := dataItem.Addr().Interface().(meta.IClassProps) + if imeta != nil { + fprops = *imeta.CLASS_FIELDS_PROPS() + } + } + + bitmaskItem, bitmaskable := data.(meta.IBitmasked) + + numField := mType.NumField() + + span.SetAttributes( + attribute.String("mType.name", mType.Name()), + attribute.Int("mType.numField", numField), + ) + + for i := 0; i < numField; i++ { + skipAdding := bitmaskable && bitmaskItem.IsMaskFilled() && !bitmaskItem.HasValue(uint64(i)) + if skipAdding { + continue + } + + field := mType.Field(i) + tfield := field.Type + // Checking if this field should be skipped + if fprops != nil { + if props, ok := fprops[field.Name]; ok { + if _, ok := props["db_skip_save"]; ok { + continue + } + } + } + // skip unexported fields + if len(field.PkgPath) > 0 { + continue + } + switch tfield.Kind() { + case reflect.Slice: + if err := SaveMetaCollection(ctx, db, dataItem.Field(i), ownerId, removedIds, deltaSave); err != nil { + return errors.WithMessagef(err, "Can not execute SaveMetaRootStruct. Got error from SaveMetaCollection. ownerId: %d.", ownerId) + } + break + case reflect.Struct: + if err := saveStruct(ctx, db, dataItem.Field(i), ownerId); err != nil { + return errors.WithMessagef(err, "Can not execute SaveMetaRootStruct. Got error from saveStruct. ownerId: %d.", ownerId) + } + break + case reflect.Map: + case reflect.Ptr: + case reflect.Array: + case reflect.UnsafePointer: + return errors.Errorf("Unsupported type: %s", tfield.Kind()) + default: + break + } + } + + return nil +} + +func saveStruct(ctx context.Context, db *db.DBC, dataItem reflect.Value, ownerId uint32) error { + + ctx, span := tracer.Start(ctx, "saveStruct") + defer span.End() + + mType := dataItem.Type() + if mType.Kind() == reflect.Ptr { + dataItem = dataItem.Elem() + mType = mType.Elem() + } + + var row meta.IMetaStruct + if dataItem.Addr().CanInterface() { + row, _ = dataItem.Addr().Interface().(meta.IMetaStruct) + } + if row == nil { + return errors.Errorf("Couldn't convert to IMetaStruct: %s", mType.Name()) + } + + props := *row.CLASS_PROPS() + if ownerField, ok := props["owner"]; ok { + field := dataItem.FieldByName(strings.Title(ownerField)) + if !field.IsValid() { + return errors.Errorf("Owner field \"%s\" is not found in struct \"%s\"", ownerField, mType.Name()) + } + + // enforcing ownerId + if field.CanSet() { + field.SetUint(uint64(ownerId)) + } + + } + + return SaveRow(ctx, db, row) +} + +func SaveMetaCollection(ctx context.Context, db *db.DBC, slice reflect.Value, ownerId uint32, removedIds meta.IRemovedIds, deltaSave bool) error { + + ctx, span := tracer.Start(ctx, "SaveMetaCollection") + defer span.End() + + if slice.Type().Kind() != reflect.Slice { + return errors.Errorf("It isn't slice: %s", slice.Type().Kind()) + } + sliceItem := reflect.New(slice.Type().Elem().Elem()) + if sliceItem.Type().Kind() != reflect.Ptr { + sliceItem = sliceItem.Addr() + } + + var row meta.IMetaStruct + if sliceItem.CanInterface() { + row, _ = sliceItem.Interface().(meta.IMetaStruct) + } + + if row == nil { + return errors.Errorf("Couldn't convert to IMetaStruct: %s", sliceItem.Kind()) + } + + item, _ := sliceItem.Interface().(meta.IMetaDataItem) + if item == nil { + return errors.Errorf("Couldn't convert to IMetaDataItem: %s", slice.Type()) + } + + if !deltaSave { + cond := fmt.Sprintf("`%s`=%d ", item.GetOwnerFieldName(), ownerId) + _, err := db.DeleteFrom(item.GetDbTableName()).Where(cond).Exec() + if err != nil { + return err + } + } + + collection, err := NewDataCollection(db, ownerId, "", item) + if err != nil { + return err + } + for ind := 0; ind < slice.Len(); ind++ { + ind_item, err := convertToIdataItem(slice.Index(ind)) + if err != nil { + return err + } + // NOTE: we don't check here for error on purpose, here it's considered to be OK + collection.Checkin(ind_item) + } + + if err := collection.Save(ctx); err != nil { + return err + } + + if removedIds != nil { + classId := item.CLASS_ID() + + if removedIds.HasList(classId) { + err := deleteByIds(db, ownerId, item, removedIds.GetList(classId)) + if err != nil { + return err + } + } + } + return nil +} + +func deleteByIds(db *db.DBC, ownerId uint32, item meta.IMetaDataItem, removedIds []uint64) error { + var cond string + + if item.GetOwnerFieldName() == item.GetIdFieldName() { + cond = fmt.Sprintf("`%s`=%d ", item.GetOwnerFieldName(), ownerId) + } else { + if len(removedIds) == 0 { + return nil + } + ids := make([]string, 0, len(removedIds)) + for _, id := range removedIds { + ids = append(ids, fmt.Sprintf("%d", id)) + } + cond = fmt.Sprintf( + "`%s`=%d AND `%s` IN(%s)", + item.GetOwnerFieldName(), + ownerId, + item.GetIdFieldName(), + strings.Join(ids, ",")) + } + + _, err := db.DeleteFrom(item.GetDbTableName()).Where(cond).Exec() + return err +} + +func convertToIdataItem(elem reflect.Value) (meta.IMetaDataItem, error) { + mType := elem.Type() + if mType.Kind() != reflect.Ptr { + elem = elem.Addr() + mType = elem.Type() + } + item, _ := elem.Interface().(meta.IMetaDataItem) + if item == nil { + return nil, errors.Errorf("Couldn't convert to IMetaDataItem: %s", mType.Name()) + } + return item, nil +} + +func convertToIdataRow(elem reflect.Value) (meta.IMetaStruct, error) { + mType := elem.Type() + if mType.Kind() != reflect.Ptr { + elem = elem.Addr() + mType = elem.Type() + } + item, _ := elem.Interface().(meta.IMetaStruct) + if item == nil { + return nil, errors.Errorf("Couldn't convert to IdataRow: %s", mType.Name()) + } + return item, nil +} + +func makeDataRowInfo(data meta.IMetaStruct) (*dataRowInfo, error) { + info := new(dataRowInfo) + info.mType = reflect.TypeOf(data).Elem() + props := *data.CLASS_PROPS() + tableName, ok := props["table"] + info.tableName = tableName + if !ok || len(info.tableName) == 0 { + return nil, errors.Errorf("Prop 'table' is empty (%s)", info.mType.Name()) + } + pkey, ok := props["pkey"] + info.pkey = "" + if ok { + info.pkey = pkey + } + info.fields = data.CLASS_FIELDS() + info.mapFields = make(map[int]int, len(info.fields)) + for ind, field := range info.fields { + found := false + for i := 0; i < info.mType.NumField(); i++ { + if strings.Title(field) == info.mType.Field(i).Name { + found = true + info.mapFields[ind] = i + } + } + if !found { + return nil, errors.Errorf("Field %s[%d] not found in struct %s", field, ind, info.mType.Name()) + } + } + return info, nil +} + +func isEmptyField(v reflect.Value) bool { + vint := reflect.ValueOf(int64(0)) + vstr := reflect.ValueOf("") + if v.Type().ConvertibleTo(vint.Type()) { + return reflect.DeepEqual(v.Convert(vint.Type()).Interface(), vint.Interface()) + } + return reflect.DeepEqual(v.Interface(), vstr.Interface()) +} + +func (rids *RemovedIds) init() { + if rids.ids == nil { + rids.ids = make(map[uint32][]uint64) + } +} + +func (rids *RemovedIds) GetList(classId uint32) []uint64 { + rids.init() + if list, ok := rids.ids[classId]; ok { + return list + } + return []uint64{} +} + +func (rids *RemovedIds) Add(classId uint32, id uint64) { + rids.init() + if _, ok := rids.ids[classId]; !ok { + rids.ids[classId] = []uint64{} + } + rids.ids[classId] = append(rids.ids[classId], id) +} + +func (rids *RemovedIds) HasList(classId uint32) bool { + rids.init() + _, ok := rids.ids[classId] + return ok +} + +func GetChangedRootStructFields(data meta.IMetaStruct, fieldNames []string) map[string]reflect.Value { + var fields = make(map[string]reflect.Value) + + dataItem := reflect.ValueOf(data) + mType := dataItem.Type() + if mType.Kind() == reflect.Ptr { + dataItem = dataItem.Elem() + mType = mType.Elem() + } + + bitmaskItem, bitmaskable := data.(meta.IBitmasked) + + for i := 0; i < mType.NumField(); i++ { + fieldName := mType.Field(i).Name + + if !sliceOfStringsContains(fieldNames, fieldName) { + continue + } + + skipAdding := bitmaskable && bitmaskItem.IsMaskFilled() && !bitmaskItem.HasValue(uint64(i)) + if skipAdding { + continue + } + + fields[fieldName] = dataItem.Field(i) + } + + return fields +} + +func sliceOfStringsContains(slice []string, v string) bool { + for _, item := range slice { + if item == v { + return true + } + } + return false +} + +func GetChangedStructFields(dataItem reflect.Value, fieldNamesFilter func(string) bool) (map[string]reflect.Value, error) { + var fields = make(map[string]reflect.Value) + + mType := dataItem.Type() + if mType.Kind() == reflect.Ptr { + dataItem = dataItem.Elem() + mType = mType.Elem() + } + + var row meta.IMetaStruct + if dataItem.Addr().CanInterface() { + row, _ = dataItem.Addr().Interface().(meta.IMetaStruct) + } + if row == nil { + return nil, errors.Errorf("Couldn't convert to IMetaStruct: %s", mType.Name()) + } + + info, err := makeDataRowInfo(row) + if err != nil { + return nil, err + } + + elem := reflect.ValueOf(row).Elem() + bitmaskItem, bitmaskable := row.(meta.IBitmasked) + + for fieldIdx, fieldNum := range info.mapFields { + fieldName := info.fields[fieldIdx] + if fieldNamesFilter != nil && !fieldNamesFilter(fieldName) { + continue + } + + value := elem.Field(fieldNum) + + if fieldName == info.pkey && isEmptyField(value) { + continue + } + + skipAdding := bitmaskable && bitmaskItem.IsMaskFilled() && !bitmaskItem.HasValue(uint64(fieldIdx)) + if skipAdding { + continue + } + + fields[fieldName] = value + } + + return fields, nil +} diff --git a/save_test.go b/save_test.go new file mode 100644 index 0000000..eca206a --- /dev/null +++ b/save_test.go @@ -0,0 +1,570 @@ +package dbmeta_test + +import ( + "context" + "log" + "os" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "git.bit5.ru/backend/dbmeta" + "git.bit5.ru/backend/db" + "git.bit5.ru/backend/meta" + + "github.com/go-logr/stdr" +) + +var logger = stdr.New(log.New(os.Stdout, "", log.Lshortfile)) + +//TODO create the tables below before tests +//CREATE TABLE `player` ( +// `id` int unsigned NOT NULL, +// `client_version` char(16) NOT NULL DEFAULT '', +// `reg_time` int unsigned NOT NULL DEFAULT '0', +// `name` varchar(255) NOT NULL DEFAULT '', +// `utc_delta` tinyint(1) NOT NULL DEFAULT '0', +// PRIMARY KEY (`id`) +//) ENGINE=InnoDB DEFAULT +// +// CREATE TABLE `item` ( +// `player_id` int unsigned NOT NULL DEFAULT '0', +// `id` int unsigned NOT NULL DEFAULT '0', +// `proto_id` int unsigned NOT NULL DEFAULT '0', +// `amount` bigint NOT NULL DEFAULT '0', +// PRIMARY KEY (`player_id`,`id`), +// KEY `proto_id` (`proto_id`) +//) ENGINE=InnoDB DEFAULT + +func getDBC() *db.DBC { + s := db.Settings{Host: "127.0.0.1", Port: "3306", User: "root", Pass: "test", Name: "tests_shard_1", Prefix: "tests"} + dbc := db.GetDBC(db.OpenPool(s), logger) + return dbc +} + +func cleanStorage(db *db.DBC) { + var tables []string + db.SelectBySQL("SHOW TABLES").LoadValues(&tables) + for _, t := range tables { + _, err := db.DeleteFrom(t).Exec() + if err != nil { + panic(err) + } + } +} + +func TestSaveRow(t *testing.T) { + ctx := context.TODO() + + conn := getDBC() + cleanStorage(conn) + + //DataPlayer index + //id 0 + //client_version 1 + //reg_time 2 + //utc_delta 3 + + player := &DataPlayer{Id: 100, Client_version: "0.0.1", Reg_time: 100, Utc_delta: 1} + player.SetFieldChanged(0) + player.SetFieldChanged(2) + require.NoError(t, dbmeta.SaveRow(ctx, conn, player)) + + var item DataPlayer + require.NoError(t, dbmeta.LoadMetaStruct(conn, &item, player.Id)) + + assert.EqualValues(t, "", item.Client_version) + assert.EqualValues(t, 0, item.Utc_delta) + assert.EqualValues(t, 100, item.Reg_time) +} + +func TestSaveItemCollectionWithMask(t *testing.T) { + ctx := context.TODO() + + conn := getDBC() + cleanStorage(conn) + + ownerId := uint32(1) + + //DataItem index + //id 0 + //player_id 1 + //proto_id 2 + //amount 3 + + item1 := &DataItem{Player_id: ownerId, Id: 10, Proto_id: 100, Amount: 1} + item1.SetFieldChanged(0) + item1.SetFieldChanged(1) + item1.SetFieldChanged(2) + + item2 := &DataItem{Player_id: ownerId, Id: 11, Proto_id: 200, Amount: 2} + item2.SetFieldChanged(0) + item2.SetFieldChanged(1) + item2.SetFieldChanged(3) + + var items []*DataItem + items = append(items, item1) + items = append(items, item2) + + require.NoError(t, dbmeta.SaveMetaCollection(ctx, conn, reflect.ValueOf(items), ownerId, nil, false)) + + items = nil + require.NoError(t, dbmeta.LoadMetaMStruct(conn, &items, ownerId)) + + assert.EqualValues(t, items[0].Player_id, ownerId) + assert.EqualValues(t, items[0].Id, 10) + assert.EqualValues(t, items[0].Proto_id, 100) + assert.EqualValues(t, items[0].Amount, 0) + + assert.EqualValues(t, items[1].Player_id, ownerId) + assert.EqualValues(t, items[1].Id, 11) + assert.EqualValues(t, items[1].Proto_id, 0) + assert.EqualValues(t, items[1].Amount, 2) +} + +func TestSaveItemCollectionWithoutMask(t *testing.T) { + ctx := context.TODO() + + conn := getDBC() + cleanStorage(conn) + + ownerId := uint32(1) + + item1 := &DataItem{Player_id: ownerId, Id: 10, Proto_id: 100, Amount: 1} + item2 := &DataItem{Player_id: ownerId, Id: 11, Proto_id: 200, Amount: 2} + + var items []*DataItem + items = append(items, item1) + items = append(items, item2) + + require.NoError(t, dbmeta.SaveMetaCollection(ctx, conn, reflect.ValueOf(items), ownerId, nil, false)) + + items = nil + require.NoError(t, dbmeta.LoadMetaMStruct(conn, &items, ownerId)) + + assert.EqualValues(t, items[0].Player_id, ownerId) + assert.EqualValues(t, items[0].Id, 10) + assert.EqualValues(t, items[0].Proto_id, 100) + assert.EqualValues(t, items[0].Amount, 1) + + assert.EqualValues(t, items[1].Player_id, ownerId) + assert.EqualValues(t, items[1].Id, 11) + assert.EqualValues(t, items[1].Proto_id, 200) + assert.EqualValues(t, items[1].Amount, 2) +} + +func TestRemoveIds(t *testing.T) { + ids := &dbmeta.RemovedIds{} + + assert.EqualValues(t, []uint64{}, ids.GetList(100)) + + ids.Add(100, 1) + ids.Add(100, 3) + + ids.Add(200, 1) + ids.Add(300, 3) + + assert.EqualValues(t, false, ids.HasList(10)) + assert.EqualValues(t, true, ids.HasList(100)) + assert.EqualValues(t, true, ids.HasList(300)) + + assert.EqualValues(t, []uint64{1, 3}, ids.GetList(100)) + assert.EqualValues(t, []uint64{3}, ids.GetList(300)) +} + +/////////////////////////////////////////////////////////////////////////////// + +type DataPlayer struct { + + Id uint32 `json:"id" db:"id"` + Client_version string `json:"client_version" db:"client_version"` + Reg_time uint32 `json:"reg_time" db:"reg_time"` + Name string `json:"name" db:"name"` + Utc_delta int32 `json:"utc_delta" db:"utc_delta"` +fieldsMask meta.FieldsMask + +} + +var _DataPlayer_class_props map[string]string = map[string]string{"POD": "","bitfields": "","pkey": "id","table": "player","owner": "id","id": "id","cloneable": "",} +var _DataPlayer_class_fields []string = []string{"id","client_version","reg_time","name","utc_delta"} +var _DataPlayer_fields_props meta.ClassFieldsProps = map[string]map[string]string{"Id" : map[string]string{"optional": "1",},"Client_version" : map[string]string{"strmax": "16","optional": "1",},"Reg_time" : map[string]string{"default": "","optional": "1",},"Name" : map[string]string{"default": "\"\"","optional": "1",},"Utc_delta" : map[string]string{"default": "","optional": "1",}} + +func DataPlayer_CLASS_ID() uint32 { + return 74407040 +} + +type IDataPlayer interface { + meta.IMetaStruct + PtrDataPlayer() *DataPlayer +} + +func (*DataPlayer) CLASS_ID() uint32 { + return 74407040 +} + +func (*DataPlayer) CLASS_NAME() string { + return "DataPlayer" +} + +func (*DataPlayer) CLASS_PROPS() *map[string]string { + return &_DataPlayer_class_props +} + +func (*DataPlayer) CLASS_FIELDS() []string { + return _DataPlayer_class_fields +} + +func (*DataPlayer) CLASS_FIELDS_PROPS() *meta.ClassFieldsProps { + return &_DataPlayer_fields_props +} + +//convenience getter +func PtrDataPlayer(m meta.IMetaStruct) *DataPlayer { + p, ok := m.(IDataPlayer) + if !ok { + return nil + } + return p.PtrDataPlayer() +} + +func (self *DataPlayer) PtrDataPlayer() *DataPlayer { + return self +} + +func NewDataPlayer() *DataPlayer { + item := new (DataPlayer) + item.Reset() + return item +} + +func (self *DataPlayer) Reset() { + + self.Id = 0 + self.Client_version = "" + self.Reg_time = 0 + self.Name = "" + self.Utc_delta = 0 +self.fieldsMask = meta.FieldsMask{} + +} + +func (self *DataPlayer) Read(reader meta.Reader) error { + return meta.ReadStruct(reader, self, "") +} + +func (self *DataPlayer) ReadFields(reader meta.Reader) error { + self.Reset() + + use_mask, mask, err := reader.TryReadMask() + + if err != nil { + + return err + + } + + self.fieldsMask = mask + + _cont_size, err := reader.GetContainerSize() + if err != nil { + return err + } + if _cont_size < 0 { + _cont_size = 0 + } + if _cont_size <= 0 { + return nil + } + if !use_mask { + _cont_size-- + } + if !use_mask || (use_mask && self.HasValue(0)) { + if err := reader.ReadU32(&self.Id, "id"); err != nil { return /*optional*/nil } + } + if _cont_size <= 0 { + return nil + } + if !use_mask { + _cont_size-- + } + if !use_mask || (use_mask && self.HasValue(1)) { + if err := reader.ReadString(&self.Client_version, "client_version"); err != nil { return /*optional*/nil } + } + if _cont_size <= 0 { + return nil + } + if !use_mask { + _cont_size-- + } + if !use_mask || (use_mask && self.HasValue(2)) { + if err := reader.ReadU32(&self.Reg_time, "reg_time"); err != nil { return /*optional*/nil } + } + if _cont_size <= 0 { + return nil + } + if !use_mask { + _cont_size-- + } + if !use_mask || (use_mask && self.HasValue(3)) { + if err := reader.ReadString(&self.Name, "name"); err != nil { return /*optional*/nil } + } + if _cont_size <= 0 { + return nil + } + if !use_mask { + _cont_size-- + } + if !use_mask || (use_mask && self.HasValue(4)) { + if err := reader.ReadI32(&self.Utc_delta, "utc_delta"); err != nil { return /*optional*/nil } + } + return nil +} + +func (self *DataPlayer) Write(writer meta.Writer) error { + return meta.WriteStruct(writer, self, "") +} + +func (self *DataPlayer) WriteFields(writer meta.Writer) error { + + if err := writer.WriteU32(self.Id, "id"); err != nil { return err } + + if err := writer.WriteString(self.Client_version, "client_version"); err != nil { return err } + + if err := writer.WriteU32(self.Reg_time, "reg_time"); err != nil { return err } + + if err := writer.WriteString(self.Name, "name"); err != nil { return err } + + if err := writer.WriteI32(self.Utc_delta, "utc_delta"); err != nil { return err } + + return nil +} + +func(self *DataPlayer) HasValue(index uint64) bool { + return self.fieldsMask.FieldChanged(index) +} +func(self *DataPlayer) SetFieldChanged(index uint64) { + self.fieldsMask.SetFieldChanged(index) +} +func(self *DataPlayer) IsMaskFilled() bool { + return self.fieldsMask.IsFilled() +} +func(self *DataPlayer) GetMask() meta.FieldsMask { + return self.fieldsMask +} + +type DataItem struct { + + Id uint32 `json:"id" db:"id"` + Player_id uint32 `json:"player_id" db:"player_id"` + Proto_id uint32 `json:"proto_id" db:"proto_id"` + Amount int64 `json:"amount" db:"amount"` +fieldsMask meta.FieldsMask + +} + +var _DataItem_class_props map[string]string = map[string]string{"POD": "","table": "item","id": "id","owner": "player_id","bitfields": "",} +var _DataItem_class_fields []string = []string{"id","player_id","proto_id","amount",} +var _DataItem_fields_props meta.ClassFieldsProps = map[string]map[string]string{"Id" : map[string]string{"optional": "1",},"Player_id" : map[string]string{"optional": "1",},"Proto_id" : map[string]string{"obscured": "","optional": "1",},"Amount" : map[string]string{"obscured": "","optional": "1",},} + +func DataItem_CLASS_ID() uint32 { + return 263721017 +} + +type IDataItem interface { + meta.IMetaStruct + PtrDataItem() *DataItem +} + +func (*DataItem) CLASS_ID() uint32 { + return 263721017 +} + +func (*DataItem) CLASS_NAME() string { + return "DataItem" +} + +func (*DataItem) CLASS_PROPS() *map[string]string { + return &_DataItem_class_props +} + +func (*DataItem) CLASS_FIELDS() []string { + return _DataItem_class_fields +} + +func (*DataItem) CLASS_FIELDS_PROPS() *meta.ClassFieldsProps { + return &_DataItem_fields_props +} + +//convenience getter +func PtrDataItem(m meta.IMetaStruct) *DataItem { + p, ok := m.(IDataItem) + if !ok { + return nil + } + return p.PtrDataItem() +} + +func (self *DataItem) PtrDataItem() *DataItem { + return self +} + +func NewDataItem() *DataItem { + item := new (DataItem) + item.Reset() + return item +} + +func (self *DataItem) Reset() { + + self.Id = 0 + self.Player_id = 0 + self.Proto_id = 0 + self.Amount = 0 +self.fieldsMask = meta.FieldsMask{} + +} + +func (self *DataItem) Read(reader meta.Reader) error { + return meta.ReadStruct(reader, self, "") +} + +func (self *DataItem) ReadFields(reader meta.Reader) error { + self.Reset() + + use_mask, mask, err := reader.TryReadMask() + + if err != nil { + + return err + + } + + self.fieldsMask = mask + + _cont_size, err := reader.GetContainerSize() + if err != nil { + return err + } + if _cont_size < 0 { + _cont_size = 0 + } + if _cont_size <= 0 { + return nil + } + if !use_mask { + _cont_size-- + } + if !use_mask || (use_mask && self.HasValue(0)) { + if err := reader.ReadU32(&self.Id, "id"); err != nil { return /*optional*/nil } + } + if _cont_size <= 0 { + return nil + } + if !use_mask { + _cont_size-- + } + if !use_mask || (use_mask && self.HasValue(1)) { + if err := reader.ReadU32(&self.Player_id, "player_id"); err != nil { return /*optional*/nil } + } + if _cont_size <= 0 { + return nil + } + if !use_mask { + _cont_size-- + } + if !use_mask || (use_mask && self.HasValue(2)) { + if err := reader.ReadU32(&self.Proto_id, "proto_id"); err != nil { return /*optional*/nil } + } + if _cont_size <= 0 { + return nil + } + if !use_mask { + _cont_size-- + } + if !use_mask || (use_mask && self.HasValue(3)) { + if err := reader.ReadI64(&self.Amount, "amount"); err != nil { return /*optional*/nil } + } + return nil +} + +func (self *DataItem) Write(writer meta.Writer) error { + return meta.WriteStruct(writer, self, "") +} + +func (self *DataItem) WriteFields(writer meta.Writer) error { + + if err := writer.WriteU32(self.Id, "id"); err != nil { return err } + + if err := writer.WriteU32(self.Player_id, "player_id"); err != nil { return err } + + if err := writer.WriteU32(self.Proto_id, "proto_id"); err != nil { return err } + + if err := writer.WriteI64(self.Amount, "amount"); err != nil { return err } + + return nil +} + +func(self *DataItem) HasValue(index uint64) bool { + return self.fieldsMask.FieldChanged(index) +} +func(self *DataItem) SetFieldChanged(index uint64) { + self.fieldsMask.SetFieldChanged(index) +} +func(self *DataItem) IsMaskFilled() bool { + return self.fieldsMask.IsFilled() +} +func(self *DataItem) GetMask() meta.FieldsMask { + return self.fieldsMask +} + +func (self *DataItem) NewInstance() meta.IMetaDataItem { + return NewDataItem() +} + +func (self *DataItem) GetDbTableName() string { + return "item" +} + +func (self *DataItem) GetDbFields() []string { + return self.CLASS_FIELDS() +} + +func (self *DataItem) GetOwnerFieldName() string { + return "player_id" +} + +func (self *DataItem) GetIdFieldName() string { + return "id" +} + +func (self *DataItem) GetIdValue() uint64 { + return uint64(self.Id) +} + +func (self *DataItem) Import(data interface{}) { + switch data.(type) { + case DataItem: + { + + row := data.(DataItem) + self.Id = row.Id + self.Player_id = row.Player_id + self.Proto_id = row.Proto_id + self.Amount = row.Amount + break + } + default: + break + } +} + +func (self *DataItem) Export(data []interface{}) { + + data[0] = self.Id + data[1] = self.Player_id + data[2] = self.Proto_id + data[3] = self.Amount +} +