leogoesger
leogoesger

Reputation: 3840

go scan Postgres array_agg

I have a one-to-many relationship in postgres (Event has many EventUser), and would like to scan and store into a struct Event.

// EventUser struct
type EventUser struct {
    ID              int64
    CheckedIn       bool
    PaidAmount      float32
}

// Event struct
type Event struct {
    ID         int64  `json:"id"`
    Name       string `json:"name"`
    StartTime  string `json:"startTime"`
    EventUsers string
}

Here is the query:

SELECT events.id, events.name, events."startTime", e."eventUsers" as "eventUsers"
FROM "Events" as events
LEFT JOIN (
        SELECT events.id as id, array_to_json(array_agg(eu.*)) as "eventUsers"
        FROM "EventUsers" as eu
        JOIN "Events" AS "events" ON events.id = eu."eventId"
        WHERE eu.status = 'RESERVED'
        GROUP BY events.id
    ) AS e USING (id)
WHERE events.status = 'COMPLETED' 

The query returns this:

{
  id: 2,
  name: "2 Events are 48 days from now",
  startTime: 1590471343345,
  eventUsers: [
    {
      id: 2,
      checkedIn: false,
      paidAmount: 8
    },
    {
      id: 3,
      checkedIn: false, 
      paidAmount: 8,
    },
  ],
};

This is what I am trying to do, which directly scan each item under eventUsers into struct, and store in the Event's struct.

got := []Event{}

for rows.Next() {
    var r Event
    err = rows.Scan(&r.ID, &r.Name, &r.StartTime, &r.EventUsers)
    if err != nil {
        panic(err)
    }
}

I think I could achieve this by storing the array as a string and then unmarshal it.

What I want is something similar to sql.NullString.

Upvotes: 1

Views: 709

Answers (1)

mkopriva
mkopriva

Reputation: 38313

You could define a slice type that implements the sql.Scanner interface. Note that when you pass an instance of a type that implements Scanner to a (*sql.Rows).Scan or (*sql.Row).Scan call, the impelmenter's Scan method will be invoked automatically.

type EventUserList []*EventUser

func (list *EventUserList) Scan(src interface{}) error {
    if data, ok := src.([]byte); ok && len(data) > 0 {
        if err := json.Unmarshal(data, list); err != nil {
            return err
        }
    }
    return nil
}

Then, assuming that the e."eventUsers" in the select query is a json array, you could use it like this:

// EventUser struct
type EventUser struct {
    ID         int64
    CheckedIn  bool
    PaidAmount float32
}

// Event struct
type Event struct {
    ID         int64         `json:"id"`
    Name       string        `json:"name"`
    StartTime  string        `json:"startTime"`
    EventUsers EventUserList `json:"eventUsers"`
}

// ...

var events []*Event
for rows.Next() {
    e := new(Event)
    if err := rows.Scan(&e.ID, &e.Name, &e.StartTime, &e.EventUsers); err != nil {
        return err
    }
    events = append(events, e)
}

Upvotes: 1

Related Questions