Oracle DBMS_SCHEDULER vs DBMS_JOB: pacote único para gerenciar, pausar e monitorar jobs

Introdução

Em muitos ambientes Oracle, convivem dois mecanismos de agendamento: o DBMS_SCHEDULER (moderno) e o DBMS_JOB (legado). Cada um possui comandos, dicionários e semânticas próprios — “enabled" de um lado, “broken” do outro; DBA_SCHEDULER_* aqui, DBA_JOBS* ali; STOP_JOB vs. “matar sessão” etc.
O resultado, no dia a dia, é fricção operacional: consultas diferentes para a mesma pergunta, scripts paralelos para operações equivalentes, e uma curva de aprendizado desnecessária para tarefas simples como ver quem está rodando, habilitar/desabilitar e pausar um lote de jobs para uma janela de manutenção.

Esta solução nasce exatamente desse cenário. O objetivo é oferecer um ponto único e transparente para inventariar, monitorar e operar ambos os mecanismos sem complexidade.
Você passa a consultar uma visão consolidada (inventário e execução), e a executar um pacote único para ações comuns — seja um job Scheduler com nome, seja um DBMS_JOB numérico.


2) O que são DBMS_SCHEDULER e DBMS_JOB

DBMS_SCHEDULER (o mecanismo moderno)

Introduzido para substituir o DBMS_JOB, o DBMS_SCHEDULER oferece uma arquitetura rica e modular:

  • Jobs, Programs e Schedules separados, possibilitando reuso e governança.
  • Chains (fluxos com dependências), Windows e Job Classes.
  • Calendário expressivo por repeat_interval (calendar expressions).
  • Parada controlada (STOP_JOB), logging detalhado, priorização e integração com Resource Manager.
  • Dicionário amplo (DBA_SCHEDULER_JOBS, DBA_SCHEDULER_RUNNING_JOBS, etc.).

Em suma: mais recursos, rastreabilidade e controle fino. É o padrão recomendado para novas rotinas.


DBMS_JOB (o mecanismo legado)

O DBMS_JOB é mais simples e direto:

  • Cada job tem um número (ex.: 128), um comando WHAT (PL/SQL a executar), um NEXT_DATE e um INTERVAL (string).
  • A disponibilidade é representada por BROKEN = 'Y'|'N' em vez de ENABLED.
  • Não possui um comando nativo para parar execução em curso; na prática, usa-se o encerramento da sessão que executa o job.
  • Dicionário compacto (DBA_JOBS, DBA_JOBS_RUNNING).

Embora menos flexível, é comum que ambientes antigos ainda mantenham rotinas estáveis sob DBMS_JOB.


Vantagens de utilizar esta solução

1. Interface única e coerente

  • Views consolidadas para inventário (JOB_ADMIN_ALL_JOBS[_V]) e execução (JOB_ADMIN_RUNNING[_V]) que mesclam Scheduler e DBMS_JOB.
  • Colunas homogêneas: enabled sempre como TRUE/FALSE, datas e intervalos formatados nas versões “_V”.

2. Produtividade imediata

  • Uma consulta responde “o que tenho?”, “quando roda?”, “quem está rodando e há quanto tempo?”.
  • O operador não precisa lembrar se o job é Scheduler ou DBMS_JOB para consultar status ou executar ações.

3. Operação simples e segura

  • Habilitar/Desabilitar por um único procedimento (enable_job/disable_job), passando nome (Scheduler) ou número (DBMS_JOB).
  • Parada elegante: para Scheduler usa STOP_JOB(force => TRUE); para DBMS_JOB, encerra a sessão de execução antes de mudar o estado (RAC-aware).
  • Lote inteligente (set_jobs_by_pattern): aplique uma ação a um conjunto por LIKE, escolhendo tipo (Scheduler/DBMS_JOB/ANY) e se deve parar os ativos.

4. Observabilidade e padronização

  • Saídas de list_jobs e list_running_jobs são limpas e didáticas, integrando as duas tecnologias com o mesmo layout.
  • Facilita auditorias, health-checks e relatórios operacionais com menos scripts.

5. Governança simplificada

  • Role única (JOBDBMSSCHEDULERALL) e sinônimos públicos: padroniza o consumo entre times e ferramentas.
  • AUTHID DEFINER: quem usa não precisa de privilégio de DBA — basta receber a role.

6. Idempotência e baixa fricção de implantação

  • CREATE OR REPLACE em todos os objetos e checagens defensivas: reexecutar reinstala.
  • Compatível com RAC e com execução em PDB (quando aplicável).

7. Menor risco operacional

  • Mensagens claras e validações garantem que “job não encontrado” ou “nome ambíguo entre owners” sejam tratados com erros explicativos.
  • Em manutenção, p_stop_running => TRUE reduz inconsistências ao pausar um lote inteiro de forma controlada.

8. Porta de entrada para modernização

  • Ao dar visibilidade e controle unificado, a solução facilita migrações graduais de DBMS_JOB para DBMS_SCHEDULER, sem interrupção do dia a dia.


Faça o download do instalador pkg_job_admin no link abaixo e habilite a gestão unificada de jobs Oracle.





Administração de jobs Oracle em um só lugar: Instalação e uso do pkg_job_admin

[ORCL.oracle@furushimait_labs ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Wed Sep 10 14:52:51 2025
Version 19.20.0.0.0

Copyright (c) 1982, 2022, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production
Version 19.20.0.0.0

SQL> @job_admin_setup_completo.sql

═════════════════════════════════════════════════════════════════════════════════════════════
Iniciando job_admin_setup_completo.sql
═════════════════════════════════════════════════════════════════════════════════════════════
Owner atual: SYS

PL/SQL procedure successfully completed.


── 1) Criando/Atualizando Views Consolidadas …

View created.


View created.


View created.


View created.

✓ Views criadas/atualizadas com sucesso.

── 2) Criando/Atualizando Pacote (SPEC) …

Package created.

No errors.

── 3) Criando/Atualizando Pacote (BODY) …

Package body created.

No errors.
✓ Pacote criado/atualizado com sucesso.

── 4) Segurança: ROLE, Grants e Sinônimos Públicos …
Role JOBDBMSSCHEDULERALL já existente.

PL/SQL procedure successfully completed.


Grant succeeded.


Grant succeeded.


Grant succeeded.


Grant succeeded.


Grant succeeded.

Sinônimos públicos criados/atualizados.

PL/SQL procedure successfully completed.

✓ Segurança configurada (ROLE + grants + sinônimos públicos).

── 5) Auto-Validação (não intrusiva) …
Pacote PKG_JOB_ADMIN sem erros de compilação.

PL/SQL procedure successfully completed.

Views acessíveis: JOB_ADMIN_ALL_JOBS e JOB_ADMIN_RUNNING OK.

PL/SQL procedure successfully completed.


── 6) Exemplos de Uso para referência  …

-- Inventário:
BEGIN
pkg_job_admin.list_jobs;                          -- todos os jobs
pkg_job_admin.list_jobs('DEMOAPP');               -- por owner
pkg_job_admin.list_jobs(p_like => 'JOB_%');       -- por padrão (LIKE)
END
/
-- Em execução agora:
BEGIN
pkg_job_admin.list_running_jobs;                       -- tudo
pkg_job_admin.list_running_jobs('DEMOAPP');            -- por owner
pkg_job_admin.list_running_jobs(p_like => 'SYNC_%');   -- por padrão (LIKE)
END
/
-- Operações pontuais:
-- SCHEDULER (por nome; owner opcional se único no banco)
-- BEGIN
--   pkg_job_admin.disable_job('DEMOAPP.JOB_ATUALIZA_INDICES', p_stop_running => TRUE)
--   pkg_job_admin.enable_job ('DEMOAPP.JOB_ATUALIZA_INDICES')
-- END
-- /
-- DBMS_JOB (por número; owner apenas para validar pertinência quando desejar)
-- BEGIN
--   pkg_job_admin.disable_job('128', p_owner => 'SYS', p_stop_running => TRUE)
--   pkg_job_admin.enable_job ('128', p_owner => 'SYS')
-- END
-- /
-- Lote por padrão (por tipo):
-- BEGIN
--   pkg_job_admin.set_jobs_by_pattern(
--     p_name_like    => 'JOB_SYNC_%',
--     p_enable       => FALSE,
--     p_owner        => 'DEMOAPP',
--     p_type         => pkg_job_admin.c_type_scheduler, -- SCHEDULER | DBMS_JOB | ANY
--     p_stop_running => TRUE
--   )
-- END
-- /
-- Visões consolidadas:
-- SELECT * FROM job_admin_all_jobs_v ORDER BY owner, job_type, job_name
-- SELECT * FROM job_admin_running_v  ORDER BY running_instance, job_type, job_name

═════════════════════════════════════════════════════════════════════════════════════════════
Conclusão: job_admin_setup_completo.sql executado.
Para conceder a ROLE a um usuário final, utilize:
GRANT JOBDBMSSCHEDULERALL TO nome_do_usuario
═════════════════════════════════════════════════════════════════════════════════════════════




O que o script cria

Objetos de dados (Views)

  • JOB_ADMIN_ALL_JOBS — Inventário unificado (SCHEDULER + DBMS_JOB).
  • JOB_ADMIN_RUNNING — Execução unificada no momento (SCHEDULER + DBMS_JOB).
  • JOB_ADMIN_ALL_JOBS_V — Versão “amigável” do inventário (datas e intervalos formatados).
  • JOB_ADMIN_RUNNING_V — Versão “amigável” da execução (intervalos formatados).

API (Package)

  • PKG_JOB_ADMIN — Procedimentos para listar, habilitar/desabilitar e controlar lotes.
    • list_jobs(p_owner, p_like)
    • list_running_jobs(p_owner, p_like)
    • set_job_enabled(p_job_identifier, p_enable, p_owner, p_stop_running)
    • enable_job(...) / disable_job(...)
    • set_jobs_by_pattern(p_name_like, p_enable, p_owner, p_type, p_stop_running)
    • Constantes: c_type_scheduler, c_type_dbms_job, c_type_any




Inventário consolidado, pronto para relatório

SELECT 
  owner, 
  job_type, 
  job_name, 
  enabled, 
  state, 
  next_run_date 
FROM 
  job_admin_all_jobs_v 
ORDER BY 
  owner, 
  job_type, 
  job_name;

Execução no momento (quem está rodando agora)

SELECT 
  owner, 
  job_type, 
  job_name, 
  running_instance, 
  elapsed_time 
FROM 
  job_admin_running_v 
ORDER BY 
  running_instance, 
  job_type, 
  job_name;





Habilitar/Desabilitar individualmente

-- DBMS_SCHEDULER por nome (com owner)
BEGIN
  pkg_job_admin.disable_job('DEMOAPP.JOB_XPTO_ETL_DIARIO', p_stop_running => TRUE);
  pkg_job_admin.enable_job ('DEMOAPP.JOB_XPTO_ETL_DIARIO');
END;
/


-- DBMS_JOB por número (com validação de owner opcional)
BEGIN
  pkg_job_admin.disable_job('128', p_owner => 'SYS', p_stop_running => TRUE);
  pkg_job_admin.enable_job ('128', p_owner => 'SYS');
END;
/





Habilitar/Desabilitar JOBS em lote

BEGIN
  pkg_job_admin.set_jobs_by_pattern(
    p_name_like    => 'JOB_XPTO_%',
    p_enable       => FALSE,                            -- desativar todos que casem com o padrão
    p_owner        => 'DEMOAPP',                        -- opcional
    p_type         => pkg_job_admin.c_type_scheduler,   -- QUALQUER TIPO : SCHEDULER | DBMS_JOB | ANY
    p_stop_running => TRUE
  );
END;
/





Quais jobs existem e quando rodarão?

SELECT owner, job_type, job_name, enabled, state, next_run_date 
FROM   job_admin_all_jobs_v
ORDER  BY owner, job_type, job_name;


--  O que observar
--    • job_type: distingue SCHEDULER vs DBMS_JOB.
--    • enabled: “TRUE/FALSE” consolidado para ambos.
--    • state: útil para SCHEDULER (ex.: SCHEDULED, DISABLED).





Quais jobs contêm a palavra ‘XPTO’ no nome ou no comando?

SELECT owner, job_type, job_name, enabled, job_action
FROM   job_admin_all_jobs
WHERE  UPPER(job_name)   LIKE '%XPTO%'
   OR  UPPER(job_action) LIKE '%XPTO%'
ORDER  BY owner, job_name;








Qual o próximo agendamento dos jobs do meu schema de aplicação?

SELECT job_name, enabled, state, next_run_date
FROM   job_admin_all_jobs_v
WHERE  owner = 'DEMOAPP'
ORDER  BY job_name;






Quem está rodando agora e há quanto tempo?

SELECT owner, job_type, job_name, running_instance, elapsed_time
FROM   job_admin_running_v
ORDER  BY running_instance, job_type, job_name;





Agora vamos ver se o job JOB_XPTO_ETL_DIARIO está rodando e há quanto tempo

SELECT owner, job_type, job_name, elapsed_time
FROM   job_admin_running_v
WHERE  job_name = 'JOB_XPTO_ETL_DIARIO';

-- • Se vier linha, o job está ativo; elapsed_time mostra a duração no formato DD HH:MI:SS.





Quais jobs estão desabilitados, para uma revisão rápida?

SELECT owner, job_type, job_name, enabled
FROM   job_admin_all_jobs_v
WHERE  enabled = 'FALSE'
ORDER  BY owner, job_type, job_name;





Como usar o Pacote (exemplos passo a passo)

Todos os exemplos assumem que você possui a role JOBDBMSSCHEDULERALL.
Use os sinônimos públicos (pkg_job_admin) — não há necessidade de prefixar com schema.

-- Inventário

BEGIN
  pkg_job_admin.list_jobs;                    -- Listar TUDO!
  pkg_job_admin.list_jobs('DEMOAPP');         -- Procurar pelo OWNER
  pkg_job_admin.list_jobs(p_like => 'JOB_%'); -- Busca por jobs cujo nome contenha o padrão informado (equivalente ao LIKE).
END;
/



--- OUTPUT : 

=== JOBS (SCHEDULER) ===
DEMOAPP.JOB_XPTO_ETL_DIARIO | enabled=TRUE | state=SCHEDULED | next=2025-09-27 01:00:00 | action=BEGIN pkg_etl.run_daily; END;
DEMOAPP.JOB_SYNC_CLIENTES   | enabled=FALSE | state=DISABLED | next=- | action=BEGIN sync_clientes(); END;

=== JOBS (DBMS_JOB) ===
DEMOAPP.128 | enabled=TRUE | next=2025-09-11 03:00:00 | what=BEGIN antiga_rotina(); END;






Execução (quem está rodando) com leitura guiada

-- Agora vamos ver se o job XPTO está rodando e há quanto tempo

BEGIN
  pkg_job_admin.list_running_jobs(p_like => 'JOB_XPTO_%');
END;
/



--- OUTPUT : 

=== RUNNING (SCHEDULER) ===
DEMOAPP.JOB_XPTO_ETL_DIARIO | sid=123 | inst=1 | elapsed=00 00:07:32 | cpu=00 00:02:10

=== RUNNING (DBMS_JOB) ===
-- (vazio, se nada estiver executando)






Operações pontuais: habilitar/desabilitar

-- DBMS_SCHEDULER por nome (owner informado — mais explícito):

BEGIN
  -- desativar e forçar parada se estiver rodando
  pkg_job_admin.disable_job('DEMOAPP.JOB_XPTO_ETL_DIARIO', p_stop_running => TRUE);
  -- reativar
  pkg_job_admin.enable_job ('DEMOAPP.JOB_XPTO_ETL_DIARIO');
END;
/



--- DBMS_JOB por número (owner opcional, útil para validar pertinência):

BEGIN
  -- desativar e forçar parada se estiver rodando
  pkg_job_admin.disable_job('128', p_owner => 'DEMOAPP', p_stop_running => TRUE);
  -- reativar
  pkg_job_admin.enable_job ('128', p_owner => 'DEMOAPP');
END;
/



-- O que acontece por trás:
--   • SCHEDULER: STOP_JOB(..., force => TRUE) (se p_stop_running=>TRUE), seguido de DISABLE/ENABLE.
--   • DBMS_JOB: encerra sessão (se ativa) e marca BROKEN (NOT p_enable).






Operações em lote: desabilitar todos os “JOB_XPTO_%” do SCHEDULER

-- Cenário: janela de manutenção requer pausar rotinas XPTO do schema de aplicação.

--- Desativar
BEGIN
  pkg_job_admin.set_jobs_by_pattern(
    p_name_like    => 'JOB_XPTO_%',
    p_enable       => FALSE,                              -- desativar
    p_owner        => 'DEMOAPP',
    p_type         => pkg_job_admin.c_type_scheduler,     
    p_stop_running => TRUE                                -- encerrar active session job
  );
END;
/



--- Reativar
BEGIN
  pkg_job_admin.set_jobs_by_pattern(
    p_name_like    => 'JOB_XPTO_%',
    p_enable       => TRUE,                               -- reativar
    p_owner        => 'DEMOAPP',
    p_type         => pkg_job_admin.c_type_scheduler
  );
END;
/






Analisar o job ‘JOB_XPTO_ETL_DIARIO’ especificamente

-- 1. Está presente? Quando roda?

SELECT *
FROM   job_admin_all_jobs_v
WHERE  owner = 'DEMOAPP' AND job_name = 'JOB_XPTO_ETL_DIARIO';


-- 2. Está executando agora? Há quanto tempo?
SELECT running_instance, elapsed_time
FROM   job_admin_running_v
WHERE  owner = 'DEMOAPP' AND job_name = 'JOB_XPTO_ETL_DIARIO';

-- 3. Pausar com segurança (parar se ativo, depois desabilitar)
BEGIN
  pkg_job_admin.disable_job('DEMOAPP.JOB_XPTO_ETL_DIARIO', p_stop_running => TRUE);
END;
/

-- 4. Reativar
BEGIN
  pkg_job_admin.enable_job('DEMOAPP.JOB_XPTO_ETL_DIARIO');
END;
/






Qual a serventia dessa solução ?

Ambientes Oracle costumam conviver com dois mecanismos de agendamento: DBMS_SCHEDULER (moderno) e DBMS_JOB (legado). Quando precisamos inventariar, monitorar, habilitar/desabilitar ou gerenciar em lote ambos os tipos, a experiência comum é assimétrica e dispersa.
Este projeto entrega uma interface única e consistente para manipular os dois mundos:

  • Visões consolidadas para inventário e execução.
  • Um pacote PL/SQL com comandos simples e legíveis para operações pontuais e em lote.
  • Role e sinônimos públicos para padronizar o consumo de forma segura.






Abaixo consta o codigo inteiro do pacote pkg_job_admin

-- =================================================================================================
--  job_admin_setup_completo.sql
--  Administração unificada de jobs no Oracle Database (DBMS_SCHEDULER e DBMS_JOB)
--  Autor: Carlos Furushima — Furushima IT
--  Direitos Autorais: Este script é propriedade intelectual de Furushima IT. Todos os direitos reservados.
--
--  OBJETIVO
--  --------
--  Fornecer uma interface única, consistente e profissional para:
--    • Inventariar jobs do SCHEDULER e do DBMS_JOB em visões consolidadas;
--    • Consultar jobs em execução (tempo decorrido e sessão, quando aplicável);
--    • Habilitar/Desabilitar jobs individualmente (por nome ou número);
--    • Habilitar/Desabilitar em lote por padrão de nome;
--    • Padronizar o consumo por meio de uma ROLE e sinônimos públicos.
--
--  O QUE É CRIADO
--  --------------
--    Views:
--      - JOB_ADMIN_ALL_JOBS      : inventário unificado (SCHEDULER + DBMS_JOB)
--      - JOB_ADMIN_RUNNING       : execução unificada (SCHEDULER + DBMS_JOB)
--      - JOB_ADMIN_ALL_JOBS_V    : inventário formatado para leitura humana
--      - JOB_ADMIN_RUNNING_V     : execução formatada para leitura humana
--
--    Pacote:
--      - PKG_JOB_ADMIN           : utilitários de listagem e (des)ativação unificada
--
--    Segurança e Acesso:
--      - ROLE JOBDBMSSCHEDULERALL com grants de SELECT/EXECUTE
--      - Sinônimos públicos para facilitar o uso por quem tiver a ROLE
--
--  PRÉ-REQUISITOS
--  --------------
--  • Executar com um usuário com privilégios para consultar dicionários DBA_* e GV$* e criar objetos.
--  • O pacote é AUTHID DEFINER (permissões do dono do pacote). Usuários finais só precisam da ROLE.
--
--  USO TÍPICO
--  ----------
--  -- Inventário:
--  BEGIN
--    pkg_job_admin.list_jobs;                       -- tudo
--    pkg_job_admin.list_jobs('DEMOAPP');            -- por owner
--    pkg_job_admin.list_jobs(p_like => 'JOB_%');    -- por padrão (LIKE)
--  END;
--  /
--
--  -- Executando agora:
--  BEGIN
--    pkg_job_admin.list_running_jobs;                       -- tudo
--    pkg_job_admin.list_running_jobs('DEMOAPP');            -- por owner
--    pkg_job_admin.list_running_jobs(p_like => 'SYNC_%');   -- por padrão (LIKE)
--  END;
--  /
--
--  -- Operações pontuais:
--  -- SCHEDULER (identificador por nome, owner opcional se único no banco)
--  -- BEGIN
--  --   pkg_job_admin.disable_job('DEMOAPP.JOB_ATUALIZA_INDICES', p_stop_running => TRUE);
--  --   pkg_job_admin.enable_job ('DEMOAPP.JOB_ATUALIZA_INDICES');
--  -- END;
--  -- /
--  --
--  -- DBMS_JOB (identificador numérico; owner opcional apenas para validar pertinência)
--  -- BEGIN
--  --   pkg_job_admin.disable_job('128', p_owner => 'SYS', p_stop_running => TRUE);
--  --   pkg_job_admin.enable_job ('128', p_owner => 'SYS');
--  -- END;
--  -- /
--
--  -- Lote por padrão (por tipo):
--  -- BEGIN
--  --   pkg_job_admin.set_jobs_by_pattern(
--  --     p_name_like    => 'JOB_SYNC_%',
--  --     p_enable       => FALSE,                               -- desativar
--  --     p_owner        => 'DEMOAPP',                           -- opcional
--  --     p_type         => pkg_job_admin.c_type_scheduler,      -- SCHEDULER | DBMS_JOB | ANY
--  --     p_stop_running => TRUE
--  --   );
--  -- END;
--  -- /
--
--  DICAS
--  -----
--  • O script é idempotente (CREATE OR REPLACE; checagens seguras). Pode ser executado mais de uma vez.
--  • Saída “limpa”: mensagens organizadas por PROMPT/DBMS_OUTPUT para auditoria e documentação.
-- =================================================================================================

SET FEEDBACK ON
SET SERVEROUTPUT ON SIZE UNLIMITED
SET VERIFY OFF

PROMPT
PROMPT ═════════════════════════════════════════════════════════════════════════════════════════════
PROMPT   Iniciando job_admin_setup_completo.sql
PROMPT ═════════════════════════════════════════════════════════════════════════════════════════════

-- -----------------------------------------------------------------------------------------------
-- 0) CONTEXTO — Mostra o usuário (owner) sob o qual os objetos serão criados
-- -----------------------------------------------------------------------------------------------
DECLARE
  l_user VARCHAR2(128) := USER;
BEGIN
  DBMS_OUTPUT.PUT_LINE('Owner atual: '||l_user);
END;
/

PROMPT
PROMPT ── 1) Criando/Atualizando Views Consolidadas …

-- ===============================================================================================
-- 1.1) JOB_ADMIN_ALL_JOBS
--      Consolida em uma única visão os jobs do DBMS_SCHEDULER e do DBMS_JOB.
--      Observações de tipos:
--        • next_run_date / last_start_date: TIMESTAMP WITH TIME ZONE (SCHEDULER) x DATE (DBMS_JOB)
--          -> o Oracle promove DATE para TIMESTAMP durante o UNION ALL (sem perda de funcionalidade).
--        • last_run_duration: INTERVAL no SCHEDULER; CAST de NULL para INTERVAL no DBMS_JOB.
--        • job_action / repeat_interval: normalizados para VARCHAR2 (até 4000) no DBMS_JOB.
-- ===============================================================================================
CREATE OR REPLACE VIEW job_admin_all_jobs AS
    SELECT owner,
           job_name,
           'SCHEDULER'               AS job_type,
           CASE WHEN enabled = 'TRUE' THEN 'TRUE' ELSE 'FALSE' END AS enabled,
           state,
           next_run_date,                                -- TIMESTAMP WITH TIME ZONE
           last_start_date,                              -- TIMESTAMP WITH TIME ZONE
           last_run_duration,                            -- INTERVAL DAY TO SECOND
           job_action,                                   -- VARCHAR2/CLOB (na maioria dos casos, até 4000)
           repeat_interval                               -- VARCHAR2
      FROM dba_scheduler_jobs
  UNION ALL
    SELECT schema_user                                   AS owner,
           TO_CHAR(job)                                  AS job_name,
           'DBMS_JOB'                                    AS job_type,
           CASE broken WHEN 'Y' THEN 'FALSE' ELSE 'TRUE' END AS enabled,
           CAST(NULL AS VARCHAR2(30))                    AS state,
           next_date,                                    -- DATE (promovido a TIMESTAMP no UNION)
           last_date                                     AS last_start_date, -- DATE
           CAST(NULL AS INTERVAL DAY TO SECOND)          AS last_run_duration,
           SUBSTR(what,     4000)                        AS job_action,      -- LONG -> VARCHAR2
           SUBSTR(interval, 4000)                        AS repeat_interval  -- LONG -> VARCHAR2
      FROM dba_jobs
;

-- ===============================================================================================
-- 1.2) JOB_ADMIN_RUNNING
--      Consolida os jobs em execução. Para DBMS_JOB, calcula elapsed pelo logon_time da sessão.
--      Observações:
--        • cpu_used não se aplica ao DBMS_JOB; representa-se com NULL (cast para INTERVAL).
-- ===============================================================================================
CREATE OR REPLACE VIEW job_admin_running AS
    SELECT owner,
           job_name,
           'SCHEDULER'               AS job_type,
           session_id,
           running_instance,
           elapsed_time,                                 -- INTERVAL
           cpu_used                                      -- INTERVAL
      FROM dba_scheduler_running_jobs
  UNION ALL
    SELECT j.schema_user           AS owner,
           TO_CHAR(r.job)          AS job_name,
           'DBMS_JOB'              AS job_type,
           r.sid                   AS session_id,
           s.inst_id               AS running_instance,
           NUMTODSINTERVAL(TRUNC((SYSDATE - s.logon_time)*86400),'SECOND') AS elapsed_time,
           CAST(NULL AS INTERVAL DAY TO SECOND)          AS cpu_used
      FROM dba_jobs_running r
      JOIN gv$session s ON s.sid = r.sid
      JOIN dba_jobs j   ON j.job = r.job
;

-- ===============================================================================================
-- 1.3) JOB_ADMIN_ALL_JOBS_V — Versão “amigável” (formatos para leitura humana)
-- ===============================================================================================
CREATE OR REPLACE VIEW job_admin_all_jobs_v AS
SELECT owner, job_name, job_type, enabled, state,
       TO_CHAR(next_run_date,'YYYY-MM-DD HH24:MI:SS')      AS next_run_date,
       TO_CHAR(last_start_date,'YYYY-MM-DD HH24:MI:SS')    AS last_start_date,
       CASE WHEN last_run_duration IS NULL THEN '-' ELSE
         EXTRACT(DAY FROM last_run_duration)||' '||
         LPAD(EXTRACT(HOUR FROM last_run_duration),2,'0')||':'||
         LPAD(EXTRACT(MINUTE FROM last_run_duration),2,'0')||':'||
         LPAD(TRUNC(EXTRACT(SECOND FROM last_run_duration)),2,'0')
       END AS last_run_duration,
       job_action, repeat_interval
  FROM job_admin_all_jobs
;

-- ===============================================================================================
-- 1.4) JOB_ADMIN_RUNNING_V — Versão “amigável” (formatos para leitura humana)
-- ===============================================================================================
CREATE OR REPLACE VIEW job_admin_running_v AS
SELECT owner, job_name, job_type, session_id, running_instance,
       CASE WHEN elapsed_time IS NULL THEN '-' ELSE
         EXTRACT(DAY FROM elapsed_time)||' '||
         LPAD(EXTRACT(HOUR FROM elapsed_time),2,'0')||':'||
         LPAD(EXTRACT(MINUTE FROM elapsed_time),2,'0')||':'||
         LPAD(TRUNC(EXTRACT(SECOND FROM elapsed_time)),2,'0')
       END AS elapsed_time,
       CASE WHEN cpu_used IS NULL THEN '-' ELSE
         EXTRACT(DAY FROM cpu_used)||' '||
         LPAD(EXTRACT(HOUR FROM cpu_used),2,'0')||':'||
         LPAD(EXTRACT(MINUTE FROM cpu_used),2,'0')||':'||
         LPAD(TRUNC(EXTRACT(SECOND FROM cpu_used)),2,'0')
       END AS cpu_used
  FROM job_admin_running
;

PROMPT ✓ Views criadas/atualizadas com sucesso.

PROMPT
PROMPT ── 2) Criando/Atualizando Pacote (SPEC) …

-- =================================================================================================
-- 2) ESPECIFICAÇÃO DO PACOTE — PKG_JOB_ADMIN
--    Contém constantes de tipo e procedimentos públicos de listagem e (des)ativação unificada.
-- =================================================================================================
CREATE OR REPLACE PACKAGE pkg_job_admin AUTHID DEFINER AS
  -- Constantes para filtro por tipo
  c_type_scheduler CONSTANT VARCHAR2(11) := 'SCHEDULER';
  c_type_dbms_job  CONSTANT VARCHAR2(8)  := 'DBMS_JOB';
  c_type_any       CONSTANT VARCHAR2(3)  := 'ANY';

  -- Listagem de jobs (inventário)
  PROCEDURE list_jobs(
    p_owner IN VARCHAR2 DEFAULT NULL,   -- filtra por owner
    p_like  IN VARCHAR2 DEFAULT NULL    -- filtra por padrão (LIKE)
  );

  -- Listagem de jobs em execução
  PROCEDURE list_running_jobs(
    p_owner IN VARCHAR2 DEFAULT NULL,   -- filtra por owner
    p_like  IN VARCHAR2 DEFAULT NULL    -- filtra por padrão (LIKE)
  );

  -- Habilita/Desabilita um job (identificador por nome SCHEDULER ou número DBMS_JOB)
  PROCEDURE set_job_enabled(
    p_job_identifier IN VARCHAR2,       -- 'OWNER.JOB_NAME' | 'JOB_NAME' | '123' (DBMS_JOB)
    p_enable         IN BOOLEAN,        -- TRUE = ENABLE; FALSE = DISABLE
    p_owner          IN VARCHAR2 DEFAULT NULL,  -- para SCHEDULER sem owner único ou DBMS_JOB para validar owner
    p_stop_running   IN BOOLEAN DEFAULT FALSE   -- encerra execução corrente (se houver)
  );

  -- Açúcares sintáticos (conveniências)
  PROCEDURE enable_job(
    p_job_identifier IN VARCHAR2,
    p_owner          IN VARCHAR2 DEFAULT NULL,
    p_stop_running   IN BOOLEAN DEFAULT FALSE
  );

  PROCEDURE disable_job(
    p_job_identifier IN VARCHAR2,
    p_owner          IN VARCHAR2 DEFAULT NULL,
    p_stop_running   IN BOOLEAN DEFAULT TRUE
  );

  -- Habilita/Desabilita em lote por padrão de nome e tipo
  PROCEDURE set_jobs_by_pattern(
    p_name_like     IN VARCHAR2,                      -- ex: 'JOB_SYNC_%'
    p_enable        IN BOOLEAN,                       -- TRUE/FALSE
    p_owner         IN VARCHAR2 DEFAULT NULL,         -- opcional
    p_type          IN VARCHAR2 DEFAULT c_type_any,   -- SCHEDULER | DBMS_JOB | ANY
    p_stop_running  IN BOOLEAN DEFAULT FALSE
  );
END pkg_job_admin;
/
SHOW ERRORS

PROMPT
PROMPT ── 3) Criando/Atualizando Pacote (BODY) …

-- =================================================================================================
-- 3) CORPO DO PACOTE — PKG_JOB_ADMIN
--    Implementa utilitários de formatação, detecção de owner/instância, e operações de controle.
-- =================================================================================================
CREATE OR REPLACE PACKAGE BODY pkg_job_admin AS

  -- ---------------------------------------------------------------------------------------------
  -- Função auxiliar: formata INTERVAL DAY TO SECOND em 'DD HH24:MI:SS'
  -- ---------------------------------------------------------------------------------------------
  FUNCTION fmt_ds(p INTERVAL DAY TO SECOND) RETURN VARCHAR2 IS
    l_day  PLS_INTEGER; l_hour PLS_INTEGER; l_min PLS_INTEGER; l_sec PLS_INTEGER;
  BEGIN
    IF p IS NULL THEN RETURN '-'; END IF;
    l_day  := EXTRACT(DAY    FROM p);
    l_hour := EXTRACT(HOUR   FROM p);
    l_min  := EXTRACT(MINUTE FROM p);
    l_sec  := TRUNC(EXTRACT(SECOND FROM p));
    RETURN LPAD(l_day,2,'0')||' '||LPAD(l_hour,2,'0')||':'||LPAD(l_min,2,'0')||':'||LPAD(l_sec,2,'0');
  END fmt_ds;

  -- ---------------------------------------------------------------------------------------------
  -- Função auxiliar: TO_CHAR seguro para datas que podem ser NULL (retorna '-' no NULL)
  -- ---------------------------------------------------------------------------------------------
  FUNCTION to_char_nvl(p_date DATE) RETURN VARCHAR2 IS
  BEGIN
    RETURN CASE WHEN p_date IS NULL THEN '-' ELSE TO_CHAR(p_date,'YYYY-MM-DD HH24:MI:SS') END;
  END;

  -- ---------------------------------------------------------------------------------------------
  -- Monta o nome totalmente qualificado do job de SCHEDULER
  -- ---------------------------------------------------------------------------------------------
  FUNCTION sched_job_fqname(p_owner IN VARCHAR2, p_job_name IN VARCHAR2) RETURN VARCHAR2 IS
  BEGIN
    RETURN CASE WHEN p_owner IS NOT NULL THEN UPPER(p_owner)||'.'||UPPER(p_job_name) ELSE UPPER(p_job_name) END;
  END;

  -- ---------------------------------------------------------------------------------------------
  -- Solicita parada de job SCHEDULER, se estiver em execução (silencioso em falhas)
  -- ---------------------------------------------------------------------------------------------
  PROCEDURE stop_scheduler_if_running(p_owner IN VARCHAR2, p_job_name IN VARCHAR2) IS
    l_fq VARCHAR2(261) := sched_job_fqname(p_owner, p_job_name);
  BEGIN
    DBMS_SCHEDULER.STOP_JOB(job_name => l_fq, force => TRUE);
  EXCEPTION
    WHEN OTHERS THEN NULL;
  END;

  -- ---------------------------------------------------------------------------------------------
  -- Encerra sessão de DBMS_JOB, se houver (suporte RAC com @inst_id)
  -- ---------------------------------------------------------------------------------------------
  PROCEDURE kill_dbms_job_session(p_job_id IN NUMBER) IS
    l_sid     NUMBER;
    l_serial  NUMBER;
    l_inst    NUMBER;
    l_stmt    VARCHAR2(200);
  BEGIN
    SELECT s.sid, s.serial#, s.inst_id
      INTO l_sid, l_serial, l_inst
      FROM dba_jobs_running r
      JOIN gv$session s ON s.sid = r.sid
     WHERE r.job = p_job_id
       AND ROWNUM = 1;

    l_stmt := 'ALTER SYSTEM KILL SESSION '''||l_sid||','||l_serial||
              CASE WHEN l_inst IS NOT NULL THEN ',@'||l_inst ELSE NULL END||''' IMMEDIATE';
    EXECUTE IMMEDIATE l_stmt;
  EXCEPTION
    WHEN NO_DATA_FOUND THEN NULL;
    WHEN OTHERS THEN NULL;
  END;

  -- ---------------------------------------------------------------------------------------------
  -- Resolve owner de job SCHEDULER quando usuário não informou p_owner
  -- Lança erro se não encontrado ou se existir em múltiplos owners.
  -- ---------------------------------------------------------------------------------------------
  PROCEDURE resolve_scheduler(p_owner IN VARCHAR2, p_job_name IN VARCHAR2,
                              o_owner OUT VARCHAR2, o_job_name OUT VARCHAR2) IS
    l_cnt INTEGER;
  BEGIN
    o_job_name := UPPER(p_job_name);
    IF p_owner IS NOT NULL THEN o_owner := UPPER(p_owner); RETURN; END IF;

    SELECT COUNT(DISTINCT owner) INTO l_cnt
      FROM dba_scheduler_jobs
     WHERE job_name = o_job_name;

    IF l_cnt = 0 THEN
      RAISE NO_DATA_FOUND;
    ELSIF l_cnt > 1 THEN
      RAISE_APPLICATION_ERROR(-20002,
        'JOB_NAME existe em múltiplos owners. Informe p_owner. JOB_NAME='||o_job_name);
    ELSE
      SELECT owner INTO o_owner
        FROM dba_scheduler_jobs
       WHERE job_name = o_job_name
         AND ROWNUM = 1;
    END IF;
  END resolve_scheduler;

  -- ---------------------------------------------------------------------------------------------
  -- Listagem: inventário unificado
  -- ---------------------------------------------------------------------------------------------
  PROCEDURE list_jobs(p_owner IN VARCHAR2 DEFAULT NULL, p_like IN VARCHAR2 DEFAULT NULL) IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE('=== JOBS (SCHEDULER) ===');
    FOR r IN (
      SELECT owner, job_name, enabled, state, next_run_date, job_action
        FROM dba_scheduler_jobs
       WHERE (p_owner IS NULL OR owner = UPPER(p_owner))
         AND (p_like  IS NULL OR job_name LIKE UPPER(p_like))
       ORDER BY owner, job_name
    ) LOOP
      DBMS_OUTPUT.PUT_LINE(r.owner||'.'||r.job_name||
                           ' | enabled='||r.enabled||
                           ' | state='||NVL(r.state,'-')||
                           ' | next='||to_char_nvl(CAST(r.next_run_date AS DATE))||
                           ' | action='||SUBSTR(r.job_action,1,60));
    END LOOP;

    DBMS_OUTPUT.PUT_LINE(CHR(10)||'=== JOBS (DBMS_JOB) ===');
    FOR r IN (
      SELECT schema_user AS owner, job AS job_id, DECODE(broken,'Y','FALSE','TRUE') AS enabled,
             next_date, what
        FROM dba_jobs
       WHERE (p_owner IS NULL OR schema_user = UPPER(p_owner))
         AND (p_like  IS NULL OR TO_CHAR(job) LIKE p_like OR UPPER(what) LIKE UPPER(p_like))
       ORDER BY schema_user, job
    ) LOOP
      DBMS_OUTPUT.PUT_LINE(r.owner||'.'||r.job_id||
                           ' | enabled='||r.enabled||
                           ' | next='||to_char_nvl(r.next_date)||
                           ' | what='||SUBSTR(r.what,1,60));
    END LOOP;
  END list_jobs;

  -- ---------------------------------------------------------------------------------------------
  -- Listagem: execução unificada
  -- ---------------------------------------------------------------------------------------------
  PROCEDURE list_running_jobs(p_owner IN VARCHAR2 DEFAULT NULL, p_like IN VARCHAR2 DEFAULT NULL) IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE('=== RUNNING (SCHEDULER) ===');
    FOR r IN (
      SELECT owner, job_name, session_id, running_instance, elapsed_time, cpu_used
        FROM dba_scheduler_running_jobs
       WHERE (p_owner IS NULL OR owner = UPPER(p_owner))
         AND (p_like  IS NULL OR job_name LIKE UPPER(p_like))
       ORDER BY running_instance, job_name
    ) LOOP
      DBMS_OUTPUT.PUT_LINE(r.owner||'.'||r.job_name||
                           ' | sid='||r.session_id||
                           ' | inst='||r.running_instance||
                           ' | elapsed='||fmt_ds(r.elapsed_time)||
                           ' | cpu='||fmt_ds(r.cpu_used));
    END LOOP;

    DBMS_OUTPUT.PUT_LINE(CHR(10)||'=== RUNNING (DBMS_JOB) ===');
    FOR r IN (
      SELECT j.schema_user AS owner, r.job AS job_id, s.sid, s.serial#,
             s.inst_id AS running_instance,
             NUMTODSINTERVAL(TRUNC((SYSDATE - s.logon_time)*86400),'SECOND') AS elapsed_time,
             j.what
        FROM dba_jobs_running r
        JOIN gv$session s ON s.sid = r.sid
        JOIN dba_jobs j   ON j.job = r.job
       WHERE (p_owner IS NULL OR j.schema_user = UPPER(p_owner))
         AND (p_like  IS NULL OR TO_CHAR(r.job) LIKE p_like OR UPPER(j.what) LIKE UPPER(p_like))
       ORDER BY running_instance, job_id
    ) LOOP
      DBMS_OUTPUT.PUT_LINE(r.owner||'.'||r.job_id||
                           ' | sid='||r.sid||','||r.serial#||
                           ' | inst='||r.running_instance||
                           ' | elapsed='||fmt_ds(r.elapsed_time));
    END LOOP;
  END list_running_jobs;

  -- ---------------------------------------------------------------------------------------------
  -- Núcleo: habilita/desabilita por identificador (nome SCHEDULER ou número DBMS_JOB)
  -- ---------------------------------------------------------------------------------------------
  PROCEDURE set_job_enabled(
    p_job_identifier IN VARCHAR2,
    p_enable         IN BOOLEAN,
    p_owner          IN VARCHAR2 DEFAULT NULL,
    p_stop_running   IN BOOLEAN DEFAULT FALSE
  ) IS
    l_owner    VARCHAR2(128);
    l_job_name VARCHAR2(128);
    l_job_id   NUMBER;
  BEGIN
    -- 1) Tentar SCHEDULER (nome)
    BEGIN
      IF REGEXP_LIKE(p_job_identifier,'^\d+$') THEN RAISE NO_DATA_FOUND; END IF;

      IF INSTR(p_job_identifier,'.') > 0 THEN
        l_owner    := UPPER(REGEXP_SUBSTR(p_job_identifier,'^([^\.]+)'));
        l_job_name := UPPER(REGEXP_SUBSTR(p_job_identifier,'\.([^\.]+)$',1,1,NULL,1));
      ELSE
        l_job_name := UPPER(p_job_identifier);
        resolve_scheduler(p_owner, l_job_name, l_owner, l_job_name);
      END IF;

      -- valida existência
      DECLARE l_dummy PLS_INTEGER;
      BEGIN
        SELECT 1 INTO l_dummy
          FROM dba_scheduler_jobs
         WHERE owner = l_owner AND job_name = l_job_name AND ROWNUM = 1;
      END;

      IF p_stop_running THEN
        stop_scheduler_if_running(l_owner, l_job_name);
      END IF;

      IF p_enable THEN
        DBMS_SCHEDULER.ENABLE( sched_job_fqname(l_owner, l_job_name) );
      ELSE
        DBMS_SCHEDULER.DISABLE( sched_job_fqname(l_owner, l_job_name) );
      END IF;

      DBMS_OUTPUT.PUT_LINE('OK (SCHEDULER) '||
        sched_job_fqname(l_owner,l_job_name)||' => '||
        CASE WHEN p_enable THEN 'ENABLED' ELSE 'DISABLED' END);

      RETURN;
    EXCEPTION
      WHEN NO_DATA_FOUND THEN NULL; -- não era scheduler; tentar DBMS_JOB
    END;

    -- 2) Tentar DBMS_JOB (número)
    BEGIN
      l_job_id := TO_NUMBER(TRIM(p_job_identifier));

      -- valida existência (restringe owner se informado)
      DECLARE l_dummy2 PLS_INTEGER;
      BEGIN
        SELECT 1 INTO l_dummy2
          FROM dba_jobs
         WHERE job = l_job_id
           AND (p_owner IS NULL OR schema_user = UPPER(p_owner))
           AND ROWNUM = 1;
      END;

      IF p_stop_running THEN
        kill_dbms_job_session(l_job_id);
      END IF;

      DBMS_JOB.BROKEN(l_job_id, NOT p_enable);

      DBMS_OUTPUT.PUT_LINE('OK (DBMS_JOB) '||l_job_id||' => '||
        CASE WHEN p_enable THEN 'ENABLED' ELSE 'DISABLED' END);

      RETURN;
    EXCEPTION
      WHEN VALUE_ERROR    THEN NULL;
      WHEN INVALID_NUMBER THEN NULL;
      WHEN NO_DATA_FOUND  THEN NULL;
    END;

    -- 3) Não encontrado em nenhuma modalidade
    RAISE_APPLICATION_ERROR(-20001,
      'Job não encontrado como SCHEDULER (por nome) nem DBMS_JOB (por número): '||p_job_identifier);
  END set_job_enabled;

  -- Açúcares sintáticos
  PROCEDURE enable_job(
    p_job_identifier IN VARCHAR2,
    p_owner          IN VARCHAR2 DEFAULT NULL,
    p_stop_running   IN BOOLEAN  DEFAULT FALSE
  ) IS
  BEGIN
    set_job_enabled(p_job_identifier => p_job_identifier,
                    p_enable         => TRUE,
                    p_owner          => p_owner,
                    p_stop_running   => p_stop_running);
  END;

  PROCEDURE disable_job(
    p_job_identifier IN VARCHAR2,
    p_owner          IN VARCHAR2 DEFAULT NULL,
    p_stop_running   IN BOOLEAN  DEFAULT TRUE
  ) IS
  BEGIN
    set_job_enabled(p_job_identifier => p_job_identifier,
                    p_enable         => FALSE,
                    p_owner          => p_owner,
                    p_stop_running   => p_stop_running);
  END;

  -- ---------------------------------------------------------------------------------------------
  -- Habilita/Desabilita em lote por padrão (LIKE) e, opcionalmente, por tipo
  -- ---------------------------------------------------------------------------------------------
  PROCEDURE set_jobs_by_pattern(
    p_name_like     IN VARCHAR2,
    p_enable        IN BOOLEAN,
    p_owner         IN VARCHAR2 DEFAULT NULL,
    p_type          IN VARCHAR2 DEFAULT c_type_any,
    p_stop_running  IN BOOLEAN  DEFAULT FALSE
  ) IS
  BEGIN
    -- SCHEDULER
    IF UPPER(p_type) IN (c_type_any, c_type_scheduler) THEN
      FOR r IN (
        SELECT owner, job_name
          FROM dba_scheduler_jobs
         WHERE (p_owner IS NULL OR owner = UPPER(p_owner))
           AND job_name LIKE UPPER(p_name_like)
      ) LOOP
        set_job_enabled(r.owner||'.'||r.job_name, p_enable, NULL, p_stop_running);
      END LOOP;
    END IF;

    -- DBMS_JOB
    IF UPPER(p_type) IN (c_type_any, c_type_dbms_job) THEN
      FOR r IN (
        SELECT schema_user AS owner, job
          FROM dba_jobs
         WHERE (p_owner IS NULL OR schema_user = UPPER(p_owner))
           AND (TO_CHAR(job) LIKE p_name_like OR UPPER(what) LIKE UPPER(p_name_like))
      ) LOOP
        set_job_enabled(TO_CHAR(r.job), p_enable, r.owner, p_stop_running);
      END LOOP;
    END IF;
  END set_jobs_by_pattern;

END pkg_job_admin;
/
SHOW ERRORS

PROMPT ✓ Pacote criado/atualizado com sucesso.

PROMPT
PROMPT ── 4) Segurança: ROLE, Grants e Sinônimos Públicos …

-- =================================================================================================
-- 4.1) ROLE — criada apenas se não existir
-- =================================================================================================
DECLARE
  l_cnt INTEGER;
BEGIN
  SELECT COUNT(*) INTO l_cnt FROM dba_roles WHERE role = 'JOBDBMSSCHEDULERALL';
  IF l_cnt = 0 THEN
    EXECUTE IMMEDIATE 'CREATE ROLE JOBDBMSSCHEDULERALL';
    DBMS_OUTPUT.PUT_LINE('Role JOBDBMSSCHEDULERALL criada.');
  ELSE
    DBMS_OUTPUT.PUT_LINE('Role JOBDBMSSCHEDULERALL já existente.');
  END IF;
END;
/

-- =================================================================================================
-- 4.2) GRANTS — executar o pacote e consultar as views por meio da ROLE
-- =================================================================================================
GRANT EXECUTE ON pkg_job_admin        TO JOBDBMSSCHEDULERALL;
GRANT SELECT  ON job_admin_all_jobs   TO JOBDBMSSCHEDULERALL;
GRANT SELECT  ON job_admin_running    TO JOBDBMSSCHEDULERALL;
GRANT SELECT  ON job_admin_all_jobs_v TO JOBDBMSSCHEDULERALL;
GRANT SELECT  ON job_admin_running_v  TO JOBDBMSSCHEDULERALL;

-- =================================================================================================
-- 4.3) SINÔNIMOS PÚBLICOS — apontam para os objetos do OWNER atual
--     Observação: garantem uso uniforme via “public synonyms” para quem tiver a ROLE.
-- =================================================================================================
DECLARE
  l_owner VARCHAR2(128) := USER;
BEGIN
  EXECUTE IMMEDIATE 'CREATE OR REPLACE PUBLIC SYNONYM job_admin_all_jobs   FOR '||l_owner||'.job_admin_all_jobs';
  EXECUTE IMMEDIATE 'CREATE OR REPLACE PUBLIC SYNONYM job_admin_running    FOR '||l_owner||'.job_admin_running';
  EXECUTE IMMEDIATE 'CREATE OR REPLACE PUBLIC SYNONYM job_admin_all_jobs_v FOR '||l_owner||'.job_admin_all_jobs_v';
  EXECUTE IMMEDIATE 'CREATE OR REPLACE PUBLIC SYNONYM job_admin_running_v  FOR '||l_owner||'.job_admin_running_v';
  EXECUTE IMMEDIATE 'CREATE OR REPLACE PUBLIC SYNONYM pkg_job_admin        FOR '||l_owner||'.pkg_job_admin';
  DBMS_OUTPUT.PUT_LINE('Sinônimos públicos criados/atualizados.');
END;
/

PROMPT ✓ Segurança configurada (ROLE + grants + sinônimos públicos).

PROMPT
PROMPT ── 5) Auto-Validação (não intrusiva) …

-- =================================================================================================
-- 5.1) Verifica erros de compilação do pacote
-- =================================================================================================
DECLARE
  l_errs INTEGER;
BEGIN
  SELECT COUNT(*) INTO l_errs
    FROM user_errors
   WHERE name = 'PKG_JOB_ADMIN';

  IF l_errs = 0 THEN
    DBMS_OUTPUT.PUT_LINE('Pacote PKG_JOB_ADMIN sem erros de compilação.');
  ELSE
    DBMS_OUTPUT.PUT_LINE('ATENÇÃO: Encontrado(s) '||l_errs||' erro(s) em PKG_JOB_ADMIN:');
    FOR r IN (
      SELECT type, name, line, position, SUBSTR(text,1,200) AS text
        FROM user_errors
       WHERE name = 'PKG_JOB_ADMIN'
       ORDER BY sequence
    ) LOOP
      DBMS_OUTPUT.PUT_LINE(r.type||' '||r.name||' @'||r.line||':'||r.position||' - '||r.text);
    END LOOP;
  END IF;
END;
/

-- =================================================================================================
-- 5.2) Validação simples de acesso às views (sanidade)
-- =================================================================================================
DECLARE
  l_all NUMBER; l_run NUMBER;
BEGIN
  SELECT COUNT(*) INTO l_all FROM job_admin_all_jobs WHERE ROWNUM <= 1;
  SELECT COUNT(*) INTO l_run FROM job_admin_running  WHERE ROWNUM <= 1;
  DBMS_OUTPUT.PUT_LINE('Views acessíveis: JOB_ADMIN_ALL_JOBS e JOB_ADMIN_RUNNING OK.');
END;
/

PROMPT
PROMPT ── 6) Exemplos de Uso (para referência; copiar/colar quando necessário) …

PROMPT
PROMPT -- Inventário:
PROMPT BEGIN
PROMPT   pkg_job_admin.list_jobs;                          -- todos os jobs
PROMPT   pkg_job_admin.list_jobs('DEMOAPP');               -- por owner
PROMPT   pkg_job_admin.list_jobs(p_like => 'JOB_%');       -- por padrão (LIKE)
PROMPT END;
PROMPT /

PROMPT -- Em execução agora:
PROMPT BEGIN
PROMPT   pkg_job_admin.list_running_jobs;                       -- tudo
PROMPT   pkg_job_admin.list_running_jobs('DEMOAPP');            -- por owner
PROMPT   pkg_job_admin.list_running_jobs(p_like => 'SYNC_%');   -- por padrão (LIKE)
PROMPT END;
PROMPT /

PROMPT -- Operações pontuais:
PROMPT -- SCHEDULER (por nome; owner opcional se único no banco)
PROMPT -- BEGIN
PROMPT --   pkg_job_admin.disable_job('DEMOAPP.JOB_ATUALIZA_INDICES', p_stop_running => TRUE);
PROMPT --   pkg_job_admin.enable_job ('DEMOAPP.JOB_ATUALIZA_INDICES');
PROMPT -- END;
PROMPT -- /
PROMPT -- DBMS_JOB (por número; owner apenas para validar pertinência quando desejar)
PROMPT -- BEGIN
PROMPT --   pkg_job_admin.disable_job('128', p_owner => 'SYS', p_stop_running => TRUE);
PROMPT --   pkg_job_admin.enable_job ('128', p_owner => 'SYS');
PROMPT -- END;
PROMPT -- /

PROMPT -- Lote por padrão (por tipo):
PROMPT -- BEGIN
PROMPT --   pkg_job_admin.set_jobs_by_pattern(
PROMPT --     p_name_like    => 'JOB_SYNC_%',
PROMPT --     p_enable       => FALSE,
PROMPT --     p_owner        => 'DEMOAPP',
PROMPT --     p_type         => pkg_job_admin.c_type_scheduler, -- SCHEDULER | DBMS_JOB | ANY
PROMPT --     p_stop_running => TRUE
PROMPT --   );
PROMPT -- END;
PROMPT -- /

PROMPT -- Visões consolidadas:
PROMPT -- SELECT * FROM job_admin_all_jobs_v ORDER BY owner, job_type, job_name;
PROMPT -- SELECT * FROM job_admin_running_v  ORDER BY running_instance, job_type, job_name;

PROMPT
PROMPT ═════════════════════════════════════════════════════════════════════════════════════════════
PROMPT   Conclusão: job_admin_setup_completo.sql executado.
PROMPT   Para conceder a ROLE a um usuário final, utilize:
PROMPT     GRANT JOBDBMSSCHEDULERALL TO nome_do_usuario;
PROMPT ═════════════════════════════════════════════════════════════════════════════════════════════