Extending Bun with custom types

Bun uses database/sql to work with different DBMS and so you can extend it with custom types using sql.Scanner and driver.Valuer interfaces.

In this tutorial we will write a simple type to work with time that does not have a date:

const timeFormat = "15:04:05.999999999"

type Time struct {
	time.Time
}

sql.Scanner

sql.Scanneropen in new window assigns a value from a database driver. The value can be one of the following types:

  • int64
  • float64
  • bool
  • []byte
  • string
  • time.Time
  • nil - for NULL values
var _ sql.Scanner = (*Time)(nil)

// Scan scans the time parsing it if necessary using timeFormat.
func (tm *Time) Scan(src interface{}) (err error) {
	switch src := src.(type) {
	case time.Time:
		*tm = NewTime(src)
		return nil
	case string:
		tm.Time, err = time.ParseInLocation(timeFormat, src, time.UTC)
		return err
	case []byte:
		tm.Time, err = time.ParseInLocation(timeFormat, string(src), time.UTC)
		return err
	case nil:
		tm.Time = time.Time{}
		return nil
	default:
		return fmt.Errorf("unsupported data type: %T", src)
	}
}

You can find the full example at GitHubopen in new window.

driver.Valuer

driver.Valueropen in new window returns a value for a database driver. The value must be one of the following types:

  • int64
  • float64
  • bool
  • []byte
  • string
  • time.Time
  • nil - for NULL values
var _ driver.Valuer = (*Time)(nil)

// Scan scans the time parsing it if necessary using timeFormat.
func (tm Time) Value() (driver.Value, error) {
	return tm.UTC().Format(timeFormat), nil
}

You can find the full example at GitHubopen in new window.

Conclusion

You can easily extend Bun with custom types to fully utilize your DBMS capabilities, for example, bunbigopen in new window adds support for big.Int to Bun.

See also: