1

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?

abr_stackoverflow
  • 691
  • 2
  • 10
  • 26
  • You say that your TimestampPb solution not work. Can you explain why it does not work? It seems that it should work with the pointer method receiver. – Tyler Kropp Mar 16 '23 at 23:51
  • I debug Scan method and there is false in this code ``` if scanner, ok := dest.(Scanner); ok { return scanner.Scan(src) } ``` So my implemented Scan doesn't fire – abr_stackoverflow Mar 17 '23 at 18:06
  • The problem in the first place is that a type doesn't have `Scan`. You have to use your new type in order to fix the problem. It would help a lot to have a small, reproducible sample that we can run ourselves which demonstrates that it doesn't work like you say. – Tyler Kropp Mar 17 '23 at 18:43
  • Does this answer your question? [How to use go receiver when struct is defined in imported package](https://stackoverflow.com/questions/41379417/how-to-use-go-receiver-when-struct-is-defined-in-imported-package) – Tyler Kropp Mar 17 '23 at 18:46
  • I added Update 1 with the sample. I want to use case with the struct. Could you tell me, can I make this case work or I am doing something wrong? – abr_stackoverflow Mar 17 '23 at 21:27
  • Your example does not compile, because it's not a minimally reproducible sample. Maybe you could try the same trick for the struct itself. Not entirely sure how the interface is being used so can't say for sure if that'd do it. – Tyler Kropp Mar 17 '23 at 22:29

1 Answers1

0

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
}
dolan
  • 1,716
  • 11
  • 22
  • 1
    Eventually I did the same trick, but via time.Time directly (without auxiliary struct). It's not the way I expected but seems extending third-party type is unrealizable. So your example the most suitable. – abr_stackoverflow Mar 18 '23 at 10:52