-- ---------------------------------------------------------------------------
-- Os privilegios repetidos: o molde repetido de `privilegios` e as gemeas de
-- `privilegios_usuarios` juntados numa linha cada, e o indice unico nos dois
-- pares -- (grupo, funcionalidade) no molde e (usuario, funcionalidade) nas
-- pessoas (D289, D374).
--
--   mysql franquiabv < scripts/migracao-privilegios-unicos.sql
--
-- RODA NO PASSO 3 DA JANELA, E NAO NO PASSO 0 (a resposta 125 (a) do dono, de
-- 12/10/2026, e a 192 (a): o passo 3 e a etapa 2). Ela APAGA LINHA -- o molde
-- repetido e as gemeas a mais --, e o laco do passo 0 so leva o que acrescenta;
-- a etapa 1 copia aquele laco. Depois de `pre-migracao-limpeza.sql`, cujo bloco 5
-- ja tira o molde repetido pela mesma regra (abaixo): aqui ela e o piso, para o
-- molde repetido que chegar a esta migracao sem a limpeza antes.
--
-- DECIDIDO PELO DONO EM 12/10/2026 (pergunta 125 da pagina do lote, a opcao
-- (a), "As duas regras, com os 117 pares juntados"): "Na copia local ja, nos dois
-- bancos (...); no corte, os 117 juntados no passo 3 e o PostgreSQL nascendo com
-- as duas regras." O PostgreSQL nasce com elas pelo gerador
-- (`$unicidades` de `scripts/gerar-ddl-postgres.php`), e nao pelo `mysql.sql`
-- que o passo 1 refaz da producao, que nao as tem antes da janela.
--
-- O MOLDE REPETIDO FICA O MAIS ANTIGO (o menor id), TAMBEM O DE VALORES
-- DIFERENTES. DECIDIDO PELO DONO EM 17/10/2026 (pergunta 237 da pagina do lote, a
-- opcao (c), "Fica o mais antigo"; o D374): "como a limpeza fazia ate hoje", e a
-- noite nao para. Entre 17 e 18/10/2026 a chave do molde nascia primeiro e, com
-- um molde repetido de valores diferentes, era recusada ("Duplicate entry"): o
-- roteiro parava antes de apagar qualquer linha, para o dono decidir qual molde
-- vale. Agora o molde repetido sai na mesma transacao das gemeas, e as duas chaves
-- nascem depois. Quem entrar no grupo depois do corte -- cadastrado, trocado para
-- ele ou reativado -- recebe o molde que ficou, que pode dar menos, ou mais, do
-- que as duas linhas somadas davam (hoje recebe as duas, e o `Acl` as soma); quem
-- ja esta no grupo nao muda: as linhas dele sao as das pessoas. A LISTA diz cada
-- molde que se resolve aqui, o que fica e os que saem (id:ler escrever).
--
-- A COPIA LOCAL A RECEBEU EM 17/10/2026, nos dois bancos: ali ela diz que nao ha
-- o que juntar e que os indices ja existem. Medido antes, nas duas: 117 pares
-- (pessoa, funcionalidade) com mais de uma linha, 235 linhas, 118 a mais; 1 par
-- de valores diferentes (o ler nas duas, o escrever numa so); 1 par de tres
-- linhas; nenhum molde repetido. A base do teste do BVIP fica como esta ate a
-- proxima copia exportada daqui: o codigo funciona com e sem os indices.
--
-- A REGRA DE QUAL LINHA FICA NAS PESSOAS (a do D289, e a da pergunta 125: "vale
-- o que as linhas davam juntas"): fica a MAIS ANTIGA do par (o menor id), com o
-- OU das linhas -- `ler` se alguma lia, `escrever` se alguma escrevia --, e as
-- outras saem. E o que o `Acl` ja somava, linha a linha: ninguem ganha nem perde
-- acesso. A gravacao da tela nova e da janela do ERP ja deixava o par numa linha
-- so, a mais antiga, desde 29/09/2026 (`UsuarioService::gravarPrivilegio`); isto
-- faz o mesmo de uma vez, nos pares que ninguem gravou desde entao. NO MOLDE, NAO
-- HA O OU: fica o mais antigo como esta (a 237 (c), e nao a (b)).
--
-- IDEMPOTENTE: sem molde repetido nem gemeas, os dois DELETE e o UPDATE mudam
-- zero linhas; com os indices, os dois ALTER dizem que ja existem. Rodar de novo
-- nao muda nada.
--
-- NAO COPIA AS TABELAS: indice secundario sem chave estrangeira o InnoDB cria
-- INPLACE, com LOCK=NONE (139 moldes e cerca de 15 mil linhas de pessoas na
-- copia). Se o servidor recusar o INPLACE, o ALTER falha e nao muda nada -- e a
-- transacao de antes ja foi: rodar de novo nao a refaz (nada mais a juntar), e
-- tenta o ALTER outra vez.
--
-- E O CODIGO ANTIGO NUMA VOLTA: com o indice, o "novo" da janela de permissoes do
-- ERP antigo, sem `id`, num par que ja tem linha -- o caminho de onde as gemeas
-- vieram -- passa a dar erro em vez de gravar a gemea calado. O codigo novo
-- responde com a frase (`UsuarioService::PAR_GRAVADO_NO_MEIO`).
-- ---------------------------------------------------------------------------

SELECT '=== ANTES: moldes repetidos, pares de pessoas repetidos, linhas a mais ===' AS '';
SELECT (SELECT COUNT(*) FROM (SELECT 1 FROM privilegios GROUP BY grupo, funcionalidade HAVING COUNT(*) > 1) m),
       COUNT(*),
       COALESCE(SUM(n) - COUNT(*), 0)
  FROM (SELECT COUNT(*) AS n FROM privilegios_usuarios GROUP BY usuario, funcionalidade HAVING COUNT(*) > 1) p;

-- 1. A LISTA DO MOLDE REPETIDO, de valores iguais ou diferentes: o grupo, a
--    funcionalidade, o que fica (o mais antigo) e os que saem, em id:ler escrever.
--    Vazia quando o bloco 5 da limpeza ja rodou.
SELECT '=== O MOLDE REPETIDO: grupo, funcionalidade, o que fica e os que saem (id:ler escrever) -- fica o mais antigo ===' AS '';
SELECT d.grupo, d.funcionalidade,
       (SELECT CONCAT(f.id, ':', f.ler, f.escrever) FROM privilegios f WHERE f.id = d.fica),
       (SELECT GROUP_CONCAT(CONCAT(s.id, ':', s.ler, s.escrever) ORDER BY s.id)
          FROM privilegios s
         WHERE s.grupo = d.grupo AND s.funcionalidade = d.funcionalidade AND s.id <> d.fica)
  FROM (
    SELECT grupo, funcionalidade, MIN(id) AS fica
      FROM privilegios
     GROUP BY grupo, funcionalidade
    HAVING COUNT(*) > 1
  ) d
 ORDER BY d.grupo, d.funcionalidade;

-- 2. O MOLDE REPETIDO E AS GEMEAS DAS PESSOAS, numa transacao: no molde, o mais
--    antigo fica como esta e os outros saem; nas pessoas, a mais antiga recebe o
--    OU do par, e as outras saem.
START TRANSACTION;

DELETE p FROM privilegios p
  JOIN (
    SELECT grupo, funcionalidade, MIN(id) AS fica
      FROM privilegios
     GROUP BY grupo, funcionalidade
    HAVING COUNT(*) > 1
  ) d ON d.grupo = p.grupo AND d.funcionalidade = p.funcionalidade
 WHERE p.id <> d.fica;

UPDATE privilegios_usuarios p
  JOIN (
    SELECT usuario, funcionalidade, MIN(id) AS fica, MAX(ler) AS ler, MAX(escrever) AS escrever
      FROM privilegios_usuarios
     GROUP BY usuario, funcionalidade
    HAVING COUNT(*) > 1
  ) g ON g.fica = p.id
   SET p.ler = g.ler, p.escrever = g.escrever;

DELETE p FROM privilegios_usuarios p
  JOIN (
    SELECT usuario, funcionalidade, MIN(id) AS fica
      FROM privilegios_usuarios
     GROUP BY usuario, funcionalidade
    HAVING COUNT(*) > 1
  ) g ON g.usuario = p.usuario AND g.funcionalidade = p.funcionalidade
 WHERE p.id <> g.fica;

COMMIT;

-- 3. AS DUAS CHAVES, com o molde e as gemeas ja juntados: a do molde e a das
--    pessoas. Na segunda vez, o aviso de que cada uma ja existe.
SELECT '=== AS CHAVES: o aviso de cada uma que ja existe ===' AS '';

SET @existe = (
    SELECT COUNT(*) FROM information_schema.statistics
     WHERE table_schema = DATABASE()
       AND table_name = 'privilegios'
       AND index_name = 'uq_privilegios_grupo_funcionalidade'
);

SET @sql = IF(@existe = 0,
    'ALTER TABLE privilegios
       ADD UNIQUE KEY uq_privilegios_grupo_funcionalidade (grupo, funcionalidade),
       ALGORITHM=INPLACE, LOCK=NONE',
    'SELECT ''o indice do molde ja existe, nada a fazer'' AS aviso'
);

PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @existe = (
    SELECT COUNT(*) FROM information_schema.statistics
     WHERE table_schema = DATABASE()
       AND table_name = 'privilegios_usuarios'
       AND index_name = 'uq_privilegios_usuarios_usuario_funcionalidade'
);

SET @sql = IF(@existe = 0,
    'ALTER TABLE privilegios_usuarios
       ADD UNIQUE KEY uq_privilegios_usuarios_usuario_funcionalidade (usuario, funcionalidade),
       ALGORITHM=INPLACE, LOCK=NONE',
    'SELECT ''o indice das pessoas ja existe, nada a fazer'' AS aviso'
);

PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SELECT '=== DEPOIS: moldes repetidos, pares de pessoas repetidos (tem que ser zero e zero) ===' AS '';
SELECT (SELECT COUNT(*) FROM (SELECT 1 FROM privilegios GROUP BY grupo, funcionalidade HAVING COUNT(*) > 1) m),
       (SELECT COUNT(*) FROM (SELECT 1 FROM privilegios_usuarios GROUP BY usuario, funcionalidade HAVING COUNT(*) > 1) p);

SELECT '=== INDICES: o nome, as colunas e se e unico (0) ===' AS '';
SELECT index_name, GROUP_CONCAT(column_name ORDER BY seq_in_index), MAX(non_unique)
  FROM information_schema.statistics
 WHERE table_schema = DATABASE()
   AND index_name IN ('uq_privilegios_grupo_funcionalidade', 'uq_privilegios_usuarios_usuario_funcionalidade')
 GROUP BY table_name, index_name
 ORDER BY index_name;
