if d == nil {
return errNilPtr
}
- *d = append((*d)[:0], s...)
+ *d = rows.setrawbuf(append(rows.rawbuf(), s...))
return nil
}
case []byte:
if d == nil {
return errNilPtr
}
- *d = s.AppendFormat((*d)[:0], time.RFC3339Nano)
+ *d = rows.setrawbuf(s.AppendFormat(rows.rawbuf(), time.RFC3339Nano))
return nil
}
case decimalDecompose:
}
case *RawBytes:
sv = reflect.ValueOf(src)
- if b, ok := asBytes([]byte(*d)[:0], sv); ok {
- *d = RawBytes(b)
+ if b, ok := asBytes(rows.rawbuf(), sv); ok {
+ *d = rows.setrawbuf(b)
return nil
}
case *bool:
{"time", time.Unix(2, 5).UTC(), "1970-01-01T00:00:02.000000005Z"},
}
- buf := make(RawBytes, 10)
+ var buf RawBytes
+ rows := &Rows{}
test := func(name string, in any, want string) {
- if err := convertAssign(&buf, in); err != nil {
+ if err := convertAssignRows(&buf, in, rows); err != nil {
t.Fatalf("%s: convertAssign = %v", name, err)
}
match := len(buf) == len(want)
n := testing.AllocsPerRun(100, func() {
for _, tt := range tests {
+ rows.raw = rows.raw[:0]
test(tt.name, tt.in, tt.want)
}
})
// and gc. With 32-bit words there are more convT2E allocs, and
// with gccgo, only pointers currently go in interface data.
// So only care on amd64 gc for now.
- measureAllocs := runtime.GOARCH == "amd64" && runtime.Compiler == "gc"
+ measureAllocs := false
+ switch runtime.GOARCH {
+ case "amd64", "arm64":
+ measureAllocs = runtime.Compiler == "gc"
+ }
if n > 0.5 && measureAllocs {
t.Fatalf("allocs = %v; want 0", n)
// not to be called concurrently.
lastcols []driver.Value
+ // raw is a buffer for RawBytes that persists between Scan calls.
+ // This is used when the driver returns a mismatched type that requires
+ // a cloning allocation. For example, if the driver returns a *string and
+ // the user is scanning into a *RawBytes, we need to copy the string.
+ // The raw buffer here lets us reuse the memory for that copy across Scan calls.
+ raw []byte
+
// closemuScanHold is whether the previous call to Scan kept closemu RLock'ed
// without unlocking it. It does that when the user passes a *RawBytes scan
// target. In that case, we need to prevent awaitDone from closing the Rows
return rs.lasterrOrErrLocked(nil)
}
+// rawbuf returns the buffer to append RawBytes values to.
+// This buffer is reused across calls to Rows.Scan.
+//
+// Usage:
+//
+// rawBytes = rows.setrawbuf(append(rows.rawbuf(), value...))
+func (rs *Rows) rawbuf() []byte {
+ if rs == nil {
+ // convertAssignRows can take a nil *Rows; for simplicity handle it here
+ return nil
+ }
+ return rs.raw
+}
+
+// setrawbuf updates the RawBytes buffer with the result of appending a new value to it.
+// It returns the new value.
+func (rs *Rows) setrawbuf(b []byte) RawBytes {
+ if rs == nil {
+ // convertAssignRows can take a nil *Rows; for simplicity handle it here
+ return RawBytes(b)
+ }
+ off := len(rs.raw)
+ rs.raw = b
+ return RawBytes(rs.raw[off:])
+}
+
var errRowsClosed = errors.New("sql: Rows are closed")
var errNoRows = errors.New("sql: no Rows available")
if scanArgsContainRawBytes(dest) {
rs.closemuScanHold = true
+ rs.raw = rs.raw[:0]
} else {
rs.closemu.RUnlock()
}
}
}
+// Issue #65201.
+//
+// If a RawBytes is reused across multiple queries,
+// subsequent queries shouldn't overwrite driver-owned memory from previous queries.
+func TestRawBytesReuse(t *testing.T) {
+ db := newTestDB(t, "people")
+ defer closeDB(t, db)
+
+ if _, err := db.Exec("USE_RAWBYTES"); err != nil {
+ t.Fatal(err)
+ }
+
+ var raw RawBytes
+
+ // The RawBytes in this query aliases driver-owned memory.
+ rows, err := db.Query("SELECT|people|name|")
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows.Next()
+ rows.Scan(&raw) // now raw is pointing to driver-owned memory
+ name1 := string(raw)
+ rows.Close()
+
+ // The RawBytes in this query does not alias driver-owned memory.
+ rows, err = db.Query("SELECT|people|age|")
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows.Next()
+ rows.Scan(&raw) // this must not write to the driver-owned memory in raw
+ rows.Close()
+
+ // Repeat the first query. Nothing should have changed.
+ rows, err = db.Query("SELECT|people|name|")
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows.Next()
+ rows.Scan(&raw) // raw points to driver-owned memory again
+ name2 := string(raw)
+ rows.Close()
+ if name1 != name2 {
+ t.Fatalf("Scan read name %q, want %q", name2, name1)
+ }
+}
+
// badConn implements a bad driver.Conn, for TestBadDriver.
// The Exec method panics.
type badConn struct{}