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 }