Skip to content

Commit

Permalink
custom NullBytea type for riverdatabasesql
Browse files Browse the repository at this point in the history
As #650 illustrates, the riverdatabasesql driver has issues with
encoding arrays of byte arrays (`bytea[]`) and just byte arrays in
general because the underlying pq implementation used by sqlc doesn't
differentiate between nil vs empty slices. Fix this with a custom type.

Fixes #650.
  • Loading branch information
bgentry committed Feb 2, 2025
1 parent 83a6720 commit 547ac05
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- `riverdatabasesql` driver: properly handle `nil` values in `bytea[]` inputs. This fixes the driver's handling of empty unique keys on insert for non-unique jobs with the newer unique jobs implementation. [PR #739](https://github.com/riverqueue/river/pull/739).

## [0.16.0] - 2024-01-27

### Added
Expand Down
4 changes: 2 additions & 2 deletions riverdriver/riverdatabasesql/internal/dbsqlc/river_job.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions riverdriver/riverdatabasesql/internal/dbsqlc/sqlc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ sql:
ttl: "TTL"

overrides:
- db_type: "bytea"
go_type:
import: "github.com/riverqueue/river/riverdriver/riverdatabasesql/internal/pgtypealias"
type: "NullBytea"

# `database/sql` really does not play nicely with json/jsonb. If it's
# left as `[]byte` or `json.RawMessage`, `database/sql` will try to
# encode it as binary (with a \x) which Postgres won't accept as
Expand Down
39 changes: 39 additions & 0 deletions riverdriver/riverdatabasesql/internal/pgtypealias/null_bytea.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package pgtypealias

import (
"database/sql/driver"
"fmt"
)

// NullBytea is a custom type for PostgreSQL bytea that returns SQL NULL when
// the underlying slice is nil or empty. This override takes over for the base
// type `bytea`, so that when sqlc generates code for arrays of bytea, each
// element is a NullBytea and properly handles nil values. This is in contrast
// to the default behavior of pq.Array in this scenario.
//
// See https://github.com/riverqueue/river/issues/650 for more information.
type NullBytea []byte //nolint:recvcheck

// Value implements the driver.Valuer interface. It returns nil when the
// underlying slice is nil or empty, ensuring that missing values are sent as
// SQL NULL.
func (nb NullBytea) Value() (driver.Value, error) {
if len(nb) == 0 {
return nil, nil //nolint:nilnil
}
return []byte(nb), nil
}

// Scan implements the sql.Scanner interface.
func (nb *NullBytea) Scan(src interface{}) error {
if src == nil {
*nb = nil
return nil
}
b, ok := src.([]byte)
if !ok {
return fmt.Errorf("nullBytea.Scan: got %T, expected []byte", src)
}
*nb = append((*nb)[0:0], b...)
return nil
}
4 changes: 2 additions & 2 deletions riverdriver/riverdatabasesql/river_database_sql_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func (e *Executor) JobInsertFastMany(ctx context.Context, params []*riverdriver.
ScheduledAt: make([]time.Time, len(params)),
State: make([]string, len(params)),
Tags: make([]string, len(params)),
UniqueKey: make([][]byte, len(params)),
UniqueKey: make([]pgtypealias.NullBytea, len(params)),
UniqueStates: make([]pgtypealias.Bits, len(params)),
}
now := time.Now().UTC()
Expand Down Expand Up @@ -270,7 +270,7 @@ func (e *Executor) JobInsertFastManyNoReturning(ctx context.Context, params []*r
ScheduledAt: make([]time.Time, len(params)),
State: make([]dbsqlc.RiverJobState, len(params)),
Tags: make([]string, len(params)),
UniqueKey: make([][]byte, len(params)),
UniqueKey: make([]pgtypealias.NullBytea, len(params)),
UniqueStates: make([]pgtypealias.Bits, len(params)),
}
now := time.Now().UTC()
Expand Down

0 comments on commit 547ac05

Please sign in to comment.