package dbmigration import ( "context" "database/sql" "strings" "git.pcux.cn/xuans/Smail/config" "git.pcux.cn/xuans/Smail/db/migration" ) // 仅迁移 Smail 版本化业务表;不能静默忽略自定义对象。 func validatePortableObjects(ctx context.Context, database portableQuery, kind config.DatabaseType) error { queries := []string{"SELECT COUNT(*) FROM sqlite_master WHERE type IN ('view','trigger')"} switch kind { case config.DatabaseTypeMySQL: queries = []string{ "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE() AND (table_type <> 'BASE TABLE' OR engine <> 'InnoDB')", "SELECT COUNT(*) FROM information_schema.triggers WHERE trigger_schema=DATABASE()", "SELECT COUNT(*) FROM information_schema.events WHERE event_schema=DATABASE()", "SELECT COUNT(*) FROM information_schema.routines WHERE routine_schema=DATABASE()", "SELECT COUNT(*) FROM information_schema.table_constraints WHERE constraint_schema=DATABASE() AND constraint_type='FOREIGN KEY'", } case config.DatabaseTypePostgreSQL: queries = []string{ "SELECT COUNT(*) FROM pg_namespace WHERE nspname NOT IN ('public','information_schema') AND nspname NOT LIKE 'pg_%'", "SELECT COUNT(*) FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace WHERE n.nspname='public' AND c.relkind NOT IN ('r','i','S')", "SELECT COUNT(*) FROM pg_trigger t JOIN pg_class c ON c.oid=t.tgrelid JOIN pg_namespace n ON n.oid=c.relnamespace WHERE n.nspname='public' AND NOT t.tgisinternal", "SELECT COUNT(*) FROM pg_proc p JOIN pg_namespace n ON n.oid=p.pronamespace WHERE n.nspname='public'", "SELECT COUNT(*) FROM pg_constraint c JOIN pg_namespace n ON n.oid=c.connamespace WHERE n.nspname='public' AND c.contype IN ('f','c','x')", } } for _, query := range queries { rows, err := database.QueryContext(ctx, query) if err != nil { return ErrSQLUnsupportedObject } var count int valid := rows.Next() && rows.Scan(&count) == nil && count == 0 && rows.Err() == nil _ = rows.Close() if !valid { return ErrSQLUnsupportedObject } } if kind == config.DatabaseTypeSQLite { if err := validateSQLiteIndexes(ctx, database); err != nil { return err } } return nil } func validateSQLiteIndexes(ctx context.Context, database portableQuery) error { rows, err := database.QueryContext(ctx, "SELECT name, sql FROM sqlite_master WHERE type='index' AND sql IS NOT NULL") if err != nil { return ErrSQLUnsupportedObject } defer rows.Close() managed := migration.ManagedSQLiteIndexNames() for rows.Next() { var name string var definition sql.NullString if err := rows.Scan(&name, &definition); err != nil || !definition.Valid || strings.TrimSpace(definition.String) == "" { return ErrSQLUnsupportedObject } if _, ok := managed[name]; !ok { return ErrSQLUnsupportedObject } } if err := rows.Err(); err != nil { return ErrSQLUnsupportedObject } return nil }