diff --git a/internal/lkgateway/depo_procs.go b/internal/lkgateway/depo_procs.go index 2f04a91..4560168 100644 --- a/internal/lkgateway/depo_procs.go +++ b/internal/lkgateway/depo_procs.go @@ -156,13 +156,15 @@ func depoProcsPreflight(ctx context.Context, f FancyMSSQLSettings, adminUser, ad } missing := depoMissing(ctx, db, depoBj2depoProcs, "P") login := depoSetupLogin(f) - var canCreate, sysadmin, bjUser int + execAs := depoProcExecAsUser(f) // пользователь для EXECUTE AS в теле процедур (обычно SYSDBA) + var canCreate, sysadmin, bjUser, execAsOK int _ = db.QueryRowContext(ctx, `SELECT CAST(ISNULL(HAS_PERMS_BY_NAME(NULL,'DATABASE','CREATE PROCEDURE'),0) AS int), CAST(ISNULL(IS_SRVROLEMEMBER('sysadmin'),0) AS int), - CASE WHEN DATABASE_PRINCIPAL_ID(@u) IS NULL THEN 0 ELSE 1 END`, - sql.Named("u", login)).Scan(&canCreate, &sysadmin, &bjUser) - canApply = canCreate == 1 || sysadmin == 1 + CASE WHEN DATABASE_PRINCIPAL_ID(@u) IS NULL THEN 0 ELSE 1 END, + CASE WHEN DATABASE_PRINCIPAL_ID(@e) IS NULL THEN 0 ELSE 1 END`, + sql.Named("u", login), sql.Named("e", execAs)).Scan(&canCreate, &sysadmin, &bjUser, &execAsOK) + canApply = (canCreate == 1 || sysadmin == 1) && execAsOK == 1 var b strings.Builder fmt.Fprintf(&b, "Сервер %s, база %s, логин %s.\n", f.Server, f.DepoDB(), conn.User) @@ -181,10 +183,37 @@ func depoProcsPreflight(ctx context.Context, f FancyMSSQLSettings, adminUser, ad } else { fmt.Fprintf(&b, "Логин %s в базе: нет — сначала подготовьте базу (логин/таблицы), GRANT будет пропущен.\n", login) } + if execAsOK == 1 { + fmt.Fprintf(&b, "Пользователь %s (для EXECUTE AS в процедурах): есть.\n", execAs) + } else { + fmt.Fprintf(&b, "Пользователь %s (для EXECUTE AS в процедурах): НЕТ — процедуры FTV_M2M_PROCESS_* используют EXECUTE AS USER='%s' и не создадутся без него. Создайте этого пользователя в базе (как в рабочей базе Fancy) или замените имя в «Тексты процедур».\n", execAs, execAs) + } b.WriteString("Будет выполнено: CREATE OR ALTER 5 процедур (WIRING→OPERATION→EVENTS→CREATE→JOB) + GRANT EXECUTE на FTV_M2M_JOB.") return b.String(), canApply, nil } +// depoProcExecAsUser — пользователь, под которым процедуры выполняют EXECUTE AS (из их текста, +// строка «execute as user = '...'»). По умолчанию SYSDBA. +func depoProcExecAsUser(f FancyMSSQLSettings) string { + body := depoProcEffective(f, "FTV_M2M_PROCESS_WIRING") + if m := execAsUserRe.FindStringSubmatch(body); len(m) == 2 { + return m[1] + } + return "SYSDBA" +} + +var execAsUserRe = regexp.MustCompile(`(?i)execute\s+as\s+user\s*=\s*'([^']+)'`) + +// depoProcErrHint — подсказка по типичной причине отказа CREATE процедур. +func depoProcErrHint(msg string) string { + l := strings.ToLower(msg) + if strings.Contains(l, "sysdba") || strings.Contains(l, "principal") || strings.Contains(l, "15517") || + strings.Contains(l, "execute as") || strings.Contains(l, "не удается олицетворить") || strings.Contains(l, "олицетвор") { + return " Процедуры FTV_M2M_PROCESS_* используют EXECUTE AS USER='SYSDBA' — в базе DEPO должен быть пользователь SYSDBA (как в рабочей базе Fancy). Создайте его или замените имя пользователя в «Тексты процедур» и повторите." + } + return "" +} + // depoProcsInstall — установить процедуры логином администратора. Сначала preflight; при отсутствии // прав — отказ без изменений. Возвращает итоговое сообщение. func depoProcsInstall(ctx context.Context, f FancyMSSQLSettings, adminUser, adminPass string) (string, error) { @@ -257,7 +286,11 @@ func (h *setupHandlers) installFancyDepoProcs(w http.ResponseWriter, r *http.Req msg, err := depoProcsInstall(ctx, s, r.FormValue("admin_user"), r.FormValue("admin_password")) if err != nil { log.Printf("depo-procs: установка не удалась: %v", err) - setupFlash(w, r, "Процедуры DEPO: не установлены — "+err.Error()) + txt := "установка не удалась: " + err.Error() + depoProcErrHint(err.Error()) + s2 := h.rc.Snapshot().Fancy // сохранить ошибку, чтобы не мелькала во флеше + s2.DepoProcCheck, s2.DepoProcCheckOK, s2.DepoProcCheckAt = txt, false, time.Now() + _ = h.rc.UpdateFancy(s2) + setupFlash(w, r, "Процедуры DEPO: "+txt) return } pmsg, pok := depoProcCheck(ctx, h.rc) diff --git a/internal/lkgateway/wizard_depo.go b/internal/lkgateway/wizard_depo.go index e169e83..c47def8 100644 --- a/internal/lkgateway/wizard_depo.go +++ b/internal/lkgateway/wizard_depo.go @@ -418,7 +418,7 @@ func (h *setupHandlers) depoSetupDo(ctx context.Context, r *http.Request, step d adm := s adm.User, adm.Password = au, r.FormValue("admin_password") if err := depoProcsInstallInto(ctx, adm, s, db, s.User); err != nil { - return fail("процедуры обработки не установлены — " + err.Error()) + return fail("процедуры обработки не установлены — " + err.Error() + depoProcErrHint(err.Error())) } step("procs", "ok", "") }