英文:
unsupported Scan, storing driver.Value type []uint8 into type *[]string
问题
我使用golang
、gin
和gorp
实现了rest api
。
员工结构体如下:
type Employee struct {
Id int64 `db:"id" json:"id"`
Firstname string `db:"firstname" json:"firstname"`
Lastname string `db:"lastname" json:"lastname"`
Dob time.Time `db:"dob" json:"dob"`
Skills []string `db:"skills" json:"skills"`
}
发送POST
请求时:
func PostEmployee(c *gin.Context) {
var emp Employee
c.Bind(&emp)
skills, _ := json.Marshal(emp.Skills)
if emp.Firstname != "" && emp.Lastname != "" {
if insert, _ := dbmap.Exec(`INSERT INTO employee (firstname, lastname, dob, skills) VALUES (?, ?, ?, ?)`, emp.Firstname, emp.Lastname, emp.Dob, skills); insert != nil {
emp_id, err := insert.LastInsertId()
// ...
}
// ...
}
}
这段代码将数据保存到mysql
数据库中,运行正常。
为了从数据库中检索数据,实现了GET
请求:
func GetEmployees(c *gin.Context) {
var emps []Employee
_, err := dbmap.Select(&emps, "SELECT * FROM employee")
log.Println(err)
if err == nil {
c.JSON(200, emps)
} else {
c.JSON(404, gin.H{"error": "no employee(s) into the table"})
}
}
GET
查询无法从数据库中获取任何数据,log.Println(err)
的日志显示:
Scan error on column index 4: unsupported Scan, storing driver.Value type []uint8 into type *[]string
有什么想法吗?
英文:
I have implemented rest api
using golang
, gin
and gorp
Employee structure:
type Employee struct {
Id int64 `db:"id" json:"id"`
Firstname string `db:"firstname" json:"firstname"`
Lastname string `db:"lastname" json:"lastname"`
Dob time.Time `db:"dob" json:"dob"`
Skills []string `db:skills json:"skills"`
}
In POST
sending request as:
func PostEmployee(c *gin.Context) {
var emp Employee
c.Bind(&emp)
skills, _ := json.Marshal(emp.Skills)
if emp.Firstname != "" && emp.Lastname != "" {
if insert, _ := dbmap.Exec(`INSERT INTO employee (firstname, lastname, dob, skills) VALUES (?, ?, ?, ?)`, emp.Firstname, emp.Lastname, emp.Dob, skills); insert != nil {
emp_id, err := insert.LastInsertId()
.....
}
......
}
This save data to mysql
database, works perfect.
For retrieving data from database implemented GET
request
func GetEmployees(c *gin.Context) {
var emps []Employee
_, err := dbmap.Select(&emps, "SELECT * FROM employee")
log.Println(err)
if err == nil {
c.JSON(200, emps)
} else {
c.JSON(404, gin.H{"error": "no employee(s) into the table"})
}
GET
query doesn't gives any data from database and log.Println(err)
log says:
Scan error on column index 4: unsupported Scan, storing driver.Value type []uint8 into type *[]string
Any ideas?
答案1
得分: 13
两种方法:
1. 为自定义类型实现sql.Scanner和driver.Valuer接口
优点:
- 存储和检索简单
- 不需要从另一个表中查询/加载
注意事项:
- 字符串大小受sql列定义限制(在这种情况下为255)。根据数据库架构,这可能会被截断或导致需要处理的错误。
- 在数据库级别删除特定技能需要额外的操作。
- 搜索需要使用包含操作符而不是等于操作符。
- 未来修改技能结构将会困难。
2. 将技能移动到单独的表中,并引用员工
优点:
- 扩展表定义
- 搜索能力更强
- 在数据库级别更容易删除某人/所有人的技能
注意事项:
- 需要另一个查询/加载来获取技能
- 增加了数据库模式定义的复杂性
英文:
Two Approaches:
1. Implement sql.Scanner and driver.Valuer interfaces for a custom type
Benefits:
- Simple to store and retrieve
- Don't have to query/load from another table
Caveats:
- String Size Is Limited by sql column definition (i.e. in this case its 255).
Depending on your database architecture this will either be truncated or will result in an error that needs handling. - Jump through hoops to delete specific skills for someone/everyone at the database level.
- Searching needs to be done through contains instead of equal operator
- Modifying the skills structure will be difficult in the future.
package tgorm
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/stretchr/testify/assert"
"strings"
"testing"
"time"
)
type Skills []string
func (s Skills) Value() (driver.Value, error) {
if len(s) == 0 {
return "[]", nil
}
return fmt.Sprintf(`["%s"]`, strings.Join(s, `","`)), nil
}
func (s *Skills) Scan(src interface{}) (err error) {
var skills []string
switch src.(type) {
case string:
err = json.Unmarshal([]byte(src.(string)), &skills)
case []byte:
err = json.Unmarshal(src.([]byte), &skills)
default:
return errors.New("Incompatible type for Skills")
}
if err != nil {
return
}
*s = skills
return nil
}
type Employee struct {
Id int64 `db:"id" json:"id"`
Firstname string `db:"firstname" json:"firstname"`
Lastname string `db:"lastname" json:"lastname"`
Dob time.Time `db:"dob" json:"dob"`
Skills Skills `gorm:"type:varchar(255);" db:"skills" json:"skills"`
}
func (e Employee) TableName() string {
return "employee"
}
func getMemoryDataBase() *gorm.DB {
db, err := gorm.Open("sqlite3", ":memory:")
if err != nil {
panic(err)
}
db = db.Debug()
db.AutoMigrate(Employee{})
return db
}
func TestSaveEmployee(t *testing.T) {
db := getMemoryDataBase()
emp := Employee{
Id: 1,
Firstname: "Fake",
Lastname: "Emp",
Dob: time.Time{},
Skills: []string{"C#", "GO", "C++"},
}
skills, _ := json.Marshal(emp.Skills)
err := db.Exec(`INSERT INTO employee (firstname, lastname, dob, skills) VALUES (?, ?, ?, ?)`, emp.Firstname, emp.Lastname, emp.Dob, skills).Error
assert.Nil(t, err)
var emps []Employee
err = db.Raw("SELECT * FROM employee").Scan(&emps).Error
assert.Nil(t, err)
assert.Equal(t, []Employee{emp}, emps)
}
2. Move Skills into a separate table, with a reference to the employee.
Benefits:
- Extend Table Definition
- Better searching capabilities
- Easier to drop skills for someone/everyone at the database level
Caveats:
- Needs another query/load for Skills
- Increased Database Schema Definition
package subgrom
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
type Skill struct {
Id int64 `db:"id" json:"id"`
Skill string `db:"skill" json:"skill"`
EmployeeRef int64
}
type Employee struct {
Id int64 `db:"id" json:"id"`
Firstname string `db:"firstname" json:"firstname"`
Lastname string `db:"lastname" json:"lastname"`
Dob time.Time `db:"dob" json:"dob"`
Skills []Skill `db:"skills" json:"skills" gorm:"foreignkey:EmployeeRef"`
}
func (e Employee) TableName() string {
return "employee"
}
func getMemoryDataBase() *gorm.DB {
db, err := gorm.Open("sqlite3", ":memory:")
if err != nil {
panic(err)
}
db = db.Debug()
db.AutoMigrate(Employee{}, Skill{})
return db
}
func TestSaveEmployee(t *testing.T) {
db := getMemoryDataBase()
emp := Employee{
Id: 1,
Firstname: "Fake",
Lastname: "Emp",
Dob: time.Time{},
Skills: []Skill{{Skill: "C#"}, {Skill: "GO"}, {Skill: "C++"}},
}
err := db.Create(&emp).Error
assert.Nil(t, err)
var emps []Employee
err = db.Preload("Skills").Find(&emps).Error
assert.Nil(t, err)
assert.Equal(t, []Employee{emp}, emps)
}
答案2
得分: 0
遇到类似的问题,对我来说问题是字段"scope_t"的顺序。
选择组 = SELECT id, name, fully_qualified_name, parent_id, scopes, scope_t FROM groups
在插入数据时,我将"scope_t"放在一个随机的位置,所以SQL返回了上述错误,原因是该位置与不同的数据类型进行了映射。
如果错误 := r.db.QueryRowContext(ctx, createGroup, group.Name, group.FullyQualifiedName,
pq.Array(group.Scopes), group.ParentID, userID, scope_type).Scan(&id); 错误 != nil {
返回nil,错误
}
TL;DR
在插入之前,还要检查可能会交换值的位置,从而导致类型错误。
英文:
Faced similar issue, for me problem was sequencing of field "scope_t".
selectGroup = `SELECT
id,
name,
fully_qualified_name,
parent_id,
scopes,
scope_t
FROM groups `
When inserting the data I placed "scope_t" at a random place so SQL return with the error mentioned above reason being that position was mapped with a different data type.
if err := r.db.QueryRowContext(ctx, createGroup, group.Name, group.FullyQualifiedName,
pq.Array(group.Scopes), group.ParentID, userID, scope_type).Scan(&id); err != nil {
return nil, err
}
TL;DR
Before inserting also check the places you might be interchanging the values due to which type error is caused.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论