Comments (9)
请教几个问题
重复及效率问题
ScanAll的实现依赖于ScanRows,而ScanRows每次都要重新用反射获取一遍各个列的类型来准备用于接收数据的values.
即便就ScanRows自身来讲,最常见的使用场景——同一个sql.Rows,不断循环或间隔一定时间调用ScanRows(*sql.Rows),重复及效率问题也是存在.
是否可以构建一个抽象Scanner来解决重复及效率问题.下面是v1版Scanner与当前的ScanRows和ScanAll语义一致.
type Scanner struct {
sqlRows *sql.Rows
cachedValues []any
}
func NewScanner(r *sql.Rows) (*Scanner, error) {
if r == nil {
// .....
}
colsInfo, err := r.ColumnTypes()
if err != nil {
return nil, err
}
values := make([]any, len(colsInfo))
for i, colInfo := range colsInfo {
typ := colInfo.ScanType()
for typ.Kind() == reflect.Pointer {
typ = typ.Elem()
}
values[i] = reflect.New(typ).Interface()
}
return &Scanner{sqlRows: r, cachedValues: values}, nil
}
func (s *Scanner) ScanOne() ([]any, error) {
// 每次都用同一个列集合来获取数据
err := s.sqlRows.Scan(s.cachedValues...)
if err != nil {
return nil, err
}
// 获取当前行的各个列值
values := make([]any, len(s.cachedValues))
for i := 0; i < len(s.cachedValues); i++ {
values[i] = reflect.ValueOf(s.cachedValues[i]).Elem().Interface()
}
return values, nil
}
func (s *Scanner) ScanAll() ([][]any, error) {
res := make([][]any, 0, 32)
for s.sqlRows.Next() {
cols, err := s.ScanOne()
if err != nil {
return nil, err
}
res = append(res, cols)
}
return res, nil
}
// 用v1版Scnner重构下面的代码通过了测试集合
func ScanRows(rows *sql.Rows) ([]any, error) {
scanner, err := NewScanner(rows)
if err != nil {
return nil, err
}
return scanner.ScanOne()
}
func ScanAll(rows *sql.Rows) ([][]any, error) {
scanner, err := NewScanner(rows)
if err != nil {
return nil, err
}
return scanner.ScanAll()
}
抽象的职责划分问题——半“自动/委托”,全“自动/委托”
使用原生的sql.Rows读取当前结果集的所有行,代码大致如下:
defer rows.Close()
var all
for rows.Next() {
var values
if err := rows.Scan(values...); err != nil {
// 扫描当前values出错
}
// 代码走到这,可以安全地使用values
all := append(all, values)
}
if rows.Err() != nil {
// 迭代期间出错 ....可能导致当前结果集读取不完整
}
// 走到这可以安全地使用all
- 用户使用ScanAll实现上述等效代码:
all, err := ScanAll(rows)
if err != nil {
// Scan出错
}
if rows.Err() != nil {
// 迭代期间出错 ....可能导致当前结果集读取不完整
}
// 走到这可以安全地使用all
当前ScanAll实现中没有调用rows.Err()所以返回的数据可能不完整,用户需要在调用ScanAll后手动检查rows.Err().
另外,rows.Err()可能“谎报”,比如连接超时,但当前结果集已经读完了对后续数据使用无影响.
是否该将rows.Err()纳入ScanAll的职责范围? 如果将rows.Err纳入ScanAll的职责范围,慌报情况不容易区分.
- 用户使用ScanRows实现上述等效代码:
defer rows.Close()
var all
for rows.Next() {
values, err := ScanRows(rows)
if err != nil {
// Scan报错
}
// 代码走到这,可以安全地使用values
all := append(all, values)
}
if rows.Err() != nil {
// 迭代期间出错 ....可能导致当前结果集读取不完整
}
类似的,ScanRows中没有调用rows.Next()及rows.Err()所以当前ScanRows的职责就是从rows中读取一行并返回行数据.
是否可以考虑将rows.Next和rows.Err纳入ScanRows的职责范围内?
- v1版Scanner的接口不在同一个抽象层次上
使用v1版Scanner,用户需要记住并区分使用ScanOne和ScanAll哪个要提前调用rows.Next()(或者之后要调用rows.Err)哪个不用,统一抽象层次这样符合用户的直觉. 另外,sql.Rows.Scan的注释中明确提示调用Scan前要先调用Next准备数据,所以将rows.Next和rows.Err纳入职责范围
下面是v2的Scanner
var ErrNoMoreRows = errors.New("ekit: no more rows in result set")
type Scanner struct {
sqlRows *sql.Rows
cachedValues []any
}
func NewScanner(r *sql.Rows) (*Scanner, error) {
// 读取列的元数据缓存
colsInfo, err := r.ColumnTypes()
if err != nil {
return nil, err
}
values := make([]any, len(colsInfo))
for i, colInfo := range colsInfo {
typ := colInfo.ScanType()
for typ.Kind() == reflect.Pointer {
typ = typ.Elem()
}
values[i] = reflect.New(typ).Interface()
}
return &Scanner{sqlRows: r, cachedValues: values}, nil
}
func (s *Scanner) ScanOne() ([]any, error) {
// 调用Next
if !s.sqlRows.Next() {
// 检测迭代错误
if err := s.sqlRows.Err(); err != nil {
return nil, err
}
// 数据读取完
return nil, fmt.Errorf("%w", ErrNoMoreRows)
}
err := s.sqlRows.Scan(s.cachedValues...)
if err != nil {
return nil, err
}
values := make([]any, len(s.cachedValues))
for i := 0; i < len(s.cachedValues); i++ {
values[i] = reflect.ValueOf(s.cachedValues[i]).Elem().Interface()
}
return values, nil
}
func (s *Scanner) ScanAll() ([][]any, error) {
res := make([][]any, 0, 32)
for {
cols, err := s.ScanOne()
if err != nil {
// 需要区分正常结束,只是没有数据的情况
if errors.Is(err, ErrNoMoreRows) {
break
}
return nil, err
}
res = append(res, cols)
}
return res, nil
}
统一抽象层次后, Scanner.ScanAll中的代码就是用户使用ScanOne的代码,缺点是有点丑——采用了sql.Row(不是sql.Rows)的处理方式.
from ekit.
对于func ScanRow(row sql.Row) ([]any, error) {} 有个疑问,直接scan的话,要写入的目标要通过泛型指定吗?类似下面这样
func ScanRow[T any](row sql.Row) ([]T, error) {
// 列的个数如何定?
res := make([]T, 0, 8)
err := row.Scan(res)
if err != nil {
return nil, err
}
return res, nil
}
from ekit.
不需要范型scantype中会给你类型信息,你只需要通过这个类型信息构建一个新值去接受数据
from ekit.
用不了泛型,因为我作为一个调用者也不知道具体的类型可能是什么
from ekit.
scantype的用法我明白,其实我是想确认下这两函数的区别
func ScanRows(rows *sql.Rows) []any, error {}
func ScanRow(row sql.Row) []any, error {}
*sql.Rows有scantype方法,貌似sql.Row没有这个方法吧?
from ekit.
我草,还真没有,那么就不需要 ScanRow 了,只需要 ScanRows 和 ScanAll,ScanRow 等将来支持了再说
from ekit.
from ekit.
好主意!之前我在创建这个 issue 的时候,并没有想到这个问题。你可以按照这个提一个新的合并请求。 这个东西看起来有点像是之前我们设计 Copier 和 Copy 方法。有一个 Scanner 的话会更好。 不过你觉得这里有没有必要考虑将 Scanner 做成接口呢?
#182 中实现将Scanner做成了接口,但是写完之后发现好像当前Scanner只能用sql.Rows不能用于sql.Row
from ekit.
sql.Row 是没有那个 ColumnType 的,所以就算了
from ekit.
Related Issues (20)
- Tuple: 新增 Pair 和 Triple 两种类型 HOT 8
- retry: Strategy 接口设计与等间隔重试实现
- retry: 指数退避重试策略实现 HOT 1
- pool相关总issue
- mapx: MutipleTreeMap
- mapx: 为 MultipleMap 添加 PutVals 方法
- mapx: LinkedMap 特性 HOT 12
- copier: ReflectCopier 支持忽略字段 HOT 1
- copier: 支持类型转换 HOT 20
- randx: 随机验证码生成器 HOT 5
- ekit:AnyValue 支持类型转换 - String 转数字类型
- unsafe 转换 string 和 []byte HOT 1
- sqlx: sqlx.NewNullXXX 方法
- syncx: SegmentKeysLock HOT 3
- slice.ContainsFunc破坏性修改确认 HOT 3
- mapx: 支持 Len 方法
- 重构randx.RandCode以及传入的RandType数值 HOT 3
- slice 转 map HOT 3
- 提供 Must 和 MustT 函数,简化错误处理 HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from ekit.