Skip to content
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

database/sql: add Null[T] #60677

Closed
wants to merge 9 commits into from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/next/60370.txt
@@ -0,0 +1,5 @@
pkg database/sql, method (*Null[$0]) Scan(interface{}) error #60370
pkg database/sql, method (Null[$0]) Value() (driver.Value, error) #60370
pkg database/sql, type Null[$0 interface{}] struct #60370
pkg database/sql, type Null[$0 interface{}] struct, Valid bool #60370
pkg database/sql, type Null[$0 interface{}] struct, V $0 #60370
33 changes: 33 additions & 0 deletions src/database/sql/sql.go
Expand Up @@ -391,6 +391,39 @@ func (n NullTime) Value() (driver.Value, error) {
return n.Time, nil
}

// Null represents a value that may be null.
// Null implements the Scanner interface so
// it can be used as a scan destination:
//
// var s Null[string]
// err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&s)
// ...
// if s.Valid {
// // use s.V
// } else {
// // NULL value
// }
type Null[T any] struct {
V T
Valid bool
}

func (n *Null[T]) Scan(value any) error {
if value == nil {
n.V, n.Valid = *new(T), false
return nil
}
n.Valid = true
return convertAssign(&n.V, value)
}

func (n Null[T]) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return n.V, nil
}

// Scanner is an interface used by Scan.
type Scanner interface {
// Scan assigns a value from a database driver.
Expand Down
17 changes: 15 additions & 2 deletions src/database/sql/sql_test.go
Expand Up @@ -1803,6 +1803,18 @@ func TestNullStringParam(t *testing.T) {
nullTestRun(t, spec)
}

func TestGenericNullStringParam(t *testing.T) {
spec := nullTestSpec{"nullstring", "string", [6]nullTestRow{
{Null[string]{"aqua", true}, "", Null[string]{"aqua", true}},
{Null[string]{"brown", false}, "", Null[string]{"", false}},
{"chartreuse", "", Null[string]{"chartreuse", true}},
{Null[string]{"darkred", true}, "", Null[string]{"darkred", true}},
{Null[string]{"eel", false}, "", Null[string]{"", false}},
{"foo", Null[string]{"black", false}, nil},
}}
nullTestRun(t, spec)
}

func TestNullInt64Param(t *testing.T) {
spec := nullTestSpec{"nullint64", "int64", [6]nullTestRow{
{NullInt64{31, true}, 1, NullInt64{31, true}},
Expand Down Expand Up @@ -1916,8 +1928,9 @@ func nullTestRun(t *testing.T, spec nullTestSpec) {
}

// Can't put null val into non-null col
if _, err := stmt.Exec(6, "bob", spec.rows[5].nullParam, spec.rows[5].notNullParam); err == nil {
t.Errorf("expected error inserting nil val with prepared statement Exec")
row5 := spec.rows[5]
if _, err := stmt.Exec(6, "bob", row5.nullParam, row5.notNullParam); err == nil {
t.Errorf("expected error inserting nil val with prepared statement Exec: NULL=%#v, NOT-NULL=%#v", row5.nullParam, row5.notNullParam)
}

_, err = db.Exec("INSERT|t|id=?,name=?,nullf=?", 999, nil, nil)
Expand Down