package lkgateway import ( "context" "database/sql" "fmt" "net/url" "strings" "time" _ "github.com/jackc/pgx/v5/stdlib" // драйвер "pgx" для PostgreSQL _ "github.com/microsoft/go-mssqldb" // драйвер "sqlserver" для MSSQL ) // Подключение к серверу учётной базы клиента Fancy DEPO — MSSQL или PostgreSQL, // SQL-логин. База выбирается в настройках (DepoDB). // validDBName — безопасное имя БД/идентификатора MSSQL (буквы/цифры/_), чтобы // не дать инъекции в CREATE DATABASE (имя БД нельзя параметризовать). func validDBName(s string) bool { if s == "" || len(s) > 64 { return false } for i, c := range s { ok := c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '_' || (i > 0 && c >= '0' && c <= '9') if !ok { return false } } return true } // fancyDriver возвращает имя database/sql-драйвера по типу СУБД. func fancyDriver(s FancyMSSQLSettings) string { if s.IsPostgres() { return "pgx" } return "sqlserver" } // fancyAdminDB — имя служебной БД для создания целевой: master (MSSQL) / // postgres (PostgreSQL). Подключаемся к ней, чтобы выполнить CREATE DATABASE. func fancyAdminDB(s FancyMSSQLSettings) string { if s.IsPostgres() { return "postgres" } return "master" } // fancyDSN строит строку подключения. db="" → к служебной БД (для CREATE DATABASE). func fancyDSN(s FancyMSSQLSettings, db string) string { port := s.DefaultPort() if s.IsPostgres() { ssl := s.Encrypt switch ssl { case "", "disable", "false": ssl = "disable" case "true": // в UI значение из MSSQL-набора — мапим в требование TLS ssl = "require" } q := url.Values{} q.Set("sslmode", ssl) q.Set("connect_timeout", "10") u := &url.URL{ Scheme: "postgres", User: url.UserPassword(s.User, s.Password), Host: fmt.Sprintf("%s:%d", s.Server, port), Path: "/" + db, RawQuery: q.Encode(), } return u.String() } // MSSQL enc := s.Encrypt if enc == "" { enc = "disable" // по умолчанию без TLS (типовой LAN-сценарий) } q := url.Values{} if db != "" { q.Set("database", db) } q.Set("encrypt", enc) q.Set("connection timeout", "10") u := &url.URL{ Scheme: "sqlserver", User: url.UserPassword(s.User, s.Password), Host: fmt.Sprintf("%s:%d", s.Server, port), RawQuery: q.Encode(), } return u.String() } // fancyOpen открывает пул подключений к указанной БД (или служебной при db=""). func fancyOpen(s FancyMSSQLSettings, db string) (*sql.DB, error) { if s.IsPostgres() && db == "" { db = fancyAdminDB(s) } conn, err := sql.Open(fancyDriver(s), fancyDSN(s, db)) if err != nil { return nil, err } conn.SetConnMaxLifetime(5 * time.Minute) conn.SetMaxOpenConns(4) return conn, nil } // fancyPing проверяет соединение и возвращает версию SQL Server. func fancyPing(ctx context.Context, s FancyMSSQLSettings, db string) (string, error) { conn, err := fancyOpen(s, db) if err != nil { return "", err } defer conn.Close() cctx, cancel := context.WithTimeout(ctx, 12*time.Second) defer cancel() if err := conn.PingContext(cctx); err != nil { return "", err } var ver string verQuery := "SELECT @@VERSION" if s.IsPostgres() { verQuery = "SELECT version()" } _ = conn.QueryRowContext(cctx, verQuery).Scan(&ver) ver = strings.TrimSpace(ver) if i := strings.IndexAny(ver, "\r\n"); i > 0 { ver = ver[:i] } return ver, nil }