You've already forked go-sqlmock
mirror of
https://github.com/DATA-DOG/go-sqlmock.git
synced 2025-06-29 00:31:35 +02:00
Invalidate memory scanned into sql.RawBytes
The intention of sql.RawBytes is for it to hold memory owned by the database. When used, it's content is only valid until the `Next`, `Scan` or `Close` is called on the `Rows` To ensure that we meet this behaviour, when `[]byte` is used in a column, it's value is copied to a buffer that we keep track of for later invalidation. By doing this, incorrect use of `sql.RawBytes` values is exposed in tests that use go-sqlmock. Without this, when a real database is used and it's driver does share memory, then those issues would not be exposed until runtime (and in non-obvious ways)
This commit is contained in:
@ -3,6 +3,8 @@
|
||||
package sqlmock
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
@ -90,3 +92,114 @@ func TestQueryMultiRows(t *testing.T) {
|
||||
t.Errorf("there were unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryRowBytesInvalidatedByNext_jsonRawMessageIntoRawBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
replace := []byte(invalid)
|
||||
rows := NewRows([]string{"raw"}).
|
||||
AddRow(json.RawMessage(`{"thing": "one", "thing2": "two"}`)).
|
||||
AddRow(json.RawMessage(`{"that": "foo", "this": "bar"}`))
|
||||
scan := func(rs *sql.Rows) ([]byte, error) {
|
||||
var raw sql.RawBytes
|
||||
return raw, rs.Scan(&raw)
|
||||
}
|
||||
want := []struct {
|
||||
Initial []byte
|
||||
Replaced []byte
|
||||
}{
|
||||
{Initial: []byte(`{"thing": "one", "thing2": "two"}`), Replaced: replace[:len(replace)-6]},
|
||||
{Initial: []byte(`{"that": "foo", "this": "bar"}`), Replaced: replace[:len(replace)-9]},
|
||||
}
|
||||
queryRowBytesInvalidatedByNext(t, rows, scan, want)
|
||||
}
|
||||
|
||||
func TestQueryRowBytesNotInvalidatedByNext_jsonRawMessageIntoBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
rows := NewRows([]string{"raw"}).
|
||||
AddRow(json.RawMessage(`{"thing": "one", "thing2": "two"}`)).
|
||||
AddRow(json.RawMessage(`{"that": "foo", "this": "bar"}`))
|
||||
scan := func(rs *sql.Rows) ([]byte, error) {
|
||||
var b []byte
|
||||
return b, rs.Scan(&b)
|
||||
}
|
||||
want := [][]byte{[]byte(`{"thing": "one", "thing2": "two"}`), []byte(`{"that": "foo", "this": "bar"}`)}
|
||||
queryRowBytesNotInvalidatedByNext(t, rows, scan, want)
|
||||
}
|
||||
|
||||
func TestQueryRowBytesNotInvalidatedByNext_bytesIntoCustomBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
rows := NewRows([]string{"raw"}).
|
||||
AddRow([]byte(`one binary value with some text!`)).
|
||||
AddRow([]byte(`two binary value with even more text than the first one`))
|
||||
scan := func(rs *sql.Rows) ([]byte, error) {
|
||||
type customBytes []byte
|
||||
var b customBytes
|
||||
return b, rs.Scan(&b)
|
||||
}
|
||||
want := [][]byte{[]byte(`one binary value with some text!`), []byte(`two binary value with even more text than the first one`)}
|
||||
queryRowBytesNotInvalidatedByNext(t, rows, scan, want)
|
||||
}
|
||||
|
||||
func TestQueryRowBytesNotInvalidatedByNext_jsonRawMessageIntoCustomBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
rows := NewRows([]string{"raw"}).
|
||||
AddRow(json.RawMessage(`{"thing": "one", "thing2": "two"}`)).
|
||||
AddRow(json.RawMessage(`{"that": "foo", "this": "bar"}`))
|
||||
scan := func(rs *sql.Rows) ([]byte, error) {
|
||||
type customBytes []byte
|
||||
var b customBytes
|
||||
return b, rs.Scan(&b)
|
||||
}
|
||||
want := [][]byte{[]byte(`{"thing": "one", "thing2": "two"}`), []byte(`{"that": "foo", "this": "bar"}`)}
|
||||
queryRowBytesNotInvalidatedByNext(t, rows, scan, want)
|
||||
}
|
||||
|
||||
func TestQueryRowBytesNotInvalidatedByClose_bytesIntoCustomBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
rows := NewRows([]string{"raw"}).AddRow([]byte(`one binary value with some text!`))
|
||||
scan := func(rs *sql.Rows) ([]byte, error) {
|
||||
type customBytes []byte
|
||||
var b customBytes
|
||||
return b, rs.Scan(&b)
|
||||
}
|
||||
queryRowBytesNotInvalidatedByClose(t, rows, scan, []byte(`one binary value with some text!`))
|
||||
}
|
||||
|
||||
func TestQueryRowBytesInvalidatedByClose_jsonRawMessageIntoRawBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
replace := []byte(invalid)
|
||||
rows := NewRows([]string{"raw"}).AddRow(json.RawMessage(`{"thing": "one", "thing2": "two"}`))
|
||||
scan := func(rs *sql.Rows) ([]byte, error) {
|
||||
var raw sql.RawBytes
|
||||
return raw, rs.Scan(&raw)
|
||||
}
|
||||
want := struct {
|
||||
Initial []byte
|
||||
Replaced []byte
|
||||
}{
|
||||
Initial: []byte(`{"thing": "one", "thing2": "two"}`),
|
||||
Replaced: replace[:len(replace)-6],
|
||||
}
|
||||
queryRowBytesInvalidatedByClose(t, rows, scan, want)
|
||||
}
|
||||
|
||||
func TestQueryRowBytesNotInvalidatedByClose_jsonRawMessageIntoBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
rows := NewRows([]string{"raw"}).AddRow(json.RawMessage(`{"thing": "one", "thing2": "two"}`))
|
||||
scan := func(rs *sql.Rows) ([]byte, error) {
|
||||
var b []byte
|
||||
return b, rs.Scan(&b)
|
||||
}
|
||||
queryRowBytesNotInvalidatedByClose(t, rows, scan, []byte(`{"thing": "one", "thing2": "two"}`))
|
||||
}
|
||||
|
||||
func TestQueryRowBytesNotInvalidatedByClose_jsonRawMessageIntoCustomBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
rows := NewRows([]string{"raw"}).AddRow(json.RawMessage(`{"thing": "one", "thing2": "two"}`))
|
||||
scan := func(rs *sql.Rows) ([]byte, error) {
|
||||
type customBytes []byte
|
||||
var b customBytes
|
||||
return b, rs.Scan(&b)
|
||||
}
|
||||
queryRowBytesNotInvalidatedByClose(t, rows, scan, []byte(`{"thing": "one", "thing2": "two"}`))
|
||||
}
|
||||
|
Reference in New Issue
Block a user