feat(11-01): add lagoon.OnDatabase and after-commit transactions

- OnDatabase runs a callback once the database is published (now, or when
  lagoon.Publish runs), so GORM callbacks registered at Boot also install
  under serve, where Boot runs before the database is opened
- Transaction runs AfterCommit callbacks in order after a successful commit;
  nested calls are savepoints whose callbacks drop with them
- the lagoon:after_commit GORM callback flushes single-statement AfterCommit
  work after GORM's own commit; outside a transaction it runs immediately
This commit is contained in:
Jakub Zych
2026-09-29 15:25:51 +02:00
parent b319e7cc61
commit 6c1f94e57c
6 changed files with 612 additions and 5 deletions

View File

@@ -21,8 +21,9 @@ const (
// Open pings dsn through pgx stdlib, requires Postgres 16 ICU pl-PL, and
// returns that exact *sql.DB plus a GORM handle opened on it.
//
// Phase 11 owns a separate pgxpool.Pool for River LISTEN/NOTIFY. Do not
// create that listener pool here; application queries share this *sql.DB.
// The job worker in the conga module opens its own single-connection
// pgxpool.Pool for River LISTEN/NOTIFY. Do not create that listener pool
// here; application queries, including River's, share this *sql.DB.
func Open(ctx context.Context, dsn string) (*sql.DB, *gorm.DB, error) {
dsn = strings.TrimSpace(dsn)
if dsn == "" {
@@ -76,6 +77,9 @@ func gormFromSQL(sqlDB *sql.DB) (*gorm.DB, error) {
if got != sqlDB {
return nil, fmt.Errorf("lagoon: GORM is not using the shared *sql.DB")
}
if err := registerAfterCommit(gdb); err != nil {
return nil, fmt.Errorf("lagoon: register %s: %w", AfterCommitCallback, err)
}
return gdb, nil
}
@@ -106,7 +110,8 @@ func DSN(cfg *compass.Config) string {
}
// Publish stores the shared SQL pool and GORM handle on the app. Both
// handles refer to the same *sql.DB.
// handles refer to the same *sql.DB. It then runs the callbacks queued by
// OnDatabase and returns the first error.
func Publish(app *backpack.App, sqlDB *sql.DB, gdb *gorm.DB) error {
if app == nil {
return fmt.Errorf("lagoon: app is nil")
@@ -117,7 +122,10 @@ func Publish(app *backpack.App, sqlDB *sql.DB, gdb *gorm.DB) error {
if err := app.Publish(sqlDB); err != nil {
return err
}
return app.Publish(gdb)
if err := app.Publish(gdb); err != nil {
return err
}
return runDatabaseHooks(app, sqlDB, gdb)
}
// CheckLocale fails unless the connected database uses ICU locale pl-PL.