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的方法了。