- Improved auto-generated list/form/search views with priority fields, two-column form layout, statusbar widget, notebook for O2M fields - Enhanced fields_get with currency_field, compute, related metadata - Fixed session handling: handleSessionInfo/handleSessionCheck use real session from cookie instead of hardcoded values - Added read_progress_bar and activity_format RPC stubs - Improved bootstrap translations with lang_parameters - Added "contacts" to session modules list Server starts successfully: 14 modules, 93 models, 378 XML templates, 503 JS modules transpiled — all from local frontend/ directory. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
96 lines
2.3 KiB
Go
96 lines
2.3 KiB
Go
// 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
|
|
}
|