几个星期前,我开始学习go并尝试构建一个简单的博客应用程序,同时学习基础知识。
目前,我正在尝试使用database/sql
和github.com/lib/pq
软件包获取和保留博客帖子。我不喜欢使用第三方软件包,例如sqlx
或gorm
,而不完全了解go的原生行为和基础知识。
我的Post
结构是这样的:
type Post struct {
Id int
Title string
Body string
Tags json.RawMessage
}
在保留帖子时,我的save()
功能可以正常运行:
func (p *Post) save() (int, error) {
const query = `INSERT INTO post (title, body, tags) VALUES($1, $2, $3) RETURNING id`
db := GetDB()
var postid int
err := db.QueryRow(query, p.Title, p.Body, p.Tags).Scan(&postid)
return postid, err
}
并阅读最新帖子,我写了一个小函数:
func getLatestPosts(page int) (*[]Post, error) {
const query = `SELECT id, title, body, tags FROM posts ORDER BY id DESC LIMIT 10`
var items []Post
db := GetDB()
rows, err := db.Query(query)
if err != nil {
return nil, err
}
for rows.Next() {
var item Post
if err := rows.Scan(&item.Id, &item.Title, &item.Body, &item.Tags); err != nil {
log.Fatal(err)
}
items = append(items, item)
}
return &items, err
}
这也适用于点击标记列为空的帖子行,此时我收到以下错误:
2015/04/16 21:53:04 sql:列索引4上的扫描错误:不支持 司机 - >扫描对: - > * json.RawMessage
我的问题是,扫描结果集时处理nullable json
列的正确方法是什么?此错误与lib/pq
?
我的架构是:
CREATE TABLE post (
"id" int4 NOT NULL DEFAULT nextval('post_id_seq'::regclass),
"title" varchar(255) NOT NULL COLLATE "default",
"body" text COLLATE "default",
"tags" json,
PRIMARY KEY ("id") NOT DEFERRABLE INITIALLY IMMEDIATE
);
这是一个已经存在的(非空)tags
字段内容:
{
"black-and-white" : "Black and White",
"the-godfather" : "The Godfather"
}
有什么想法吗?
答案 0 :(得分:3)
TL; DR:将您的结构更改为Tags *json.RawMessage
或使用该类型的临时结构。
请注意,您可以search the Go source获取错误消息,以便在您感兴趣时更好地了解幕后发生的事情(标准软件包主要是写得好的好来源)去代码)。
我使用下表对新的PostgreSQL-9.4.1服务器进行了测试:
CREATE TABLE post (
"id" serial PRIMARY KEY,
"title" varchar(255) NOT NULL,
"body" text,
"tags" json
);
(顺便说一句,它可能更好地给出重新创建的命令,或者用于创建表的地方,而不是直接使用的表格用途。此外,当我熟悉PostgreSQL时,我记得varchar
列不是错误而不是仅仅使用text
,可能是长度约束,这是非常罕见的。)
将该表与您的结构类型一起使用:
converting Exec argument #2's type: unsupported type json.RawMessage, a slice
在插页上。更改为[]byte(p.Tags)
使其消失(但请参阅下文),然后查询已按原样运行。
当我将NULL值放入表中时,我只遇到了与查询相同的错误。解决方法是将struct字段更改为Tags *json.RawMessage
。然后我可以删除我在插入时添加的强制转换,并且查询工作正常,或者根据需要将字段设置为nil
。
如果您这样做,请在使用之前忘记检查item.Tags
是否为nil
。或者,创建数据库字段NOT NULL
。
我不太熟悉Go的数据库支持,知道是否需要指向切片的指针来处理NULL是合理的;我不希望因为Go已经区分了空切片和零切片。
或者,您可以按原样保留类型,并使用如下临时类型:
var item post
var tmp *json.RawMessage
if err := rows.Scan(&item.Id, &item.Title, &item.Body, &tmp); err != nil {
log.Fatal(err)
}
if tmp != nil {
item.Tags = *tmp
}
使用NULL主体测试时,您可能会遇到类似的问题。您可以在您的类型中创建数据库列NOT NULL
或使用sql.NullString
,也可以使用Valid
,如上所述(使用NullString
的{{1}}字段来查看是否应该复制字符串)。
其他一些小调:
golint
建议使用ID
代替Id
。GetDB
实施,我希望它只是获得共享/全球*sql.DB
。您不想反复拨打sql.Open
。getLatestPosts
函数重新*[]Post
;不要这样做。只需返回[]Post
。您几乎从不想使用指向切片的指针,当然也不想使用返回类型。