golang用pgx查询数据时如何将查询结果方便的放入Map中
pgx库简介
下面是来自官网的简介: pgx - PostgreSQL驱动和工具包
pgx是一个用于PostgreSQL的纯Go语言驱动和工具包。
pgx驱动是一个底层的高性能接口,暴露了PostgreSQL特有的功能,如LISTEN
/NOTIFY
和COPY
。它还包含一个标准database/sql
接口的适配器。
工具包组件是一组相关的包,实现了像解析wire协议和PostgreSQL与Go之间的类型映射等PostgreSQL功能。这些底层包可用于实现替代驱动、代理、负载均衡器、逻辑复制客户端等。
pgx应该是目前最好的启动包了,“pg” 另一个著名的PostgreSQL启动包的状态目前仅仅是维护了,并且推荐了pgx。
"对于需要新功能或可靠解决报告中的错误的用户,我们推荐使用正在积极开发中的
pgx比较底层,没有ORM的功能,比如我要将一行查询结果扫描到一个Map中。
近期由于开发一个pg到elastic的复制工具(包括wal增量复制和全量复制),在实现全量复制时,我需要将一行查询结果放到Map中,结果尴尬了,pgx中竟然没有这样的功能,难道我要因此使用gorm或者xorm(虽然也很方便,但是多引入一个包我也不那么愿意),经过询问必应,只得到了,自己声明一堆和字段对应变量然后扫描这样的例子:
// RetreiveBook
func RetreiveBook(db *sql.DB) ([]Book, error) {
var books []Book
// 查询
sql := `select book_id, title, author, to_char(publish_date, 'YYYY/MM/DD') as publish_date from m_book`
rows, err := db.Query(sql)
if err != nil {
log.Println(err)
return books, err
}
defer rows.Close()
for rows.Next() {
var book Book
// 获取各列的值,放到对应的地址中
rows.Scan(&book.BookId, &book.Title, &book.Author, &book.PublishDate)
books = append(books, book)
}
return books, nil
}
太麻烦了,我忍不了。
pgx有内置的方法
于是我不甘心的仔细查看pgx的文档和实例,终于,我发现了这个方法:
func pgx.CollectRows(rows pgx.Rows, fn pgx.RowToFunc[map[string]any]) ([]map[string]any, error)
// func[T any](rows pgx.Rows, fn pgx.RowToFunc[T]) ([]T, error)
// CollectRows iterates through rows, calling fn for each row, and collecting the results into a slice of T.
但是显然,这个方法是一次性的生成一个map的切片,如果数据量不大还好,但是对于我要用到的全量复制场景就未免不适合了,但是只要有了方向就好了,赶紧跟踪进去,
// CollectRows iterates through rows, calling fn for each row, and collecting the results into a slice of T.
func CollectRows[T any](rows Rows, fn RowToFunc[T]) ([]T, error) {
defer rows.Close()
slice := []T{}
for rows.Next() {
value, err := fn(rows)
if err != nil {
return nil, err
}
slice = append(slice, value)
}
if err := rows.Err(); err != nil {
return nil, err
}
return slice, nil
}
可见上述代码中,仅仅是简单的Next() Scan()然后回调pgx.RowToMap这个函数生成map,我不需要它一次性地给我map的切片,但是我可以轻松将他实现的过程移植到我的代码中,至此问题完美解决了。至于pgx.RowToMap这个方法的实现,其实非常简单,下面就是其源代码:
// RowToMap returns a map scanned from row.
func RowToMap(row CollectableRow) (map[string]any, error) {
var value map[string]any
err := row.Scan((*mapRowScanner)(&value))
return value, err
}
type mapRowScanner map[string]any
func (rs *mapRowScanner) ScanRow(rows Rows) error {
values, err := rows.Values()
if err != nil {
return err
}
*rs = make(mapRowScanner, len(values))
for i := range values {
(*rs)[string(rows.FieldDescriptions()[i].Name)] = values[i]
}
return nil
}
其中19行就是这个属性方法rows.FieldDescriptions()我不知道,否则自己也可以方便地实现扫描到map的方法了。