Sphire Hydra Documentation
import "github.com/sphireinc/Hydra/hydra"
Hydra populates tagged struct fields from a single database row.
Typical flow:
hydra tagshydra.HydratableInitpackage main
import (
"database/sql"
"log"
"github.com/sphireinc/Hydra/hydra"
_ "github.com/mattn/go-sqlite3"
)
type Person struct {
ID int `hydra:"id,pk"`
Email string `hydra:"email,lookup"`
Name string `hydra:"name"`
hydra.Hydratable
}
func (Person) HydraTableName() string {
return "person"
}
func main() {
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
log.Fatal(err)
}
defer db.Close()
_, err = db.Exec(`
CREATE TABLE person (
id INTEGER PRIMARY KEY,
email TEXT NOT NULL,
name TEXT NOT NULL
);
INSERT INTO person (id, email, name)
VALUES (1, 'alice@example.com', 'Alice');
`)
if err != nil {
log.Fatal(err)
}
person := &Person{}
person.Init(person)
person.XDBTypeOverride = "sqlite"
if err := person.HydrateByPrimaryKey(db, 1); err != nil {
log.Fatal(err)
}
}
Hydra expects an addressable struct pointer.
person := &Person{}
person.Init(person)
Calling Init on a non-pointer struct value is not the intended usage.
Hydra resolves the table name in this order:
XTableNameOverrideHydraTableName() stringHydra supports these database handle types:
*sql.DB
*pgx.Conn
Use XDBTypeOverride to route the fetcher.
Examples:
obj.XDBTypeOverride = "sqlite"
obj.XDBTypeOverride = "mysql"
obj.XDBTypeOverride = "postgres"
obj.XDBTypeOverride = "cockroachdb"
If no matching row exists, Hydra returns:
hydra.ErrNotFound
If hydration is attempted with an empty where map, Hydra returns:
hydra.ErrEmptyWhereClause
Hydra validates table names and column names before building SQL.
Only simple identifiers are accepted.
type Person struct {
ID int `hydra:"id"`
Name string `hydra:"name"`
}
type Person struct {
ID int `hydra:"id,pk"`
}
This enables:
err := person.HydrateByPrimaryKey(db, 1)
type Person struct {
Email string `hydra:"email,lookup"`
}
This enables:
person := &Person{Email: "alice@example.com"}
person.Init(person)
err := person.HydrateByLookup(db)
Hydra supports built-in conversion for:
stringboolNULL is supported for:
Attempting to place NULL into a non-nullable scalar field returns an error.
Hydra supports two converter models.
If the field implements:
HydraConvert(src any) error
Hydra uses it before built-in primitive conversion.
Example:
type RFC3339Time struct {
time.Time
}
func (t *RFC3339Time) HydraConvert(src any) error {
switch v := src.(type) {
case string:
parsed, err := time.Parse(time.RFC3339, v)
if err != nil {
return err
}
t.Time = parsed
return nil
case []byte:
parsed, err := time.Parse(time.RFC3339, string(v))
if err != nil {
return err
}
t.Time = parsed
return nil
default:
return fmt.Errorf("unsupported value %T", src)
}
}
A struct can provide converters keyed by field name or column name:
HydraConverters() map[string]hydra.HydraFieldConverter
This is useful for:
Hydra exposes context-aware calls:
HydrateContext(ctx, db, whereClauses)
HydrateByPrimaryKeyContext(ctx, db, value)
HydrateByLookupContext(ctx, db)
FetchContext(ctx, db, tableName, columns, whereClauses)
Use them when the caller needs cancellation or deadlines.
Common errors include:
hydra.ErrNotInitializedhydra.ErrEmptyWhereClausehydra.ErrNotFoundExample:
if err := person.HydrateByPrimaryKey(db, 42); err != nil {
switch {
case errors.Is(err, hydra.ErrNotFound):
// row missing
case errors.Is(err, hydra.ErrNotInitialized):
// Init was not called
default:
// other conversion, validation, or database error
}
}
person := &Person{}
person.Init(person)
person.XDBTypeOverride = "sqlite"
err := person.Hydrate(db, map[string]interface{}{
"id": 1,
})
person := &Person{}
person.Init(person)
person.XDBTypeOverride = "sqlite"
err := person.HydrateByPrimaryKey(db, 1)
person := &Person{
Email: "alice@example.com",
}
person.Init(person)
person.XDBTypeOverride = "sqlite"
err := person.HydrateByLookup(db)
Unit tests:
make test
make test-hydra
Full Docker-backed functional suite:
make test-func