// Package service — schema migration support. // Mirrors: odoo/modules/migration.py (safe subset) package service import ( "context" "fmt" "log" "github.com/jackc/pgx/v5/pgxpool" "odoo-go/pkg/orm" ) // MigrateSchema compares registered model fields with existing database columns // and adds any missing columns. This is a safe, additive-only migration: // it does NOT remove columns, change types, or drop tables. // // Mirrors: odoo/modules/loading.py _auto_init() — the part that adds new // columns when a model gains a field after the initial CREATE TABLE. func MigrateSchema(ctx context.Context, pool *pgxpool.Pool) error { tx, err := pool.Begin(ctx) if err != nil { return fmt.Errorf("migrate: begin: %w", err) } defer tx.Rollback(ctx) added := 0 for _, m := range orm.Registry.Models() { if m.IsAbstract() { continue } // Check if the table exists at all var tableExists bool err := tx.QueryRow(ctx, `SELECT EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_name = $1 AND table_schema = 'public' )`, m.Table()).Scan(&tableExists) if err != nil || !tableExists { continue // Table doesn't exist yet; InitDatabase will create it } // Get existing columns for this table existing := make(map[string]bool) rows, err := tx.Query(ctx, `SELECT column_name FROM information_schema.columns WHERE table_name = $1 AND table_schema = 'public'`, m.Table()) if err != nil { continue } for rows.Next() { var col string rows.Scan(&col) existing[col] = true } rows.Close() // Add missing columns for _, f := range m.StoredFields() { if f.Name == "id" { continue } if existing[f.Column()] { continue } sqlType := f.SQLType() if sqlType == "" { continue } alter := fmt.Sprintf(`ALTER TABLE %q ADD COLUMN %q %s`, m.Table(), f.Column(), sqlType) if _, err := tx.Exec(ctx, alter); err != nil { log.Printf("migrate: warning: add column %s.%s: %v", m.Table(), f.Column(), err) } else { log.Printf("migrate: added column %s.%s (%s)", m.Table(), f.Column(), sqlType) added++ } } } if err := tx.Commit(ctx); err != nil { return fmt.Errorf("migrate: commit: %w", err) } if added > 0 { log.Printf("migrate: %d column(s) added", added) } else { log.Println("migrate: schema up to date") } return nil }