package handler import ( "context" "database/sql" "fmt" "strings" "time" _ "github.com/go-sql-driver/mysql" _ "github.com/jackc/pgx/v5/stdlib" "github.com/redis/go-redis/v9" "sql-platform/internal/model" ) // testDBConnection tests a real database connection based on db_type. func testDBConnection(instance *model.Instance) (bool, string) { password, err := decryptPassword(instance.Password) if err != nil { return false, fmt.Sprintf("Password decryption failed: %v", err) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() switch instance.DBType { case "mysql": return testMySQL(ctx, instance.Host, instance.Port, instance.Username, password) case "postgresql", "postgres", "pg": dbname := "" if instance.Database != nil { dbname = *instance.Database } return testPostgreSQL(ctx, instance.Host, instance.Port, instance.Username, password, dbname) case "redis": return testRedis(ctx, instance.Host, instance.Port, password) default: return false, fmt.Sprintf("Unsupported database type: %s", instance.DBType) } } func testMySQL(ctx context.Context, host string, port int, user, pass string) (bool, string) { dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/?timeout=10s&parseTime=true", user, pass, host, port) db, err := sql.Open("mysql", dsn) if err != nil { return false, fmt.Sprintf("Connection failed: %v", err) } defer db.Close() if err := db.PingContext(ctx); err != nil { return false, fmt.Sprintf("Connection failed: %v", err) } var ver string db.QueryRowContext(ctx, "SELECT VERSION()").Scan(&ver) return true, fmt.Sprintf("MySQL connection successful (version: %s)", ver) } func testPostgreSQL(ctx context.Context, host string, port int, user, pass, dbname string) (bool, string) { if dbname == "" { dbname = "postgres" } dsn := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable&connect_timeout=10", user, pass, host, port, dbname) db, err := sql.Open("pgx", dsn) if err != nil { return false, fmt.Sprintf("Connection failed: %v", err) } defer db.Close() if err := db.PingContext(ctx); err != nil { return false, fmt.Sprintf("Connection failed: %v", err) } var ver string db.QueryRowContext(ctx, "SELECT version()").Scan(&ver) return true, fmt.Sprintf("PostgreSQL connection successful (version: %s)", ver) } func testRedis(ctx context.Context, host string, port int, pass string) (bool, string) { addr := fmt.Sprintf("%s:%d", host, port) rdb := redis.NewClient(&redis.Options{ Addr: addr, Password: pass, DB: 0, DialTimeout: 10 * time.Second, ReadTimeout: 10 * time.Second, }) defer rdb.Close() pong, err := rdb.Ping(ctx).Result() if err != nil { return false, fmt.Sprintf("Connection failed: %v", err) } var version string if info, err := rdb.Info(ctx, "server").Result(); err == nil { for _, line := range strings.Split(info, "\n") { line = strings.TrimRight(line, "\r") if strings.HasPrefix(line, "redis_version:") { version = strings.TrimPrefix(line, "redis_version:") break } } } if version != "" { return true, fmt.Sprintf("Redis connection successful (PING=%s, version: %s)", pong, version) } return true, fmt.Sprintf("Redis connection successful (PING=%s)", pong) } // getDB opens a real database connection and returns *sql.DB func getDBConnection(instance *model.Instance, dbName string) (*sql.DB, error) { password, err := decryptPassword(instance.Password) if err != nil { return nil, fmt.Errorf("password decryption failed: %v", err) } switch instance.DBType { case "mysql": dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?timeout=10s&parseTime=true", instance.Username, password, instance.Host, instance.Port, dbName) return sql.Open("mysql", dsn) case "postgresql", "postgres", "pg": if dbName == "" { dbName = "postgres" } dsn := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable&connect_timeout=10", instance.Username, password, instance.Host, instance.Port, dbName) return sql.Open("pgx", dsn) default: return nil, fmt.Errorf("unsupported database type: %s", instance.DBType) } } // listDatabases queries the actual databases from the instance func listDatabases(instance *model.Instance) ([]string, error) { db, err := getDBConnection(instance, "") if err != nil { return nil, err } defer db.Close() switch instance.DBType { case "mysql": rows, err := db.Query("SHOW DATABASES") if err != nil { return nil, err } defer rows.Close() var databases []string for rows.Next() { var name string rows.Scan(&name) // Filter out system databases if name != "information_schema" && name != "performance_schema" && name != "mysql" && name != "sys" { databases = append(databases, name) } } return databases, nil case "postgresql", "postgres", "pg": dbConn, err := getDBConnection(instance, "postgres") if err != nil { return nil, err } defer dbConn.Close() rows, err := dbConn.Query("SELECT datname FROM pg_database WHERE datistemplate = false") if err != nil { return nil, err } defer rows.Close() var databases []string for rows.Next() { var name string rows.Scan(&name) databases = append(databases, name) } return databases, nil default: return nil, fmt.Errorf("unsupported type: %s", instance.DBType) } } // listTables queries the actual tables from a database func listTables(instance *model.Instance, dbName string) ([]map[string]interface{}, error) { db, err := getDBConnection(instance, dbName) if err != nil { return nil, err } defer db.Close() switch instance.DBType { case "mysql": rows, err := db.Query("SELECT TABLE_NAME, TABLE_TYPE, TABLE_ROWS, DATA_LENGTH FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? ORDER BY TABLE_NAME", dbName) if err != nil { return nil, err } defer rows.Close() var tables []map[string]interface{} for rows.Next() { var name, tblType string var rows_ int var dataLen int64 rows.Scan(&name, &tblType, &rows_, &dataLen) tables = append(tables, map[string]interface{}{ "name": name, "type": tblType, "rows": rows_, "data_length": dataLen, }) } return tables, nil case "postgresql", "postgres", "pg": rows, err := db.Query("SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename") if err != nil { return nil, err } defer rows.Close() var tables []map[string]interface{} for rows.Next() { var name string rows.Scan(&name) tables = append(tables, map[string]interface{}{ "name": name, "type": "BASE TABLE", }) } return tables, nil default: return nil, fmt.Errorf("unsupported type: %s", instance.DBType) } }