Go slowly   About  Contact  Archives

Update partially and return fully in Go

In typical CRUD applications, it’s common these days that your update APIs work properly on objects in a partial way. You should not enforcing the client to send the whole object to update just a field anymore. And with Postgres returning clause, we can return all object data despite being updated only some of them.

Let’s say you have to update a Student object, we will define all its fields as pointers so when the client doesn’t send up anything, it will be nil:

type Student struct {
      Name *string
      Age  *int
}

We must use pointers here because otherwise, we can’t differ default zero values with not-set values that are sent from the client. To update the record, we come up with two answers.

1. CASE WHEN… with RETURNING

In the database interface, we will choose the fields that need to update and return all object data when done, we use pgx and scany package here for executing query and scanning data back to Go struct:

import (
      "os"
      "github.com/jackc/pgx/v4"
      "github.com/georgysavva/scany/pgxscan"
)


func main() {
      conn, _ := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))

      query := `
      UPDATE students
      SET
          name = CASE WHEN $1 = true THEN $2 ELSE name END,
          age = CASE WHEN $3 = true THEN $4 ELSE age END
      RETURNING
          name, age`

      rows, _ := conn.Query(
         ctx, query,
         student.Name != nil, student.Name,
         student.Age != nil, student.Age
      )
      defer rows.Close()

      stud := Student{}

      for rows.Next() {
          _ = pgxscan.ScanRow(&stud, rows)
      }
}

This solution looks OK, but we can’t validate the object with all fields available at the same time before updating into the database. Hence the second approach.

2. SELECT first, UPDATE later

No code is needed to explain this method. First you retrieve the object from the database, then change those fields that need updating (non-nil fields), do whatever validation required, and then write the whole object back.

Despite we must always do a SELECT before an UPDATE, this should be the preferred solution.

Written on December 31, 2021.