Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/database/sql/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ func convertAssign(dest, src any) error {
return convertAssignRows(dest, src, nil)
}

// ignoreNullValues determines whether to ignore null values in SQL
var ignoreNullValues = false

// convertAssignRows copies to dest the value in src, converting it if possible.
// An error is returned if the copy would result in loss of information.
// dest should be a pointer type. If rows is passed in, the rows will
Expand Down Expand Up @@ -314,6 +317,10 @@ func convertAssignRows(dest, src any, rows *Rows) error {
}
*d = nil
return nil
default:
if ignoreNullValues {
return nil
}
}
// The driver is returning a cursor the client may iterate over.
case driver.Rows:
Expand Down
86 changes: 86 additions & 0 deletions src/database/sql/sql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,7 @@ func TestCursorFake(t *testing.T) {
}

func TestInvalidNilValues(t *testing.T) {

var date1 time.Time
var date2 int

Expand Down Expand Up @@ -1546,6 +1547,91 @@ func TestInvalidNilValues(t *testing.T) {
}
}

func TestValidNilValues(t *testing.T) {
ignoreNullValues = true
defer func() { ignoreNullValues = false }()

var date1 time.Time
var int1 int
var float1 float64
var uint1 uint
var string1 string
var bool1 bool
type testStruct struct {
name string
}
var struct1 testStruct
var int2 int
int2 = 17

tests := []struct {
name string
input any
}{
{
name: "date",
input: &date1,
},
{
name: "int",
input: &int1,
},
{
name: "initialized int",
input: &int2,
},
{
name: "float",
input: &float1,
},
{
name: "uint",
input: &uint1,
},
{
name: "string",
input: &string1,
},
{
name: "bool",
input: &bool1,
},
{
name: "struct",
input: &struct1,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
db := newTestDB(t, "people")
defer closeDB(t, db)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
conn, err := db.Conn(ctx)
if err != nil {
t.Fatal(err)
}
conn.dc.ci.(*fakeConn).skipDirtySession = true
defer conn.Close()

originalValue := tt.input
err = conn.QueryRowContext(ctx, "SELECT|people|bdate|age=?", 1).Scan(tt.input)
if err != nil {
t.Fatalf("expected no error when querying nil column, but get %s", err.Error())
} else if tt.input != originalValue {
t.Fatalf("expected null scan to preserve original value %v, but got %v", originalValue, tt.input)
}

err = conn.PingContext(ctx)
if err != nil {
t.Fatal(err)
}
})
}
}

func TestConnTx(t *testing.T) {
db := newTestDB(t, "people")
defer closeDB(t, db)
Expand Down