使用 SQL 和 gRPC 时如何使用 Scan 和 Value?

How to use Scan and Value when working with SQL and gRPC?

有人可以解释一下如何在下面的例子中正确使用 Scan()Value() 吗?

我正在尝试使用以下示例:

我的原型:

message Timestamp {
  google.protobuf.Timestamp timestamp = 1;
}
message User {
  uint32 ID = 1;
  Timestamp createdAt = 2;
}

代码(需要修正time.Now()):

package v1

import (
        "database/sql/driver"
        "fmt"
        "time"

        "github.com/golang/protobuf/ptypes"
)

func (u *User) Create() {
  u.CreatedAt = time.Now() // FIXME: ERROR. How do I make use of Scan() and Value() here?

  // saving to SQL database
}

func (ts *Timestamp) Scan(value interface{}) error {
    switch t := value.(type) {
    case time.Time:
            var err error
            ts.Timestamp, err = ptypes.TimestampProto(t)
            if err != nil {
                    return err
            }
    default:
            return fmt.Errorf("Not a protobuf Timestamp")
    }
    return nil
}

func (ts Timestamp) Value() (driver.Value, error) {
        return ptypes.Timestamp(ts.Timestamp)
}

扫描器和评估器接口并不是您自己真正会使用的东西,至少在涉及到在数据库中存储自定义类型时不会。我将首先介绍 Scan()Value() 函数的使用,然后再解决您的问题。

当您获得 sql.Row 结果,并希望将结果集中的值分配(扫描)到自定义类型的变量中时。文档显示 sql.Row.Scan() 函数接受 0 个或多个 interface{} 类型的参数,基本上任何参数。 (check docs here).

在可以扫描值的支持类型列表中,最后一行是重要的:

any type implementing Scanner (see Scanner docs)

通过函数 func (ts *Timestamp) Scan(value interface{}) error {Timestamp 类型现在实现了 Scanner 接口,因此允许 sql.Row 为该类型赋值。 Scanner 接口的文档位于我上面链接的 Scan() 文档的正下方。

当然,这可以帮助您从数据库中读取值,但在存储这些类型时却无处可去。为此,您需要 Valuer 接口。如果您还没有猜到,func (ts Timestamp) Value() (driver.Value, error) 函数确实使您的 Timestamp 类型实现了这个接口。 driver.Valuer 接口的文档可以在 here 找到,一直在底部。

Valuer 接口的要点是允许将 any 类型转换为 driver.Value 驱动程序可以使用和存储的方法在数据库中(再次:docs here)。


解决问题

首先,我假设您的协议输出已写入 v1 包。如果不是,它不会对你很好地工作。

违规行确实是您标记的行:

u.CreatedAt = time.Now()

首先,User.CreatedAt 属于 Timestamp 类型,它本身就是一条包含单个时间戳的消息。要将 CreatedAt 时间设置为 time.Now(),您需要正确初始化 CreatedAt 字段:

u.CreatedAt = &Timestamp{
    Timestamp: ptypes.TimestampNow(), // this returns a PROTOBUF TYPE!
}

已经在你的 ScanValue 函数中这样做了,所以我真的不明白你为什么不在这里这样做...


建议

如果 protoc 输出确实写入了 v1 包,我真的,真的会删除 User.Create() 函数。事实上,我会直接杀了它。您的协议缓冲区用于通信。通过 RPC 公开您的程序。这是一个 API。这些 message 类型本质上是请求和响应对象(如果您愿意,可以美化 DTO)。您正在向其中添加此 Create 函数,这会将它们变成 AR 类型。它使您的 protobuf 包无法使用。 gRPC 的美妙之处在于,您生成 golang、C++、Python、……其他人可以用来调用您的程序的代码。如果你让你的 gRPC 包依赖于数据库,就像你正在做的那样,我个人永远不会使用它。