Skip to content
Advertisement

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

Could someone explain how to properly use Scan() and Value() in the following example?

I’m trying to make use of the following examples:

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.

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement