New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: database/sql: Rows.Scan should return lasterr if present #24431
Comments
I'll accept there is a documentation issue here at the least and there is possibly a reasonable code change.. Right now if rows.Scan produces an error, it isn't a good idea to necessarily just return that error and run. You'd really want to:
If Rows.Scan does not return an error, you may safely omit the Rows.Err check, but you shoud still check for an error from Rows.Close. Let's see what that would look like: for rows.Next() {
var id int64
err := rows.Scan(&id)
if err != nil {
if rows.Err() != nil {
return rows.Err()
}
rows.Close()
return err
}
}
if err := rows.Close(); err != nil {
return err
}
// Do work. or var scanErr error
for rows.Next() {
var id int64
scanErr = rows.Scan(&id)
if scanErr != nil {
break
}
}
if rows.Err() != nil {
return rows.Err()
}
if err := rows.Close(); err != nil {
return err
}
if scanErr != nil {
return scanErr
}
// Do work. With your proposal you could write: for rows.Next() {
var id int64
err := rows.Scan(&id)
if err != nil {
rows.Close() // We may be able to ignore this error.
return err // This would be either a scan error or Rows.Err(lasterr) from Rows.Next.
}
}
if err := rows.Close(); err != nil {
return err
}
// Do work. It does seem like it could simplify some code, or make existing code actually more useful. The downside someone may be (probably is) checking the text of rows.Scan's error and doing something with it. So this may be a compatibility issue. (Note, I would only be concerned about the close error if I was writing to the database while also reading from it.) |
I'd assume that calling Scan would always return a syntax/parsing/type casting failure from reading the value, and I might rewrap in a different way than I'd wrap rows.Err or rows.Close, which I would expect to be a connection failure or timeout. |
One of the motivations for this is that in the case of err := db.QueryRowContext(ctx, q).Scan(&id)
if err.Error() == "sql: Rows are closed" && ctx.Err() != nil {
return ctx.Err()
} else if err != nil {
return err
} Which could just be simplified to: err := db.QueryRowContext(ctx, q).Scan(&id)
if err != nil {
return err
} Rows Next->Scan->Err seems to have been built with a single-threaded use in mind, but with the additional of context timeouts and cancellations, other errors can happen between the successive calls. Scan already returns an error ( |
@erykwalder Your last comment isn't convincing; But I agree with you in general, I really see no reason not to return the lasterr if present on scan if Rows is closed. @bradfitz I don't think this will be a compatibility problem, but theoretically someone could be checking the error text from rows.Scan. Do you have an opinion on this? If you don't I lean to doing this. |
It does, but again, does not handle errors that can happen asynchronously or from separate routines (specifically a context being timed out or cancelled). This is demonstrated by a simple program which runs inconsistently: package main
import (
"context"
"database/sql"
"fmt"
"time"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres", "postgres:///test?sslmode=disable")
if err != nil {
panic(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Microsecond)
defer cancel()
err = db.QueryRowContext(ctx, `SELECT 1`).Scan(new(int))
if err != nil && err != context.DeadlineExceeded {
fmt.Println(err)
}
} With sufficient runs, it will print Looking at the source of Row.Scan, the problem is identifiable (comments are mine):
|
Let's try this and test it over the next 4 months and see. I think making Scan return the correct errors on context cancels makes sense. Returning "Rows are closed" when they were really just canceled is slightly misleading, even if they were closed by the context-cancel-waiting goroutine. |
@kardianos I'm happy to work on this if you don't already have something. I've already submitted a CLA fwiw. |
@erykwalder That would be fine with me. |
Change https://golang.org/cl/104276 mentions this issue: |
When a context has been cancelled or timed out,
Rows.Scan
will returnsql: Rows are closed
. It would be nice to return the result oflasterr
if it was notnil
and notio.EOF
, since an error was encountered between the call toRows.Next
andRows.Scan
. Additionally, forRow.Scan
, there is no interface to detect if there was an error that occurred betweenNext
andScan
, since only the results ofScan
are exposed.The caller could check
context.Err()
when it encounterssql: Rows are closed
, but it would be more convenient for Scan to return the error value.I believe it would be sufficient to add:
at the top of Scan, after:
rs.closemu.RLock()
The text was updated successfully, but these errors were encountered: