aboutsummaryrefslogtreecommitdiff
path: root/src/database/sql/sql.go
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2025-07-23 14:26:54 -0700
committerGopher Robot <gobot@golang.org>2025-08-06 10:51:00 -0700
commit8a924caaf348fdc366bab906424616b2974ad4e9 (patch)
treeb44e10449fe99d3febc0bf2b87cde101080fe8ad /src/database/sql/sql.go
parent8fa31a2d7d9e60c50a3a94080c097b6e65773f4b (diff)
downloadgo-8a924caaf348fdc366bab906424616b2974ad4e9.tar.xz
[release-branch.go1.23] database/sql: avoid closing Rows while scan is in progress
A database/sql/driver.Rows can return database-owned data from Rows.Next. The driver.Rows documentation doesn't explicitly document the lifetime guarantees for this data, but a reasonable expectation is that the caller of Next should only access it until the next call to Rows.Close or Rows.Next. Avoid violating that constraint when a query is cancelled while a call to database/sql.Rows.Scan (note the difference between the two different Rows types!) is in progress. We previously took care to avoid closing a driver.Rows while the user has access to driver-owned memory via a RawData, but we could still close a driver.Rows while a Scan call was in the process of reading previously-returned driver-owned data. Update the fake DB used in database/sql tests to invalidate returned data to help catch other places we might be incorrectly retaining it. Updates #74831 Fixes #74832 Change-Id: Ice45b5fad51b679c38e3e1d21ef39156b56d6037 Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2540 Reviewed-by: Roland Shoemaker <bracewell@google.com> Reviewed-by: Neal Patel <nealpatel@google.com> Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2601 Reviewed-on: https://go-review.googlesource.com/c/go/+/693558 TryBot-Bypass: Dmitri Shuralyov <dmitshur@golang.org> Reviewed-by: Mark Freeman <markfreeman@google.com> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
Diffstat (limited to 'src/database/sql/sql.go')
-rw-r--r--src/database/sql/sql.go26
1 files changed, 12 insertions, 14 deletions
diff --git a/src/database/sql/sql.go b/src/database/sql/sql.go
index c247a9b506..3346ad48f1 100644
--- a/src/database/sql/sql.go
+++ b/src/database/sql/sql.go
@@ -3360,38 +3360,36 @@ func (rs *Rows) Scan(dest ...any) error {
// without calling Next.
return fmt.Errorf("sql: Scan called without calling Next (closemuScanHold)")
}
+
rs.closemu.RLock()
+ rs.raw = rs.raw[:0]
+ err := rs.scanLocked(dest...)
+ if err == nil && scanArgsContainRawBytes(dest) {
+ rs.closemuScanHold = true
+ } else {
+ rs.closemu.RUnlock()
+ }
+ return err
+}
+func (rs *Rows) scanLocked(dest ...any) error {
if rs.lasterr != nil && rs.lasterr != io.EOF {
- rs.closemu.RUnlock()
return rs.lasterr
}
if rs.closed {
- err := rs.lasterrOrErrLocked(errRowsClosed)
- rs.closemu.RUnlock()
- return err
- }
-
- if scanArgsContainRawBytes(dest) {
- rs.closemuScanHold = true
- rs.raw = rs.raw[:0]
- } else {
- rs.closemu.RUnlock()
+ return rs.lasterrOrErrLocked(errRowsClosed)
}
if rs.lastcols == nil {
- rs.closemuRUnlockIfHeldByScan()
return errors.New("sql: Scan called without calling Next")
}
if len(dest) != len(rs.lastcols) {
- rs.closemuRUnlockIfHeldByScan()
return fmt.Errorf("sql: expected %d destination arguments in Scan, not %d", len(rs.lastcols), len(dest))
}
for i, sv := range rs.lastcols {
err := convertAssignRows(dest[i], sv, rs)
if err != nil {
- rs.closemuRUnlockIfHeldByScan()
return fmt.Errorf(`sql: Scan error on column index %d, name %q: %w`, i, rs.rowsi.Columns()[i], err)
}
}