first commit

This commit is contained in:
Pavel Shevaev 2022-11-15 15:38:11 +03:00
commit 911cdd3019
9 changed files with 1657 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
tags

191
collection.go Normal file
View File

@ -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
}

25
dbmeta.go Normal file
View File

@ -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
}

147
delete.go Normal file
View File

@ -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
}

12
go.mod Normal file
View File

@ -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
)

53
go.sum Normal file
View File

@ -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=

165
load.go Normal file
View File

@ -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
}

493
save.go Normal file
View File

@ -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
}

570
save_test.go Normal file
View File

@ -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
}