ScanStructOne
Use ScanStructOne when a query is expected to return exactly one database row.
Use ScanStructOne when a query is expected to return exactly one row.
This is useful for lookups where the result should be unique.
Typical use cases include:
- loading a record by primary key
- loading a user by email
- checking a unique token
- reading one configuration row
- fetching a single aggregate result
- validating that a query returns only one result
Basic Idea
ScanStructOne scans one database row into *T.
user, err := mapper.ScanStructOne[User](rows)
if err != nil {
return err
}The result is a pointer to the scanned struct.
fmt.Println(user.Name)If no row is returned, mapper returns mapper.ErrNoRows.
If more than one row is returned, mapper returns mapper.ErrTooManyRows.
Define a Struct
Create a struct that represents the expected result row.
type User struct {
ID int64 `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
Active bool `db:"active"`
CreatedAt time.Time `db:"created_at"`
}Query One Row
Query rows using your database driver.
Even when expecting one row, use a rows-based query because mapper works with mapper.Rows.
rows, err := db.Query(`
SELECT *
FROM users
WHERE id = ?
LIMIT 1
`, id)
if err != nil {
return nil, err
}
defer rows.Close()
return mapper.ScanStructOne[User](rows)Complete Query Example
This example defines the model and a small repository-style function.
package main
import (
"database/sql"
"time"
"github.com/netlifeguru/mapper"
)
type User struct {
ID int64 `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
Active bool `db:"active"`
CreatedAt time.Time `db:"created_at"`
}
func getUserByID(db *sql.DB, id int) (*User, error) {
rows, err := db.Query(`
SELECT *
FROM users
WHERE id = ?
LIMIT 1
`, id)
if err != nil {
return nil, err
}
defer rows.Close()
return mapper.ScanStructOne[User](rows)
}Complete Usage Example
This example loads one user and handles the mapper.ErrNoRows case separately.
package main
import (
"errors"
"fmt"
"log"
"github.com/joho/godotenv"
_ "github.com/go-sql-driver/mysql"
"github.com/netlifeguru/mapper"
)
func main() {
err := godotenv.Load()
if err != nil {
log.Println(".env file not found, I'm using system env variables")
}
db, err := connectDB()
if err != nil {
log.Fatal(err)
}
defer db.Close()
user, err := getUserByID(db, 1)
if err != nil {
if errors.Is(err, mapper.ErrNoRows) {
log.Println("user not found")
return
}
log.Fatal(err)
}
fmt.Printf(
"ID: %d | Name: %s | Email: %s | Active: %t | Created: %s\n",
user.ID,
user.Name,
user.Email,
user.Active,
user.CreatedAt.Format("2006-01-02 15:04:05"),
)
}Handling No Rows
Use errors.Is to check for mapper.ErrNoRows.
user, err := mapper.ScanStructOne[User](rows)
if errors.Is(err, mapper.ErrNoRows) {
return nil
}
if err != nil {
return err
}
fmt.Println(user.Name)This is useful when “not found” is not a system error.
For example, an application can log the missing row and return normally.
if errors.Is(err, mapper.ErrNoRows) {
log.Println("user not found")
return
}Handling Too Many Rows
Use errors.Is to check for mapper.ErrTooManyRows.
user, err := mapper.ScanStructOne[User](rows)
if errors.Is(err, mapper.ErrTooManyRows) {
return errors.New("expected one user, got multiple")
}
if err != nil {
return err
}This usually means the query is not unique enough or the database contains unexpected duplicate data.
For unique lookups, prefer a unique column or primary key.
SELECT id, name, email
FROM users
WHERE email = ?If the query should only return one row, adding LIMIT 1 can protect the query shape, but it also hides duplicate data.
Use LIMIT 1 when you only need the first match.
Avoid LIMIT 1 when duplicates should be detected.
Required Lookup
Sometimes a missing row should be treated as an error.
func requireUserByID(db *sql.DB, id int) (*User, error) {
user, err := getUserByID(db, id)
if errors.Is(err, mapper.ErrNoRows) {
return nil, errors.New("user not found")
}
if err != nil {
return nil, err
}
return user, nil
}Optional Lookup
For optional lookups, return nil when the row is not found.
func optionalUserByID(db *sql.DB, id int) (*User, error) {
user, err := getUserByID(db, id)
if errors.Is(err, mapper.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, err
}
return user, nil
}When to Use ScanStructOne
Use ScanStructOne when:
- the query should return exactly one row
- zero rows should be handled explicitly
- multiple rows should be treated as an error
- the caller expects
*T - you are loading by a unique identifier
Good examples:
SELECT id, name, email FROM users WHERE id = ?SELECT id, name, email FROM users WHERE email = ?SELECT COUNT(*) AS total FROM usersWhen Not to Use It
Do not use ScanStructOne for normal list queries.
Use ScanStructSlice instead.
users, err := mapper.ScanStructSlice[User](rows)Do not use ScanStructOne if you intentionally want only the first row and do not care about duplicates.
In that case, make the query explicit with LIMIT 1, or handle the behavior at the SQL level.
Related Example
A standalone example is available in the examples repository: