diff options
| author | Stefan Majewsky <majewsky@gmx.net> | 2026-05-08 16:52:45 +0200 |
|---|---|---|
| committer | Stefan Majewsky <majewsky@gmx.net> | 2026-05-08 16:52:46 +0200 |
| commit | f79d61a9eef42a05aca6f4ddb8d95192549036a5 (patch) | |
| tree | 3ff619b4d42195de6fd80c1f36bd218b479ec834 /select.go | |
| parent | dadb36a1f73d39948e4989a3ab4333a61f70ee7a (diff) | |
| download | go-oblast-f79d61a9eef42a05aca6f4ddb8d95192549036a5.tar.gz | |
allow both None or ErrNoRows in SelectOne{,Where}
The None-returning style is more convenient when the application needs
to genuinely behave differently in this case, e.g. returning 404 from an
HTTP API endpoint (instead of 500 for a generic database error).
The ErrNoRows-returning style is more convenient when control flow is
not different for this case vs. other error cases.
Diffstat (limited to 'select.go')
| -rw-r--r-- | select.go | 68 |
1 files changed, 48 insertions, 20 deletions
@@ -160,11 +160,11 @@ func collectRow(rows *sql.Rows, plan plan, v reflect.Value, slots []any, indexes // SelectOne executes the provided SQL query and fills an instance of the record type R if there is exactly one row in the result set, // according to the column names reported by the database as part of the result set. // -// If there are no rows in the result set, [None] is returned. +// If there are no rows in the result set, [sql.ErrNoRows] is returned. // // Warning: Because of limitations in the interface of database/sql, this function is built on [Store.Select] and cannot be any faster than it. // For maximum performance, use [Store.SelectOneWhere] which avoids the overhead of potentially having to read multiple rows. -func (s Store[R]) SelectOne(ctx context.Context, db Handle, query string, args ...any) (Option[R], error) { +func (s Store[R]) SelectOne(ctx context.Context, db Handle, query string, args ...any) (R, error) { // NOTE: This function body should be as short as possible to reduce the binary size after monomorphization. // Any expression that does not depend on type R should be factored out into a reusable function. // @@ -174,6 +174,24 @@ func (s Store[R]) SelectOne(ctx context.Context, db Handle, query string, args . results, err := s.Select(ctx, db, query, args...) switch { case err != nil: + var zero R + return zero, err + case len(results) == 0: + var zero R + return zero, sql.ErrNoRows + default: + return results[0], nil + } +} + +// SelectOneOrNone is like SelectOne, but returns [None] instead of [sql.ErrNoRows]. +func (s Store[R]) SelectOneOrNone(ctx context.Context, db Handle, query string, args ...any) (Option[R], error) { + // NOTE: This function body should be as short as possible to reduce the binary size after monomorphization. + // Any expression that does not depend on type R should be factored out into a reusable function. + + results, err := s.Select(ctx, db, query, args...) + switch { + case err != nil: return None[R](), err case len(results) == 0: return None[R](), nil @@ -187,20 +205,21 @@ func (s Store[R]) SelectOne(ctx context.Context, db Handle, query string, args . // // This method is more efficient than [Store.SelectOne] on CPU runtime, but has a slight memory allocation overhead per call from query preparation. // This can be avoided by using [Store.PrepareSelectQueryWhere] instead. -func (s Store[R]) SelectOneWhere(ctx context.Context, db Handle, partialQuery string, args ...any) (Option[R], error) { +func (s Store[R]) SelectOneWhere(ctx context.Context, db Handle, partialQuery string, args ...any) (R, error) { // NOTE: This function body should be as short as possible to reduce the binary size after monomorphization. // Any expression that does not depend on type R should be factored out into a reusable function. var result R err := selectOneWhere(ctx, db, s.plan, reflect.ValueOf(&result).Elem(), partialQuery, args) - switch { - case err == nil: - return Some(result), nil - case errors.Is(err, sql.ErrNoRows): - return None[R](), nil - default: - return None[R](), err - } + return result, err +} + +// SelectOneOrNoneWhere is like SelectOneWhere, but returns [None] instead of [sql.ErrNoRows]. +func (s Store[R]) SelectOneOrNoneWhere(ctx context.Context, db Handle, partialQuery string, args ...any) (Option[R], error) { + // NOTE: This function body should be as short as possible to reduce the binary size after monomorphization. + // Any expression that does not depend on type R should be factored out into a reusable function. + + return noRowsToNone(s.SelectOneWhere(ctx, db, partialQuery, args...)) } func selectOneWhere(ctx context.Context, db Handle, plan plan, v reflect.Value, partialQuery string, args []any) error { @@ -223,6 +242,17 @@ func selectOne(ctx context.Context, db Handle, plan plan, v reflect.Value, query return db.QueryRowContext(ctx, query, args...).Scan(slots...) } +func noRowsToNone[R any](record R, err error) (Option[R], error) { + switch { + case err == nil: + return Some(record), nil + case errors.Is(err, sql.ErrNoRows): + return None[R](), nil + default: + return None[R](), err + } +} + // PrepareSelectQueryWhere performs the same query string preparation as [Store.SelectWhere] or [Store.SelectOneWhere]. // The resulting query can then be executed multiple times without incurring repeated memory allocation overhead from this preparation step. func (s Store[R]) PrepareSelectQueryWhere(partialQuery string) (PreparedSelectQuery[R], error) { @@ -283,18 +313,16 @@ func (q PreparedSelectQuery[R]) Select(ctx context.Context, db Handle, args ...a } // SelectOne behaves the same as [Store.SelectOneWhere], but uses the query that was precomputed when q was constructed. -func (q PreparedSelectQuery[R]) SelectOne(ctx context.Context, db Handle, args ...any) (Option[R], error) { +func (q PreparedSelectQuery[R]) SelectOne(ctx context.Context, db Handle, args ...any) (R, error) { // NOTE: This function body should be as short as possible to reduce the binary size after monomorphization. // Any expression that does not depend on type R should be factored out into a reusable function. var result R err := selectOne(ctx, db, q.store.plan, reflect.ValueOf(&result).Elem(), q.query, args) - switch { - case err == nil: - return Some(result), nil - case errors.Is(err, sql.ErrNoRows): - return None[R](), nil - default: - return None[R](), err - } + return result, err +} + +// SelectOneOrNone is like SelectOne, but returns [None] instead of [sql.ErrNoRows]. +func (q PreparedSelectQuery[R]) SelectOneOrNone(ctx context.Context, db Handle, args ...any) (Option[R], error) { + return noRowsToNone(q.SelectOne(ctx, db, args...)) } |
