EVO provides a first-class PostgreSQL driver via lib/pgsql. It integrates with GORM and the EVO schema migration system, with full support for schemas, advisory locking, and constraint introspection.
import (
"github.com/getevo/evo/v2"
"github.com/getevo/evo/v2/lib/pgsql"
)package main
import (
"github.com/getevo/evo/v2"
"github.com/getevo/evo/v2/lib/pgsql"
)
func main() {
if err := evo.Setup(pgsql.Driver{}); err != nil {
panic(err)
}
evo.Run()
}Database:
Enabled: true
Type: postgres
Server: "localhost:5432" # host:port
Username: "postgres"
Password: "secret"
Database: "myapp"
Schema: "public" # PostgreSQL schema (default: public)
SSLMode: "disable" # disable | require
Params: "" # extra DSN params appended verbatim
Debug: 3 # 1:silent 2:warn 3:error 4:info
MaxOpenConns: "100"
MaxIdleConns: "10"
ConnMaxLifTime: "1h"
SlowQueryThreshold: "500ms"| Value | Meaning |
|---|---|
disable (default) |
No TLS |
require or true |
Require TLS |
Run separate instances (processes) each pointing to a different schema:
# Tenant A
Database:
Schema: "tenant_a"
# Tenant B
Database:
Schema: "tenant_b"The EVO migration system, DDL generation, and all queries automatically use the configured schema.
Implements db.Driver. Pass it to evo.Setup():
evo.Setup(pgsql.Driver{})The driver:
- Builds the DSN from
DriverConfig. - Opens a GORM connection using
gorm.io/driver/postgres. - Registers
PGDialectfor schema-aware DDL and migration.
Registers the PostgreSQL dialect without opening a connection. Use this in standalone migration scripts or tests where you manage the connection yourself:
package main
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
"github.com/getevo/evo/v2/lib/pgsql"
"github.com/getevo/evo/v2/lib/db/schema"
)
func main() {
pgsql.RegisterDialect() // register dialect without opening connection
db, _ := gorm.Open(postgres.Open("host=localhost user=postgres ..."), &gorm.Config{})
schema.UseModels(&MyModel{})
// Generate migration SQL
stmts := pgsql.Driver{}.GetMigrationScript(db)
for _, s := range stmts {
println(s)
}
}PGDialect implements schema.Dialect and provides PostgreSQL-specific behaviour:
| Method | Description |
|---|---|
Name() |
Returns "postgres" |
Quote(name) |
Wraps identifier in double quotes: "tablename" |
GetCurrentDatabase(db) |
Runs SELECT current_database() |
GetTableVersion(db, database, table) |
Reads version from pg_class comment |
SetTableVersionSQL(table, version) |
COMMENT ON TABLE ... IS '...' |
GetJoinConstraints(db, database) |
Introspects foreign keys via pg_constraint |
GenerateMigration(...) |
Generates full CREATE/ALTER DDL |
AcquireMigrationLock(db) |
pg_advisory_lock(hashtext(...)) |
ReleaseMigrationLock(db) |
pg_advisory_unlock(...) |
BootstrapHistoryTable(db) |
Creates schema_migration table if absent |
The migration system acquires a PostgreSQL advisory lock before running migrations. This prevents concurrent migration runs in multi-replica deployments:
SELECT pg_advisory_lock(hashtext('schema_migration_lock'))
-- ... run migrations ...
SELECT pg_advisory_unlock(hashtext('schema_migration_lock'))
Automatically created on first migration:
CREATE TABLE IF NOT EXISTS "schema_migration" (
"id" BIGSERIAL PRIMARY KEY,
"hash" CHAR(32) NOT NULL,
"status" VARCHAR(10) NOT NULL,
"executed_queries" INT NOT NULL DEFAULT 0,
"error_message" TEXT,
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:255;not null"`
Email string `gorm:"size:255;uniqueIndex"`
CreatedAt time.Time
UpdatedAt time.Time
}Register the model and run migrations:
package main
import (
"github.com/getevo/evo/v2"
"github.com/getevo/evo/v2/lib/db/schema"
"github.com/getevo/evo/v2/lib/pgsql"
)
func main() {
evo.Setup(pgsql.Driver{})
schema.UseModels(&User{})
evo.Run() // pass --migration-do to execute migrations
}Run with migration flag:
./myapp --migration-do # apply migrations
./myapp --migration-dry-run # print SQL without executing
./myapp --migration-dump # dump CREATE TABLE DDLAfter evo.Setup(pgsql.Driver{}) you access the database through the db package or evo.GetDBO():
import (
"github.com/getevo/evo/v2/lib/db"
)
// Insert
user := User{Name: "Alice", Email: "alice@example.com"}
db.Create(&user)
// Query
var users []User
db.Where("name = ?", "Alice").Find(&users)
// Update
db.Model(&user).Update("name", "Alice Smith")
// Delete
db.Delete(&user)
// Raw SQL with PostgreSQL syntax
var count int64
db.Raw(`SELECT COUNT(*) FROM "users" WHERE "email" LIKE ?`, "%@example.com").Scan(&count)import "github.com/getevo/evo/v2/lib/db/types"
type Product struct {
ID uint `gorm:"primaryKey"`
Name string
Metadata types.JSON `gorm:"type:jsonb"`
Tags types.JSONSlice `gorm:"type:jsonb"`
}import "github.com/getevo/evo/v2/lib/db/types"
type Order struct {
ID types.UUID `gorm:"type:uuid;default:gen_random_uuid();primaryKey"`
Reference string
}import "github.com/getevo/evo/v2/lib/db/types"
type Post struct {
ID uint
Tags types.Strings `gorm:"type:text[]"`
}Use db.Ping and db.HealthCheck to integrate with the EVO health check system:
import (
"context"
"github.com/getevo/evo/v2"
"github.com/getevo/evo/v2/lib/db"
)
func main() {
evo.Setup(pgsql.Driver{})
// Liveness: fast ping
evo.OnHealthCheck(func() error {
return db.Ping(context.Background(), db.GetInstance())
})
// Readiness: wait for DB on startup
evo.OnReadyCheck(func() error {
return db.WaitForDB(context.Background(), db.GetInstance(), 5, time.Second)
})
evo.Run()
}See health-checks.md and database.md for full details.
- Verify
Server,Username,Password,Databasevalues. - Check
SSLMode— many local setups needdisable. - Confirm the PostgreSQL server is reachable from the application host.
- The
Schemafield defaults topublic. If you use a custom schema, create it first:CREATE SCHEMA tenant_a;
If the application crashed mid-migration the advisory lock may remain. Release it manually:
SELECT pg_advisory_unlock(hashtext('schema_migration_lock'));