1

I have a function GetAccount which is generated by sqlc.

When I call GetAccount(/*unused id*/), An ErrNoRows error should be returned. Instead I am getting no error and an Account with default values (zeros and empty strings) returned.

GetAccount implementation:

const getAccount = `-- name: GetAccount :one
SELECT id, owner, balance, currency, created_at
FROM accounts
WHERE id = $1
`

func (q *Queries) GetAccount(ctx context.Context, id int64) (Account, error) {
    row := q.db.QueryRowContext(ctx, getAccount, id)
    var i Account
    err := row.Scan(
      &i.ID,
      &i.Owner,
      &i.Balance,
      &i.Currency,
      &i.CreatedAt,
    )
    return i, err
}

Why I am not getting any error when there are no rows to return?

Edit:

As requested, here is how I am calling GetAccount. It is a Gin request handler.

type getAccountRequest struct {
    ID int64 `uri:"id" binding:"required,min=1"`
}

func (server *Server) getAccount(ctx *gin.Context) {
    var request getAccountRequest
    err := ctx.ShouldBindUri(&request)
    if err != nil {
        ctx.JSON(http.StatusBadRequest, errorResponse(err))
        return
    }

    account, err := server.store.GetAccount(ctx, request.ID) //<-called here
    if err == sql.ErrNoRows {
        ctx.JSON(http.StatusNotFound, errorResponse(err))
        return
    } else if err != nil {
        ctx.JSON(http.StatusInternalServerError, errorResponse(err))
        return
    }
    ctx.JSON(http.StatusOK, account)
}

Edit 2:

For clarity, when I say

An ErrNoRows error should be returned

I state this because of the call to row.Scan which should produce the error.

Documentation:

func (r *Row) Scan(dest ...any) error

Scan copies the columns from the matched row into the values pointed at by dest. See the documentation on Rows.Scan for details. If more than one row matches the query, Scan uses the first row and discards the rest. If no row matches the query, Scan returns ErrNoRows.

SSMSJM
  • 149
  • 6
  • 1
    Can you please show how you are calling `GetAccount` and checking for errors? The function, as generated by `sqlc`, looks fine so more information is needed to enable us to duplicate the issue (aim for a [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example)). You may want to start by adding some temporary logging (output `id` and `err` and ensure the values are as you expect; often this kind of issue is caused by an oversight elsewhere in the code). – Brits Oct 03 '22 at 04:16
  • if you check the docs (`go doc sql.DB.QueryRowContext`), returning empty values is normal behavior. In your case even more so because you are not returning a pointer, but a struct initialized with empty values. I've used this many times and it does return an error as well. Post the full code with error checks as well – Mihai Oct 03 '22 at 04:32
  • @Brits As requested I have added the code that calls GetAccount to my original post. I've not added debug printing, but I have stepped through the code and inspected values to see that the returned `err` is indeed nil contrary to my understanding of the documentation and how it should work. – SSMSJM Oct 03 '22 at 08:45
  • @Mihai It is actually the call to row.Scan that should produce the error, not QueryRowContext. (`go doc sql.Scan`) – SSMSJM Oct 03 '22 at 08:46
  • @SSMSJM It's what the docs are saying and I never said anything else. the error is differed to Scan... – Mihai Oct 03 '22 at 08:55
  • 1
    @SSMSJM is `server.store` of type `*Queries`? or is it some kind of wrapper in between? Is `q.db` an `*sql.DB` instance? or is it some wrapper around `*sql.DB`? What driver are you using? and what version of the driver are you using? For debugging you should try implementing a simple `main` program that connects to the same db using `database/sql` and the driver of your choice, and then execute the raw sql query using `*sql.DB` directly, passing it a known-to-not-exist id, without any of the other code that's getting in the way. And then see what that results in. – mkopriva Oct 03 '22 at 13:07

1 Answers1

1

You are overwriting the sql error:

    account, err := server.store.GetAccount(ctx, request.ID) //<-called here
    err = ctx.ShouldBindUri(&request)
    if err == sql.ErrNoRows {

You should check the error immediately after the GetAccount call:

    account, err := server.store.GetAccount(ctx, request.ID) //<-called here
    if err == sql.ErrNoRows {
Mihai
  • 9,526
  • 2
  • 18
  • 40
  • A very dumb mistake. Thanks for pointing it out. – SSMSJM Oct 03 '22 at 08:56
  • Sorry, I had to take away accepted answer, because the dumb mistake I made was not actually my code. It was when I tried to edit and slim down the code to keep it concise, I somehow messed it up. I have updated my question with the full actual function unedited. The problem still exits. I'll leave the"useful" vote as your answer was indeed correct for the code I had originally posted. – SSMSJM Oct 03 '22 at 09:11
  • so what is the error that you are actually getting? or do you get err nil? – Mihai Oct 03 '22 at 09:45
  • no actual error, just the unexpected nil `err` – SSMSJM Oct 03 '22 at 10:08
  • can you remove everything but the SELECT statement from the query? so no `--name and one:`? – Mihai Oct 03 '22 at 10:14
  • lines starting with double dash in SQL are comments, so it is just the select query. Removing the comment makes no difference to the result :) – SSMSJM Oct 03 '22 at 10:18