80 lines
2.2 KiB
Markdown
80 lines
2.2 KiB
Markdown
# Struct Scanning and NULLable Columns
|
|
|
|
## Struct Scanning with sqlx
|
|
|
|
Tag struct fields with `db:"column_name"` for sqlx:
|
|
|
|
```go
|
|
type User struct {
|
|
ID int64 `db:"id"`
|
|
Name string `db:"name"`
|
|
Email string `db:"email"`
|
|
DeletedAt *time.Time `db:"deleted_at"` // NULLable
|
|
}
|
|
|
|
// Single row
|
|
var user User
|
|
err := db.GetContext(ctx, &user, "SELECT id, name, email, deleted_at FROM users WHERE id = $1", id)
|
|
|
|
// Multiple rows
|
|
var users []User
|
|
err := db.SelectContext(ctx, &users, "SELECT id, name, email, deleted_at FROM users WHERE active = true")
|
|
```
|
|
|
|
## Struct Scanning with pgx
|
|
|
|
With pgx (v5+), use `pgx.CollectRows` for automatic struct mapping:
|
|
|
|
```go
|
|
rows, err := pool.Query(ctx, "SELECT id, name, email FROM users WHERE active = true")
|
|
if err != nil {
|
|
return fmt.Errorf("querying users: %w", err)
|
|
}
|
|
users, err := pgx.CollectRows(rows, pgx.RowToStructByName[User])
|
|
```
|
|
|
|
## JSON Marshaling
|
|
|
|
Struct tags for both database and JSON work together. Pointer fields marshal to `null` in JSON when NULL in the database:
|
|
|
|
```go
|
|
type User struct {
|
|
ID int64 `db:"id" json:"id"`
|
|
Name string `db:"name" json:"name"`
|
|
Email string `db:"email" json:"email"`
|
|
Bio *string `db:"bio" json:"bio,omitempty"` // NULL → omitted in JSON
|
|
DeletedAt *time.Time `db:"deleted_at" json:"deleted_at"` // NULL → null in JSON
|
|
}
|
|
```
|
|
|
|
## NULLable Columns
|
|
|
|
Three approaches, from most to least recommended:
|
|
|
|
**1. Pointer fields (recommended)** — clean, works with JSON marshaling:
|
|
|
|
```go
|
|
type User struct {
|
|
ID int64 `db:"id" json:"id"`
|
|
Name string `db:"name" json:"name"`
|
|
DeletedAt *time.Time `db:"deleted_at" json:"deleted_at"` // nil when NULL
|
|
}
|
|
// Check: if user.DeletedAt != nil { ... }
|
|
```
|
|
|
|
**2. `sql.NullXxx` types** or `sql.Null[T]` generic — explicit but verbose, requires custom JSON marshaling:
|
|
|
|
```go
|
|
type User struct {
|
|
ID int64 `db:"id"`
|
|
Bio sql.NullString `db:"bio"`
|
|
}
|
|
// Check: if user.Bio.Valid { use(user.Bio.String) }
|
|
```
|
|
|
|
**3. `COALESCE` in SQL** — moves NULL handling to the query:
|
|
|
|
```sql
|
|
SELECT id, COALESCE(bio, '') AS bio FROM users WHERE id = $1
|
|
```
|