50e420f4a0
Мастер, шаг «Учёт 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
465 lines
23 KiB
Go
465 lines
23 KiB
Go
package lkgateway
|
||
|
||
// Подготовка базы DEPO на Firebird (Fancy на Firebird 2.5, 27.10.2026) — то же, что depo_setup.go для MSSQL:
|
||
// одним действием логином администратора (обычно SYSDBA, не сохраняется) или скриптом для администратора.
|
||
// Отличия от MSSQL:
|
||
// - логин bj — SYSDBA_BJ: Fancy даёт логинам SYSDBA% все права на документы (FU_HAVE_RIGHT: USER like 'SYSDBA%'),
|
||
// а EXECUTE AS, которым MSSQL-версия переходит на SYSDBA, в Firebird нет. Для Firebird это обычный
|
||
// пользователь сервера: права только выданные — чтение учёта, таблицы обмена, запуск FTV_M2M_JOB;
|
||
// - логин bj заводится оператором Fancy (OD_OPERATORS, признак «робот»): по оператору текущего пользователя
|
||
// Fancy выбирает депозитарий и нумерует документы (FU_NEXT_DOC_NUM);
|
||
// - обработку запускает bj (SQL Server Agent нет);
|
||
// - таблицы, процедуры и права — встроенные скрипты depoprocs/firebird (копия — deploy/depo/fancy-m2m-firebird);
|
||
// привязки к справочникам клиента (FTV_M2M_SETTINGS) bj определяет сам по признакам Fancy.
|
||
// Пароль Firebird 2.5 различает только по первым 8 знакам.
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"embed"
|
||
"fmt"
|
||
"regexp"
|
||
"strings"
|
||
"time"
|
||
|
||
"golang.org/x/text/encoding"
|
||
"golang.org/x/text/encoding/charmap"
|
||
)
|
||
|
||
//go:embed depoprocs/firebird/*.sql
|
||
var depoFBFS embed.FS
|
||
|
||
// depoFBScriptFiles — скрипты установки по порядку: таблицы, процедуры, права.
|
||
var depoFBScriptFiles = []string{"00_FTV_M2M_TABLES.sql", "10_FTV_M2M_PROCS.sql", "20_FTV_M2M_GRANTS.sql"}
|
||
|
||
// depoFBDefaultLogin — логин bj в Firebird (имя на SYSDBA — условие прав Fancy).
|
||
const depoFBDefaultLogin = "SYSDBA_BJ"
|
||
|
||
// fbLoginRe — имя пользователя Firebird без кавычек, на SYSDBA (иначе Fancy запретит bj создавать документы).
|
||
var fbLoginRe = regexp.MustCompile(`^(?i:SYSDBA)[A-Za-z0-9_$]{1,25}$`)
|
||
|
||
func depoFBScript(name string) string {
|
||
b, _ := depoFBFS.ReadFile("depoprocs/firebird/" + name)
|
||
return string(b)
|
||
}
|
||
|
||
// depoFBGrants — права (20) для логина bj.
|
||
func depoFBGrants(login string) string {
|
||
return strings.ReplaceAll(depoFBScript("20_FTV_M2M_GRANTS.sql"), ":BJ_USER", login)
|
||
}
|
||
|
||
// depoFBBindings — оператор Fancy для логина bj, кодировщик M2M и привязки FTV_M2M_SETTINGS по признакам Fancy
|
||
// (заполняются только пустые: исправленное вручную повторная установка не трогает). Один блок PSQL — и для
|
||
// установки из bj, и в скрипте администратора.
|
||
func depoFBBindings(login string) string {
|
||
return fmt.Sprintf(`EXECUTE BLOCK AS
|
||
DECLARE VARIABLE V INTEGER;
|
||
BEGIN
|
||
/* логин bj — оператор Fancy (робот): по оператору Fancy выбирает депозитарий и нумерует документы */
|
||
IF (NOT EXISTS (SELECT 1 FROM OD_OPERATORS WHERE USER_ID = '%[1]s')) THEN
|
||
INSERT INTO OD_OPERATORS (ID, USER_ID, NAME, U_FACE, PLAN_ID, USER_DOL, IS_ROBOT)
|
||
SELECT FIRST 1 GEN_ID(OD_OPERATORS_ID_GEN, 1), '%[1]s', 'Bridge&Join (переводы M2M)', O.U_FACE, O.PLAN_ID, 'Робот M2M', 1
|
||
FROM OD_OPERATORS O WHERE O.U_FACE IN (SELECT S.WHOS FROM OD_SETS S)
|
||
ORDER BY CASE WHEN O.USER_ID = 'SYSDBA' THEN 0 ELSE 1 END, O.ID;
|
||
/* кодировщик M2M — коды участников сервиса из справочника НРД (событие HANDBOOK) */
|
||
IF (NOT EXISTS (SELECT 1 FROM OD_FACES WHERE LAST_FLAG = 1 AND MNEM = 'M2M')) THEN
|
||
INSERT INTO OD_FACES (ID, S_TYPE, NAME, MNEM) VALUES (GEN_ID(OD_FACES_ID_GEN, 1), 1, 'Кодировщик M2M (справочник участников НРД)', 'M2M');
|
||
|
||
V = NULL; SELECT FIRST 1 U_FACE FROM OD_OPERATORS WHERE USER_ID = '%[1]s' INTO :V;
|
||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'WHOS' AND VAL IS NULL;
|
||
V = NULL; SELECT FIRST 1 ID FROM OD_OPERATORS WHERE USER_ID = '%[1]s' INTO :V;
|
||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'OPER_USER' AND VAL IS NULL;
|
||
V = NULL; SELECT FIRST 1 ID FROM OD_DOC_CATS WHERE SYS_NAME = 'D_FILER' ORDER BY ID INTO :V;
|
||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'FILER_CAT' AND VAL IS NULL;
|
||
V = NULL; SELECT FIRST 1 ID FROM OD_BALANCES WHERE NUM_S = '98010' ORDER BY ID INTO :V;
|
||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'D_NUM' AND VAL IS NULL;
|
||
V = NULL; SELECT FIRST 1 ID FROM OD_BALANCES WHERE NUM_S = '98070.04' ORDER BY ID INTO :V;
|
||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'K_NUM' AND VAL IS NULL;
|
||
V = NULL; SELECT FIRST 1 SELF_ID FROM OD_FACES WHERE LAST_FLAG = 1 AND (MNEM = 'ММВБ' OR INN = '7702077840') ORDER BY CASE WHEN MNEM = 'ММВБ' THEN 0 ELSE 1 END, ID INTO :V;
|
||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'MARKET' AND VAL IS NULL;
|
||
V = NULL; SELECT FIRST 1 NUM FROM OD_SYS_TABS WHERE CODE = 'DEPO96_DCLA' AND NAME IN ('Отчет депозитария', 'Отчёт депозитария') INTO :V;
|
||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'B_TYPE' AND VAL IS NULL;
|
||
V = NULL; SELECT FIRST 1 SELF_ID FROM OD_FACES WHERE LAST_FLAG = 1 AND (MNEM = 'НДЦ' OR INN = '7702165310') ORDER BY CASE WHEN MNEM = 'НДЦ' THEN 0 ELSE 1 END, ID INTO :V;
|
||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'CORR_FACE' AND VAL IS NULL;
|
||
V = NULL; SELECT FIRST 1 RUBRICA FROM D_FILER WHERE RUBRICA IS NOT NULL GROUP BY RUBRICA ORDER BY COUNT(*) DESC INTO :V;
|
||
UPDATE FTV_M2M_SETTINGS SET VAL = COALESCE(:V, 0) WHERE NAME = 'FILER_RUBRICA' AND VAL IS NULL;
|
||
END`, login)
|
||
}
|
||
|
||
// depoFBReadAll — право логина bj читать учёт: все таблицы Fancy, кроме таблиц обмена (на них — свои права).
|
||
// Имена без кавычек: так команда работает и в диалекте 1 (isql по скрипту), где "…" — строка.
|
||
func depoFBReadAll(login string) string {
|
||
return fmt.Sprintf(`EXECUTE BLOCK AS
|
||
DECLARE VARIABLE N VARCHAR(31);
|
||
BEGIN
|
||
FOR SELECT TRIM(RDB$RELATION_NAME) FROM RDB$RELATIONS
|
||
WHERE COALESCE(RDB$SYSTEM_FLAG, 0) = 0 AND RDB$RELATION_NAME NOT STARTING WITH 'FTV_M2M'
|
||
INTO :N DO
|
||
EXECUTE STATEMENT 'GRANT SELECT ON ' || N || ' TO %s';
|
||
END`, login)
|
||
}
|
||
|
||
var (
|
||
fbSetTermRe = regexp.MustCompile(`(?i)^SET\s+TERM\s+(\S+)$`)
|
||
fbIsqlCmdRe = regexp.MustCompile(`(?i)^(COMMIT(\s+WORK)?|SET\s+\w+.*)$`)
|
||
)
|
||
|
||
// fbStatements — isql-скрипт на команды для драйвера: SET TERM меняет разделитель, COMMIT и прочие SET — команды
|
||
// isql (у драйвера каждая команда фиксируется сама), комментарии вне команд отбрасываются.
|
||
func fbStatements(script string) []string {
|
||
term := ";"
|
||
var out []string
|
||
var cur strings.Builder
|
||
flush := func() {
|
||
s := strings.TrimSpace(cur.String())
|
||
cur.Reset()
|
||
if body := strings.TrimSpace(fbStripComments(s)); body != "" {
|
||
if m := fbSetTermRe.FindStringSubmatch(body); m != nil {
|
||
term = m[1]
|
||
return
|
||
}
|
||
if fbIsqlCmdRe.MatchString(body) {
|
||
return
|
||
}
|
||
out = append(out, s)
|
||
}
|
||
}
|
||
for i := 0; i < len(script); i++ {
|
||
c := script[i]
|
||
switch {
|
||
case c == '\'' || c == '"':
|
||
j := i + 1
|
||
for j < len(script) {
|
||
if script[j] == c {
|
||
if j+1 < len(script) && script[j+1] == c {
|
||
j += 2
|
||
continue
|
||
}
|
||
break
|
||
}
|
||
j++
|
||
}
|
||
if j >= len(script) {
|
||
j = len(script) - 1
|
||
}
|
||
cur.WriteString(script[i : j+1])
|
||
i = j
|
||
case c == '/' && i+1 < len(script) && script[i+1] == '*':
|
||
j := strings.Index(script[i+2:], "*/")
|
||
end := len(script)
|
||
if j >= 0 {
|
||
end = i + 2 + j + 2
|
||
}
|
||
cur.WriteString(script[i:end])
|
||
i = end - 1
|
||
case c == '-' && i+1 < len(script) && script[i+1] == '-':
|
||
j := strings.IndexByte(script[i:], '\n')
|
||
if j < 0 {
|
||
j = len(script) - i
|
||
}
|
||
cur.WriteString(script[i : i+j])
|
||
i += j - 1
|
||
case strings.HasPrefix(script[i:], term):
|
||
i += len(term) - 1
|
||
flush()
|
||
default:
|
||
cur.WriteByte(c)
|
||
}
|
||
}
|
||
flush()
|
||
return out
|
||
}
|
||
|
||
// fbStripComments — текст без комментариев (чтобы узнать команду isql или пустой кусок).
|
||
func fbStripComments(s string) string {
|
||
var b strings.Builder
|
||
for i := 0; i < len(s); i++ {
|
||
switch {
|
||
case s[i] == '/' && i+1 < len(s) && s[i+1] == '*':
|
||
j := strings.Index(s[i+2:], "*/")
|
||
if j < 0 {
|
||
return b.String()
|
||
}
|
||
i += j + 3
|
||
case s[i] == '-' && i+1 < len(s) && s[i+1] == '-':
|
||
j := strings.IndexByte(s[i:], '\n')
|
||
if j < 0 {
|
||
return b.String()
|
||
}
|
||
i += j
|
||
default:
|
||
b.WriteByte(s[i])
|
||
}
|
||
}
|
||
return b.String()
|
||
}
|
||
|
||
// fbExecScript — выполнить скрипт по командам; ошибка — с началом команды.
|
||
func fbExecScript(ctx context.Context, db *sql.DB, what, script string) error {
|
||
for _, st := range fbStatements(script) {
|
||
if _, err := db.ExecContext(ctx, st); err != nil {
|
||
return fmt.Errorf("%s: %s → %s", what, depoStmtHead(fbStripComments(st)), quikErrorDetail(err))
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// fbUser — создать пользователя сервера Firebird или сменить ему пароль (повторная подготовка). Firebird 2.5
|
||
// меняет пользователей при фиксации транзакции: неудачный CREATE USER (логин уже есть) — откат, иначе ошибка
|
||
// фиксации остаётся в подключении и повторяется на каждой следующей команде.
|
||
func fbUser(ctx context.Context, db *sql.DB, login, pass string) error {
|
||
err := fbExecTx(ctx, db, "CREATE USER "+login+" PASSWORD '"+sqlStr(pass)+"'")
|
||
if err == nil {
|
||
return nil
|
||
}
|
||
m := strings.ToLower(err.Error())
|
||
if !strings.Contains(m, "rdb$users") && !strings.Contains(m, "already") && !strings.Contains(m, "уже") {
|
||
return err
|
||
}
|
||
return fbExecTx(ctx, db, "ALTER USER "+login+" PASSWORD '"+sqlStr(pass)+"'")
|
||
}
|
||
|
||
// fbExecTx — команда в своей транзакции; ошибка выполнения или фиксации — откат.
|
||
func fbExecTx(ctx context.Context, db *sql.DB, q string) error {
|
||
tx, err := db.BeginTx(ctx, nil)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if _, err := tx.ExecContext(ctx, q); err != nil {
|
||
_ = tx.Rollback()
|
||
return err
|
||
}
|
||
if err := tx.Commit(); err != nil {
|
||
_ = tx.Rollback()
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// depoFBUDFCheck — функции Fancy (UDF ib_Fansy) на сервере: без них учётные таблицы не читаются.
|
||
func depoFBUDFCheck(ctx context.Context, db *sql.DB) error {
|
||
var s sql.NullString
|
||
err := db.QueryRowContext(ctx, "SELECT FIRST 1 NUM_DATE FROM OD_DOCS").Scan(&s)
|
||
if err == nil || err == sql.ErrNoRows {
|
||
return nil
|
||
}
|
||
m := err.Error()
|
||
if strings.Contains(m, "is not defined") || strings.Contains(m, "entrypoint") {
|
||
return fmt.Errorf("сервер Firebird не находит функции Fancy (UDF ib_Fansy): %s. Библиотека ib_Fansy должна лежать в папке UDF сервера Firebird — так же, как для работы самой Fancy", quikErrorDetail(err))
|
||
}
|
||
return fmt.Errorf("чтение OD_DOCS: %s", quikErrorDetail(err))
|
||
}
|
||
|
||
// depoFBSettings — привязки FTV_M2M_SETTINGS после установки: «ИМЯ=значение» и список незаполненных.
|
||
func depoFBSettings(ctx context.Context, db *sql.DB) (set, empty []string) {
|
||
rows, err := db.QueryContext(ctx, "SELECT NAME, VAL FROM FTV_M2M_SETTINGS ORDER BY NAME")
|
||
if err != nil {
|
||
return nil, nil
|
||
}
|
||
defer rows.Close()
|
||
for rows.Next() {
|
||
var n string
|
||
var v sql.NullInt64
|
||
if rows.Scan(&n, &v) != nil {
|
||
continue
|
||
}
|
||
n = strings.TrimSpace(n)
|
||
if v.Valid {
|
||
set = append(set, fmt.Sprintf("%s=%d", n, v.Int64))
|
||
} else {
|
||
empty = append(empty, n)
|
||
}
|
||
}
|
||
return set, empty
|
||
}
|
||
|
||
// depoFBSetup — «bj сделает сам» для Firebird логином администратора adm: проверка базы, логин bj, таблицы
|
||
// обмена, оператор и привязки Fancy, процедуры, права. Возвращает итог привязок (что не определилось — warn).
|
||
func depoFBSetup(ctx context.Context, adm FancyMSSQLSettings, db, login, pass string, step depoStep) error {
|
||
if !fbLoginRe.MatchString(login) {
|
||
return fmt.Errorf("логин bj для Firebird — латинские буквы, цифры и _, начинается с SYSDBA (например, %s): Fancy даёт права на документы только логинам SYSDBA…", depoFBDefaultLogin)
|
||
}
|
||
step("connect", "run", "Подключаюсь к базе "+db+" на "+adm.Server+" логином администратора «"+adm.User+"»")
|
||
conn, err := fancyOpen(adm, db)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer conn.Close()
|
||
cctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
|
||
defer cancel()
|
||
if err := conn.PingContext(cctx); err != nil {
|
||
return fmt.Errorf("к базе %s на %s логином %s подключиться не удалось — %s", db, adm.Server, adm.User, quikErrorDetail(err))
|
||
}
|
||
step("connect", "ok", "")
|
||
step("isdepo", "run", "Проверяю, что это база учёта DEPO и функции Fancy на сервере доступны")
|
||
if err := depoCheckIsDepo(cctx, adm, db); err != nil {
|
||
return err
|
||
}
|
||
if err := depoFBUDFCheck(cctx, conn); err != nil {
|
||
return err
|
||
}
|
||
step("isdepo", "ok", "")
|
||
step("apply", "run", "Создаю логин bj «"+login+"», таблицы обмена FTV_M2M* и оператора Fancy для bj")
|
||
if err := fbUser(cctx, conn, login, pass); err != nil {
|
||
return fmt.Errorf("логин %s: %s (создавать пользователей сервера Firebird может только SYSDBA)", login, quikErrorDetail(err))
|
||
}
|
||
if err := fbExecScript(cctx, conn, "таблицы обмена", depoFBScript("00_FTV_M2M_TABLES.sql")); err != nil {
|
||
return err
|
||
}
|
||
if _, err := conn.ExecContext(cctx, depoFBBindings(login)); err != nil {
|
||
return fmt.Errorf("оператор и привязки Fancy: %s", quikErrorDetail(err))
|
||
}
|
||
step("apply", "ok", "")
|
||
step("procs", "run", "Устанавливаю процедуры обработки и выдаю права логину bj")
|
||
if err := fbExecScript(cctx, conn, "процедуры", depoFBScript("10_FTV_M2M_PROCS.sql")); err != nil {
|
||
return err
|
||
}
|
||
if err := fbExecScript(cctx, conn, "права", depoFBGrants(login)); err != nil {
|
||
return err
|
||
}
|
||
if _, err := conn.ExecContext(cctx, depoFBReadAll(login)); err != nil {
|
||
return fmt.Errorf("право чтения учёта логину %s: %s", login, quikErrorDetail(err))
|
||
}
|
||
step("procs", "ok", "")
|
||
set, empty := depoFBSettings(cctx, conn)
|
||
if len(empty) > 0 {
|
||
step("bindings", "warn", "Не определены по справочникам Fancy: "+strings.Join(empty, ", ")+
|
||
" — впишите значения в таблицу FTV_M2M_SETTINGS (UPDATE FTV_M2M_SETTINGS SET VAL = … WHERE NAME = '…'), иначе обработка остановится с этой ошибкой. Определены: "+strings.Join(set, ", "))
|
||
} else {
|
||
step("bindings", "ok", "Привязки к справочникам Fancy: "+strings.Join(set, ", "))
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// win1251 — текст скрипта для isql -ch WIN1251 (знаки вне WIN1251 — заменяются).
|
||
func win1251(s string) []byte {
|
||
b, err := encoding.ReplaceUnsupported(charmap.Windows1251.NewEncoder()).Bytes([]byte(s))
|
||
if err != nil {
|
||
return []byte(s)
|
||
}
|
||
return b
|
||
}
|
||
|
||
// depoSetupScriptFB — скрипт для администратора (isql, файл в WIN1251): то же, что depoFBSetup. pass="" — без
|
||
// создания логина (обновление процедур: логин bj уже есть).
|
||
func depoSetupScriptFB(f FancyMSSQLSettings, db, login, pass string) string {
|
||
var b strings.Builder
|
||
if pass == "" {
|
||
fmt.Fprintf(&b, `/* Bridge&Join: таблицы обмена, привязки, процедуры обработки и права логину bj %[3]s (Fancy на Firebird).
|
||
Запуск под SYSDBA утилитой isql с кодировкой WIN1251:
|
||
isql -ch WIN1251 -user SYSDBA -password <пароль> -i bj-depo-procs-firebird.sql %[1]s:%[2]s
|
||
Можно запускать повторно: недостающее добавляется, процедуры заменяются, заполненные привязки не меняются. */
|
||
|
||
`, f.Server, db, login)
|
||
depoFBScriptBody(&b, login)
|
||
return b.String()
|
||
}
|
||
fmt.Fprintf(&b, `/* Bridge&Join: подготовка базы учёта DEPO (Fancy на Firebird) для bj.
|
||
Запуск под SYSDBA утилитой isql с кодировкой WIN1251:
|
||
isql -ch WIN1251 -user SYSDBA -password <пароль> -i bj-depo-setup-firebird.sql %[1]s:%[2]s
|
||
Создаёт логин bj %[3]s (пользователь сервера Firebird: только выданные ниже права; Fancy даёт логинам SYSDBA…
|
||
права на документы, а по оператору Fancy — депозитарий и нумерацию), таблицы обмена FTV_M2M*, оператора
|
||
Fancy для bj, кодировщик M2M и привязки к справочникам Fancy (FTV_M2M_SETTINGS — только пустые), процедуры
|
||
обработки и права. Учётные данные не меняет. Можно запускать повторно.
|
||
Если логин %[3]s на сервере уже есть, CREATE USER выдаст ошибку — это нормально, следующая строка ставит пароль.
|
||
В скрипте пароль логина bj — после выполнения файл удалите. */
|
||
|
||
CREATE USER %[3]s PASSWORD '%[4]s';
|
||
ALTER USER %[3]s PASSWORD '%[4]s';
|
||
COMMIT;
|
||
|
||
`, f.Server, db, login, sqlStr(pass))
|
||
depoFBScriptBody(&b, login)
|
||
return b.String()
|
||
}
|
||
|
||
// depoFBScriptBody — таблицы, привязки, процедуры и права в скрипт isql.
|
||
func depoFBScriptBody(b *strings.Builder, login string) {
|
||
b.WriteString(depoFBScript("00_FTV_M2M_TABLES.sql"))
|
||
b.WriteString("\n/* Оператор Fancy для bj, кодировщик M2M, привязки к справочникам */\nSET TERM ^ ;\n")
|
||
b.WriteString(depoFBBindings(login))
|
||
b.WriteString("^\nSET TERM ; ^\nCOMMIT;\n\n")
|
||
b.WriteString(depoFBScript("10_FTV_M2M_PROCS.sql"))
|
||
b.WriteString("\n")
|
||
b.WriteString(depoFBGrants(login))
|
||
b.WriteString("\n/* Чтение учёта логином bj */\nSET TERM ^ ;\n")
|
||
b.WriteString(depoFBReadAll(login))
|
||
b.WriteString("^\nSET TERM ; ^\nCOMMIT;\n\n/* Привязки: пустые значения нужно вписать вручную */\nSELECT NAME, VAL FROM FTV_M2M_SETTINGS;\n")
|
||
}
|
||
|
||
// depoFBHash — отпечаток установки для Firebird (плашка «процедуры DEPO обновились»).
|
||
func depoFBHash() []byte {
|
||
var b strings.Builder
|
||
for _, n := range depoFBScriptFiles {
|
||
b.WriteString(n + "\n" + depoFBScript(n) + "\n")
|
||
}
|
||
b.WriteString(depoFBBindings("") + depoFBReadAll(""))
|
||
return []byte(b.String())
|
||
}
|
||
|
||
// depoProcsPreflightFB — «Проверить возможности» для Firebird: подключение администратора, база DEPO, функции
|
||
// Fancy, какие процедуры уже есть.
|
||
func depoProcsPreflightFB(ctx context.Context, f FancyMSSQLSettings, adminUser, adminPass string) (string, bool, error) {
|
||
adm := f
|
||
if u := strings.TrimSpace(adminUser); u != "" {
|
||
adm.User, adm.Password = u, adminPass
|
||
}
|
||
db, err := fancyOpen(adm, f.DepoDB())
|
||
if err != nil {
|
||
return "", false, err
|
||
}
|
||
defer db.Close()
|
||
if err := db.PingContext(ctx); err != nil {
|
||
return "", false, fmt.Errorf("подключение к базе %s логином %s: %s", f.DepoDB(), adm.User, quikErrorDetail(err))
|
||
}
|
||
if err := depoCheckIsDepo(ctx, adm, f.DepoDB()); err != nil {
|
||
return "", false, err
|
||
}
|
||
if err := depoFBUDFCheck(ctx, db); err != nil {
|
||
return "", false, err
|
||
}
|
||
missing := depoMissing(ctx, db, depoBj2depoProcs, "P")
|
||
var b strings.Builder
|
||
fmt.Fprintf(&b, "Сервер %s, база %s, логин %s.\n", f.Server, f.DepoDB(), adm.User)
|
||
if len(missing) == 0 {
|
||
b.WriteString("Процедуры обработки: все на месте (будут заменены CREATE OR ALTER).\n")
|
||
} else {
|
||
fmt.Fprintf(&b, "Процедуры обработки: нет %s.\n", strings.Join(missing, ", "))
|
||
}
|
||
canApply := strings.EqualFold(adm.User, "SYSDBA")
|
||
if !canApply {
|
||
b.WriteString("Нужен логин SYSDBA: процедуры и права в базе Fancy ставит администратор сервера Firebird.\n")
|
||
}
|
||
fmt.Fprintf(&b, "Будет выполнено: таблицы обмена (недостающее), привязки к справочникам (только пустые), процедуры обработки, права логину %s.", depoSetupLogin(f))
|
||
return b.String(), canApply, nil
|
||
}
|
||
|
||
// depoProcsInstallFB — «Установить» для Firebird: та же подготовка, что в мастере, с прежним логином и паролем bj.
|
||
func depoProcsInstallFB(ctx context.Context, f FancyMSSQLSettings, adminUser, adminPass string) (string, error) {
|
||
adm := f
|
||
if u := strings.TrimSpace(adminUser); u != "" {
|
||
adm.User, adm.Password = u, adminPass
|
||
}
|
||
if !strings.EqualFold(adm.User, "SYSDBA") {
|
||
return "", fmt.Errorf("введите логин и пароль SYSDBA: процедуры и права в базе Fancy ставит администратор сервера Firebird")
|
||
}
|
||
login := depoSetupLogin(f)
|
||
if strings.TrimSpace(f.Password) == "" {
|
||
return "", fmt.Errorf("у bj нет пароля логина %s — подготовьте базу в мастере (шаг «Учёт DEPO»)", login)
|
||
}
|
||
var notes []string
|
||
step := func(key, state, text string) {
|
||
if state == "warn" {
|
||
notes = append(notes, text)
|
||
}
|
||
}
|
||
if err := depoFBSetup(ctx, adm, f.DepoDB(), login, f.Password, step); err != nil {
|
||
return "", err
|
||
}
|
||
msg := "таблицы обмена, привязки, процедуры обработки и права логину " + login + " установлены"
|
||
if len(notes) > 0 {
|
||
msg += ". " + strings.Join(notes, " ")
|
||
}
|
||
return msg, nil
|
||
}
|