Could someone explain how to properly use Scan()
and Value()
in the following example?
I’m trying to make use of the following examples:
- https://github.com/jinzhu/gorm/issues/2017#issuecomment-537627961
- https://github.com/travisjeffery/proto-go-sql/blob/master/_example/person_sql.go
My proto:
message Timestamp { google.protobuf.Timestamp timestamp = 1; } message User { uint32 ID = 1; Timestamp createdAt = 2; }
Code (need to fix 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) }
Advertisement
Answer
The scanner and valuer interfaces are not really things you’ll use yourself, not when it comes to storing custom types in a DB, at least. I’ll first cover the use of the Scan()
and Value()
functions, then I’ll address your issues.
When you get a sql.Row
result, and want to assign (scan) the values from the result set into your variables of a custom type. The docs show the sql.Row.Scan()
function takes 0 or more arguments of type interface{}
, basically anything. (check docs here).
In the list of supported types into which values can be scanned, the last line is the important one:
any type implementing Scanner (see Scanner docs)
With the function func (ts *Timestamp) Scan(value interface{}) error {
, the Timestamp
type now implements the Scanner
interface, thus allowing sql.Row
to assign values to this type. The documentation for the Scanner
interface is located right below the docs for Scan()
, which I linked above.
Of course, that helps you to read values from the DB, gets you nowhere when it comes to storing these types. For that, you need the Valuer
interface. In case you haven’t guessed it yet, the func (ts Timestamp) Value() (driver.Value, error)
function indeed makes it so your Timestamp
type implements this interface. The documentation for the driver.Valuer
interface can be found here, all the way at the bottom.
The point of the Valuer
interface is to allow a way to convert any type to a driver.Value
, that the driver can work with and store in the DB (again: docs here).
Fixing the issues
First up, I’m goign to have to assume your protoc output is written to the v1
package. If it isn’t, it’s not going to work very well for you.
The offending line is indeed the one you marked out:
u.CreatedAt = time.Now()
First up, User.CreatedAt
is of type Timestamp
, which is in and of itself a message containing a single timestamp. To set the CreatedAt
time to time.Now()
, you need to initialise the CreatedAt
field correctly:
u.CreatedAt = &Timestamp{ Timestamp: ptypes.TimestampNow(), // this returns a PROTOBUF TYPE! }
You are doing this in your Scan
and Value
functions already, so I really don’t get why you didn’t do it here…
Advice
If the protoc output is indeed written to the v1
package, I really, really would remove the User.Create()
function. In fact, I’d outright kill it. Your protocol buffers are used for communication. Exposing your program via RPC’s. It’s an API. These message
types are essentially request and response objects (glorified DTO’s if you will). You’re adding this Create
function to it, which turns them into AR types. It makes your protobuf package unusable. The beauty of gRPC is that you generate golang, C++, Python, … code that others can use to call your program. If you make your gRPC package dependent on a database, as you are doing, I personally would never, ever use it.