gointerfacepq

How to add interface to non-local struct in golang?


I use https://github.com/lib/pq for getting data from postgres. For extracting data I use third-party struct, which has field with protobuf Timestamp https://pkg.go.dev/google.golang.org/protobuf/types/known/timestamppb#Timestamp So So the issue is make work Scan from time.Time to timestamppb.Timestamp

type Stuff struct { //this struct non-local, this is from third-party package
  Date *timestamppb.Timestamp
}

stuff = Stuff{}

scanErr := rows.Scan(&stuff.Date)

I tried to Scan to struct witch implements sql.Scanner interface. That was easy. I just implement Scan function like this:

type Test struct {
}


func (t Test) Scan(src any) error {
//convert value
}

but it doesn't work with timestamppb.Timestamp because it is non-local type. So then I tried to define local type and do the same trick

type TimestampPb timestamppb.Timestamp

func (t TimestampPb) Scan(src any) error {
//convert value
}

but this trick didn't work. Moreover I have warning "'Scan' passes a lock by the value: type 'TimestampPb' contains 'protoimpl.MessageState' contains 'sync.Mutex' which is 'sync.Locker'" This either doesn't work if I specify pointer for TimestampPb

func (t *TimestampPb) Scan(src any) error {

So I wonder, how can I make timestamppb.Timestamp instance of sql.Scanner. Is it possible?

Update 1 Little example that I tried

type TimestampPb timestamppb.Timestamp

type test struct { //third-party struct, I can't modify it
    date *timestamppb.Timestamp
}

func (s *Storage) test() {
    rows, _ := s.db.Query("SELECT \"test_date\" FROM \"test_table\" LIMIT 1")

    defer func() {
        if rows != nil {
            _ = rows.Close()
        }
    }()

    for rows.Next() {
        //var value *TimestampPb //works
        //var value TimestampPb //works
        //var value *timestamppb.Timestamp //doesn't work
        //testStruct := test{} //doesn't work

        //err := rows.Scan(&value) //scan to variable
        //err := rows.Scan(&testStruct.date) //scan to field in the struct
        if err != nil {
            log.Fatalln(err)
        }
    }
}

func (tpb *TimestampPb) Scan(src any) error {
    return nil
}

I wonder if I can use case with the testStruct?


Solution

  • The pq library doesn't support *timestamppb.Timestamp as a timestamp type. See the supported date types in the documentation.

    A different type will need to be for scanning.

    Often I do that by using an auxiliary type in the function. For example:

    type row struct {
      Date *timestamppb.Timestamp
      // ... more fields here
    }
    
    func Query() ([]row, error) {
        rows, err := s.db.Query(...)
      if err != nil {
        return nil, err
      }
      defer rows.Close()
    
      var rows []row
      for rows.Next() {
        type aux struct {
          Date time.Time
          // ... more fields here
        }
        var value aux
        if err := rows.Scan(&value); err != nil {
          return nil, err
        }
        rows = append(rows, row{
          Date: timestamppb.New(value.Date),
          // ... more fields here
        }
      }
      return rows, nil
    }