commit 911cdd30193c58bf0a2c19091cc7c16cb5f9c3f2 Author: Pavel Shevaev Date: Tue Nov 15 15:38:11 2022 +0300 first commit 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 +} +