jmoiron/sqlx · error

sql: RawBytes isn't allowed on Row.Scan

Error message

sql: RawBytes isn't allowed on Row.Scan

What it means

Row.Scan (sqlx's wrapper) rejects *sql.RawBytes destinations because database/sql forbids RawBytes in single-row Scan: RawBytes points into memory owned by the driver that is only valid between calls to Rows.Next, and Row.Scan closes the rows immediately after scanning, so the bytes would dangle. sqlx enforces the same rule up-front with this explicit error.

Source

Thrown at sqlx.go:198

	}

	// TODO(bradfitz): for now we need to defensively clone all
	// []byte that the driver returned (not permitting
	// *RawBytes in Rows.Scan), since we're about to close
	// the Rows in our defer, when we return from this function.
	// the contract with the driver.Next(...) interface is that it
	// can return slices into read-only temporary memory that's
	// only valid until the next Scan/Close.  But the TODO is that
	// for a lot of drivers, this copy will be unnecessary.  We
	// should provide an optional interface for drivers to
	// implement to say, "don't worry, the []bytes that I return
	// from Next will not be modified again." (for instance, if
	// they were obtained from the network anyway) But for now we
	// don't care.
	defer r.rows.Close()
	for _, dp := range dest {
		if _, ok := dp.(*sql.RawBytes); ok {
			return errors.New("sql: RawBytes isn't allowed on Row.Scan")
		}
	}

	if !r.rows.Next() {
		if err := r.rows.Err(); err != nil {
			return err
		}
		return sql.ErrNoRows
	}
	err := r.rows.Scan(dest...)
	if err != nil {
		return err
	}
	// Make sure the query can be processed to completion with no errors.
	if err := r.rows.Close(); err != nil {
		return err
	}
	return nil

View on GitHub (pinned to 41dac167fd)

Solutions

  1. Change the field to []byte or sql.NullString instead of *sql.RawBytes
  2. Query with db.QueryRow(...).Scan into a []byte and assign it to the struct manually
  3. Keep two struct types: one with RawBytes for Rows-based scanning, one with []byte for Get
  4. Load the blob with a plain Query and scan from the live Rows before Close

Example fix

// before
type Doc struct { Body *sql.RawBytes }
row := db.QueryRowx("SELECT body FROM docs WHERE id=$1", id)
row.StructScan(&doc) // panics/errors
// after
type Doc struct { Body []byte }
row := db.QueryRowx("SELECT body FROM docs WHERE id=$1", id)
row.StructScan(&doc)
Defensive patterns

Strategy: type-guard

Validate before calling

func checkNoRawBytes(dest interface{}) error {
    v := reflect.Indirect(reflect.ValueOf(dest))
    if v.Kind() == reflect.Struct {
        for i := 0; i < v.NumField(); i++ {
            if v.Field(i).Type() == reflect.TypeOf((*sql.RawBytes)(nil)) {
                return fmt.Errorf("field %s is *sql.RawBytes; not allowed in Row.Scan", v.Type().Field(i).Name)
            }
        }
    }
    return nil
}

Type guard

func isRawBytesField(f reflect.StructField) bool {
    return f.Type == reflect.TypeOf((*sql.RawBytes)(nil))
}

Try / catch

err := row.StructScan(&doc)
if err != nil {
    if err.Error() == "sql: RawBytes isn't allowed on Row.Scan" {
        return fmt.Errorf("replace RawBytes field with []byte: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling row.StructScan/scanAny (via Get) with a destination struct or slice containing a *sql.RawBytes field, e.g. db.Get(&s, "SELECT blob FROM t") where s has a RawBytes field.

Common situations: Optimizing scans of large blob/bytea columns with RawBytes after reading database/sql docs; shared struct types used both in Rows scanning (where RawBytes is legal) and single-row Get (where it is not).

Related errors


AI-assisted analysis of jmoiron/sqlx@41dac167fd (2026-09-03). Data as JSON: /api/errors/2dcba8da9700a82a. Report an issue: GitHub.