Files
Bridge-and-Join-s/internal/lkgateway/fancydb.go
T
fontvielle 50e420f4a0 DEPO на Firebird в мастере: подготовка базы одним действием, как для MSSQL
Мастер, шаг «Учёт DEPO», и «Обмен с DEPO» в настройках: СУБД «Firebird» (Fancy на Firebird 2.5).
- Подключение: драйвер nakagami/firebirdsql, путь к файлу базы или псевдоним, WIN1251. Запросы bj к DEPO
  (T-SQL) подключение переводит само (fancyfb.go): механически (dbo., TOP, ISNULL, GETDATE, LEN, N, exec)
  вне строк и комментариев, точные тексты — fsql/fb.go (каталоги, LIKE [_], COUNT(*) OVER, SELECT без FROM,
  PATINDEX); @имена — позиционные ?, GUID — в верхнем регистре. Системные запросы из кода — в fsql/depocatalog.go.
- «Подготовить» логином SYSDBA (depo_setup_fb.go): проверка базы и UDF Fancy, логин SYSDBA_BJ (Fancy даёт права
  на документы логинам SYSDBA%; для Firebird — обычный пользователь), оператор-робот Fancy, кодировщик M2M,
  привязки FTV_M2M_SETTINGS по признакам Fancy, таблицы, процедуры, права; обработку запускает bj.
  «Получить скрипт» — то же для isql в WIN1251; письмо администратору, установка процедур, плашка обновления.
- Скрипты Firebird встроены (depoprocs/firebird, тест сверяет с deploy/); количества — DOUBLE PRECISION: из
  диалекта 3 в базу диалекта 1 процедуру с NUMERIC(18,7) не создать.
- Проверено на копии базы Fancy (отдельный экземпляр bj): мастер с нуля — все проверки, BJ_PING; тестовый режим
  DEPO — К1 Бубликов, зачисление и списание до исполнения без ошибок; очередь, справочник НРД.
  Тест FBLive готовит на базе все запросы bj к DEPO: 94 из 101, оставшиеся 7 — ЛК и ручной ввод (следующий этап).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
2026-10-27 18:55:02 +03:00

144 lines
4.3 KiB
Go

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
_ "github.com/nakagami/firebirdsql" // драйвер "firebirdsql" для Firebird
)
// Подключение к серверу учётной базы клиента Fancy DEPO — MSSQL, PostgreSQL или Firebird,
// 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"
}
if s.IsFirebird() {
return "firebirdsql"
}
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 {
if s.IsFirebird() {
return fbDSN(s, db)
}
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)
}
if s.IsFirebird() && db == "" { // служебной базы у Firebird нет: подключение к серверу — это подключение к базе
db = s.DepoDB()
}
conn, err := fancyOpenGated(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()"
}
if s.IsFirebird() {
verQuery = "SELECT 'Firebird ' || RDB$GET_CONTEXT('SYSTEM', 'ENGINE_VERSION') FROM RDB$DATABASE"
}
_ = 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
}