Учёт DEPO: задание SQL Server Agent — своё у каждой базы (bj2depo_<база>), имя в мастере

На одном сервере бывает несколько баз DEPO (боевая и тестовая): общее имя
bj2depo давало ложную зелёную проверку — тестовой базе засчитывалось боевое
задание, а своё не создавалось.
- Имя задания — настройка DepoJobName; в мастере, пункт 3, поле «Имя задания
  SQL Server Agent», по умолчанию bj2depo_<имя базы> (меняется вместе с базой,
  пока его не поправили). Установки старше — bj2depo, как было.
- Скрипт до любых изменений останавливается, если задание с этим именем
  обрабатывает другую базу или эту базу уже обрабатывает задание с другим именем
  (иначе двойная обработка событий).
- Проверка задания сравнивает его базу (sp_help_jobstep) с базой bj.
Синтаксис проверен на SQL Server без выполнения (SET PARSEONLY); проверка
задания на боевом сервере — зелёная (bj2depo ↔ BJ_DEPO_DATA).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
zuevav
2026-10-06 16:09:14 +03:00
parent 42524f6a4f
commit 21d87fb530
8 changed files with 135 additions and 25 deletions
+33 -5
View File
@@ -59,7 +59,7 @@ func depoSetupScript(f FancyMSSQLSettings, db, login, pass string, agentJob bool
var b strings.Builder
job := "вызывает сам bj"
if agentJob {
job = "задание SQL Server Agent «" + depoJobName + "»"
job = "задание SQL Server Agent «" + depoJobName(f) + "»"
}
fmt.Fprintf(&b, `-- Bridge&Join: подготовка базы учёта DEPO для bj. Microsoft SQL Server.
SET NOCOUNT ON;
@@ -98,7 +98,7 @@ BEGIN
END
PRINT N'База DEPO: ' + @db;
DECLARE @in nvarchar(300) = N'USE ' + QUOTENAME(@db) + N'; ';
%[5]s
-- 1. Логин bj (если такой логин на сервере уже есть — ему ставится пароль из @pass)
-- (EXEC(...) склеивает только строки и переменные — команда собирается в @sql заранее)
DECLARE @sql nvarchar(max);
@@ -109,7 +109,7 @@ ELSE
EXEC(@sql);
-- 2. Таблицы обмена FTV_M2M* (только недостающие таблицы, колонки и индексы — данные не меняются)
`, login, job, sqlStr(db), sqlStr(pass))
`, login, job, sqlStr(db), sqlStr(pass), depoJobGuard(f, agentJob))
for _, s := range depoTablesDDL(false) {
fmt.Fprintf(&b, "EXEC(@in + N'%s');\n", sqlStr(s))
}
@@ -133,7 +133,7 @@ ELSE
PRINT N'Задание %[1]s уже есть — не изменялось.';
-- логину bj — чтение списка заданий и их истории: bj проверяет, что задание работает
EXEC(N'USE msdb; IF USER_ID(N''%[4]s'') IS NULL CREATE USER [%[4]s] FOR LOGIN [%[4]s]; ELSE IF EXISTS (SELECT 1 FROM sys.database_principals WHERE name = N''%[4]s'' AND sid <> SUSER_SID(N''%[4]s'')) ALTER USER [%[4]s] WITH LOGIN = [%[4]s]; IF IS_ROLEMEMBER(N''SQLAgentReaderRole'', N''%[4]s'') = 0 ALTER ROLE SQLAgentReaderRole ADD MEMBER [%[4]s];');
`, depoJobName, proc, depoJobEvery(f), login)
`, depoJobName(f), proc, depoJobEvery(f), login)
} else {
fmt.Fprintf(&b, `
-- 4. Обработку вызывает bj: логину bj — право запускать процедуру %[1]s
@@ -184,6 +184,34 @@ END $$;
return b.String()
}
// depoJobGuard — проверки задания до любых изменений: у каждой базы DEPO на сервере своё задание. Задание с
// этим именем уже обрабатывает другую базу — стоп (иначе тест обработали бы боевым заданием и наоборот);
// эту базу уже обрабатывает задание с другим именем — стоп (два задания обрабатывали бы одни события дважды).
func depoJobGuard(f FancyMSSQLSettings, agentJob bool) string {
if !agentJob {
return ""
}
proc := depoJobProcName(f)
if i := strings.LastIndex(proc, "."); i >= 0 {
proc = proc[i+1:]
}
return fmt.Sprintf(`
-- Задание обработки «%[1]s» — только для этой базы: у каждой базы DEPO на сервере своё задание
IF EXISTS (SELECT 1 FROM msdb.dbo.sysjobs j JOIN msdb.dbo.sysjobsteps s ON s.job_id = j.job_id WHERE j.name = N'%[1]s' AND s.database_name <> @db)
BEGIN
RAISERROR(N'Задание %[1]s уже есть и обрабатывает другую базу. Задайте для базы %%s другое имя задания в bj (шаг «Учёт DEPO», пункт 3) и получите скрипт заново.', 16, 1, @db);
RETURN;
END
DECLARE @other sysname = (SELECT TOP 1 j.name FROM msdb.dbo.sysjobs j JOIN msdb.dbo.sysjobsteps s ON s.job_id = j.job_id
WHERE s.database_name = @db AND s.command LIKE N'%%%[2]s%%' AND j.name <> N'%[1]s');
IF @other IS NOT NULL
BEGIN
RAISERROR(N'Обработку базы %%s уже запускает задание %%s. Впишите в bj это имя задания (шаг «Учёт DEPO», пункт 3) и получите скрипт заново.', 16, 1, @db, @other);
RETURN;
END
`, depoJobName(f), proc)
}
// depoSetupFileName — имя файла скрипта.
func depoSetupFileName(f FancyMSSQLSettings) string {
if f.IsPostgres() {
@@ -215,7 +243,7 @@ func depoSetupRun(ctx context.Context, f FancyMSSQLSettings, db, adminUser, admi
step("isdepo", "ok", "")
what := "логин bj «" + login + "», таблицы обмена, права"
if agentJob && !f.IsPostgres() {
what += ", задание «" + depoJobName + "»"
what += ", задание «" + depoJobName(f) + "»"
}
step("apply", "run", "Применяю скрипт подготовки: "+what)
if f.IsPostgres() {