Skip to content
Databases

Databases

Steward runs on SQLite, MySQL and PostgreSQL. steward new takes the engine as a flag:

steward new myadmin --module example.com/myadmin --db postgres

Everything the framework does — grids, filters, relation paths, uploads, migrations, the search index — works the same on all three. What follows is the short list of places where the engine is still visible.

Quick search is case-insensitive everywhere

A grid’s search box and a LIKE filter match regardless of case. MySQL gets that from its collation and SQLite from its ASCII case folding; on PostgreSQL, where LIKE is case-sensitive, Steward emits ILIKE instead. Searching riverton finds Riverton on every engine.

Any column type, not only text

QuickSearch and Like read a column of any type — uuid, numeric, timestamptz — and not only the text ones:

type MediaFile struct {
    ID           string `gorm:"type:uuid;primaryKey"`
    SubmissionID string `gorm:"type:uuid;index"`
    FieldKey     string `gorm:"size:64"`
}

g.QuickSearch("SubmissionID", "FieldKey")   // both are searched

PostgreSQL defines its pattern operators for text alone and casts nothing to it implicitly, so Steward casts the column there: CAST(col AS TEXT) ILIKE ?. Searching part of a uuid finds the row. The other engines compare the column as it is.

Equality is not cast — SubmissionID:6f1e7c7e-… still compares uuid = uuid and still uses the index.

Note

A pattern search cannot use a btree index on any engine: %term% has no prefix to seek on, and PostgreSQL’s case-insensitive ILIKE rules out the varchar_pattern_ops index that would otherwise serve term%. On a table large enough for that to matter, give the panel a search engine — quick search and the palette go through it instead, and the SQL fallback is what runs only when none is configured.

PostgreSQL: set the timezone in your DSN

PostgreSQL stores time.Time as timestamptz and reads a bare date — the 2026-07-31 a date filter compares against — in the session’s timezone. A server session on UTC behind an application running on WIB moves every date boundary by seven hours, so “on the 31st” quietly means “from 07:00 on the 31st to 07:00 on the 1st”.

Name the application’s zone in the DSN and the two agree:

dsn := "postgres://user:pass@localhost:5432/myadmin?sslmode=disable&TimeZone=Asia/Jakarta"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})

Use the IANA name (Asia/Jakarta), not the abbreviation (WIB) — the server rejects the latter.

PostgreSQL: AutoMigrate wants the owning model first

If you use AutoMigrate for your own tables rather than migrations, pass the model that declares a many2many before the models on the other side of it:

db.AutoMigrate(&Article{}, &Tag{})   // Article declares the join table

The join table carries foreign keys to both sides, and PostgreSQL refuses to create it before the tables it points at exist. SQLite accepts either order, so a schema built by AutoMigrate can work locally and fail on deployment. Migrations do not have this problem: they run in the order you wrote them.

What Steward stores

The framework’s own tables (users, roles, permissions, settings, jobs, the search index) use types every engine has. There is no engine-specific column in the framework, so steward migrate runs unchanged on all three.