<?php
/**
 * Demo Oracle — detail d'un SQL_ID (section « sql/detail »).
 *
 * Portage de modules/oracle/_report_sql_detail.php. La page est atteinte par un
 * SQL_ID, depuis le menu contextuel des Top SQL du temps reel ou de la liste
 * des sessions actives ; elle n'a pas d'entree de menu propre.
 *
 * CINQ ONGLETS SERVIS
 *   Plans d'execution     identiques, arbre complet, predicats, plans adaptables
 *   Efficacite des plans  identique
 *   Texte SQL             identique, mise en forme et coloration comprises
 *   Historique des charges identique
 *   SQL Monitoring        les executions de ce SQL_ID, reconstituees depuis
 *                         GV$SESSION. GV$SQL_MONITOR n'est toujours PAS
 *                         interroge : la demonstration sert ce rapport sans
 *                         aucune licence (cf. reports/sqlmon).
 *
 * TROIS ONGLETS RESERVES A LA VERSION COMPLETE
 *   Objets lies              les donnees ne sont PAS lues : ni statistiques de
 *                            table, ni colonnes, ni index. Le repository n'en
 *                            porte meme pas les requetes.
 *   Statistiques d'execution ces deux-la se calculent cote navigateur a partir
 *   Historique des plans     de donnees deja servies (charges et efficacite).
 *                            Il n'y a donc rien a retirer du serveur : ce sont
 *                            leurs fonctions de rendu qui ne sont pas livrees
 *                            dans le JavaScript de la demonstration.
 *
 * DEUX RETRAITS DANS L'ONGLET DES PLANS
 *   Les directives outline sont supprimees de chaque plan avant l'envoi au
 *   navigateur : elles sont la matiere premiere d'un SQL Profile.
 *   Le bouton « Fixer ce plan » disparait avec elles, et
 *   OracleSqlFixRepository — SQL Profile, SQL Patch, SQL Plan Baseline —
 *   n'est pas porte du tout.
 *
 * Inclus par report.php. Variables : $oraDb, $db, $currentUser, $settings.
 */
require_once __DIR__ . '/lib/DemoOracleOciConnect.php';
require_once __DIR__ . '/lib/DemoOracleAshSource.php';
require_once __DIR__ . '/lib/DemoOracleSqlDetailRepository.php';
require_once __DIR__ . '/lib/DemoOracleSqlXml.php';
require_once __DIR__ . '/lib/DemoOracleConfig.php';
require_once __DIR__ . '/lib/DemoOracleSqlMonitorRepository.php';

/* ── SQL_ID ──────────────────────────────────────────────────────────────── */
$sqlId = trim((string)($_GET['sql_id'] ?? ''));
if (!preg_match('/^[0-9a-z]{13}$/i', $sqlId)) { ?>
<div class="alert alert-error"><?= t('demo_oracle.sqld_bad_id') ?></div>
<?php return; }

/* ── Lecture ─────────────────────────────────────────────────────────────── */
$ociError   = null;
$fetchError = null;
$hasLicense = false;
$plans      = [];
/* `hist` et `hist_source` plutot qu'une clef nommee `awr` : le repli
   sw$bash_snapper atterrissait sous ce nom, et l'ecran affichait « AWR » sur
   des donnees qui n'en venaient pas. La source voyage avec ses donnees. */
$efficiency = ['memory' => [], 'hist' => [], 'hist_source' => ''];
$sqlText    = ['source' => '', 'text' => ''];
$memPhvs    = [];
$loadHist   = ['rows' => [], 'source' => ''];
$hasBashSnapper = false;
$packAccess     = 'NONE';
$licenseFrom    = '';   // 'cloud' ou 'param' : sur quelle base la licence a ete decidee
$sqlMon     = [];

$conn = demo_oracle_oci_open($oraDb, $db, $ociError, '', 'sql/detail');
if (!$conn) {
    $fetchError = $ociError
        ? ('ORA-' . $ociError['code'] . ': ' . $ociError['message'])
        : t('demo_oracle.connect_error');
} else {
    /* ATTENTION : sur une connexion cloud, hasDiagnosticPack() renvoie vrai
       SANS lire le parametre d'instance — une base declaree cloud est reputee
       licenciee. Choix assume, mais qui doit se VOIR : d'ou $licenseFrom,
       affiche par la vue. */
    $isCloud     = ($oraDb['conn_type'] ?? '') === 'cloud';
    $licenseFrom = $isCloud ? 'cloud' : 'param';
    $hasLicense  = DemoOracleAshSource::hasDiagnosticPack($conn, $isCloud);
    $packAccess  = $isCloud ? 'DIAGNOSTIC+TUNING (Cloud)'
                            : DemoOracleAshSource::packAccess($conn);

    $hasBashSnapper = !$hasLicense && DemoOracleAshSource::hasBashSnapper($conn);

    $planCols = "p.plan_hash_value, p.id, p.parent_id, p.depth, p.operation, p.options,
        NVL(p.object_owner,'') object_owner, NVL(p.object_name,'') object_name,
        NVL(p.object_alias,'') object_alias, p.cost, p.cpu_cost, p.io_cost, p.cardinality, p.bytes, p.time,
        NVL(p.access_predicates,'') access_predicates,
        NVL(p.filter_predicates,'') filter_predicates,
        NVL(p.projection,'') projection,
        NVL(p.partition_start,'') partition_start, NVL(p.partition_stop,'') partition_stop";

    /* Plans en memoire — GV$SQL_PLAN, RAC-aware, sans licence.
       Un meme SQL peut avoir plusieurs curseurs enfants partageant un PHV ;
       on ne garde qu'une entree par PHV. */
    $rows  = DemoOracleSqlDetailRepository::getPlanRows($conn, $sqlId, $planCols);
    $byPhv = [];
    foreach ($rows as $r) $byPhv[$r['phv']][] = $r;
    foreach ($byPhv as $phv => $steps) {
        $xi = _demo_sqld_parseXml(DemoOracleSqlDetailRepository::getOtherXml($conn, $sqlId, $phv, 'memory'));
        /* OTHER_XML memoire vide -> tenter l'historique AWR du meme plan.
           Le test d'inanite regarde AUSSI les outlines, et c'est voulu : elles
           sont retirees plus bas, une fois la source choisie. Les retirer
           avant fausserait ce test et declencherait des lectures AWR
           inutiles. */
        if ($hasLicense && _demo_sqld_xmlEmpty($xi)) {
            $xiAwr = _demo_sqld_parseXml(DemoOracleSqlDetailRepository::getOtherXml($conn, $sqlId, $phv, 'awr'));
            if (!_demo_sqld_xmlEmpty($xiAwr)) $xi = $xiAwr;
        }
        $plans[]   = ['phv' => $phv, 'source' => 'memory', 'xml_info' => $xi, 'steps' => $steps];
        $memPhvs[] = $phv;
    }

    /* Plans historises — DBA_HIST_SQL_PLAN, licence Diagnostic Pack requise. */
    if ($hasLicense) {
        $rows = DemoOracleSqlDetailRepository::getAwrPlanRows($conn, $sqlId, $planCols);
        $awrByPhv = [];
        foreach ($rows as $r) $awrByPhv[$r['phv']][] = $r;
        foreach ($awrByPhv as $phv => $steps) {
            if (in_array($phv, $memPhvs)) continue;
            $xi = _demo_sqld_parseXml(DemoOracleSqlDetailRepository::getOtherXml($conn, $sqlId, $phv, 'awr'));
            $plans[] = ['phv' => $phv, 'source' => 'awr', 'xml_info' => $xi, 'steps' => $steps];
        }
    }

    /* Efficacite */
    $efficiency['memory'] = DemoOracleSqlDetailRepository::getEfficiencyFromMemory($conn, $sqlId);
    /* La source est enregistree AVEC les lignes : sans elle, la page ne peut
       que deviner, et elle devinait mal. */
    if ($hasLicense) {
        $efficiency['hist']        = DemoOracleSqlDetailRepository::getEfficiencyFromAwr($conn, $sqlId);
        $efficiency['hist_source'] = 'awr';
    } elseif ($hasBashSnapper) {
        $efficiency['hist']        = DemoOracleSqlDetailRepository::getEfficiencyFromBashSnapper($conn, $sqlId);
        $efficiency['hist_source'] = 'bash';
    }

    /* Texte SQL */
    $t = DemoOracleSqlDetailRepository::getSqlTextFromMemory($conn, $sqlId);
    if ($t !== '') $sqlText = ['source' => 'memory', 'text' => $t];
    if ($sqlText['text'] === '' && $hasLicense) {
        $t = DemoOracleSqlDetailRepository::getSqlTextFromAwr($conn, $sqlId);
        if ($t !== '') $sqlText = ['source' => 'awr', 'text' => $t];
    }

    /* Historique des charges */
    /* Le repli sw$bash_snapper ne lit PAS SW$BASH_HIST_SQLSTAT mais
       SW$BASH_HIST_ACTIVE_SESS_HISTORY, qui ne porte ni lectures logiques, ni
       lectures physiques, ni lignes, ni parses, ni fetches : ces colonnes
       arrivent a zero. La source transmise permet a la vue de le dire. */
    if ($hasLicense) {
        $loadHist['rows']   = DemoOracleSqlDetailRepository::getLoadHistoryFromAwr($conn, $sqlId);
        $loadHist['source'] = 'awr';
    } elseif ($hasBashSnapper) {
        $loadHist['rows']   = DemoOracleSqlDetailRepository::getLoadHistoryFromBashSnapper($conn, $sqlId);
        $loadHist['source'] = 'bash';
    }

    /* Objets lies : rien n'est lu, le repository n'en porte pas les requetes. */

    /* SQL Monitoring : les executions de ce SQL_ID, reconstituees depuis
       GV$SESSION. GV$SQL_MONITOR n'est toujours pas interroge — cet onglet
       est servi SANS licence, comme la section reports/sqlmon. */
    $sqlMon = DemoOracleSqlMonitorRepository::listExecutionsSession(
        $conn, $sqlId, DEMO_ORACLE_SQLMON_MAX_EXECUTIONS);

    oci_close($conn);
}

/*
 * Retrait des outlines, une fois les plans constitues.
 *
 * Une outline est la liste de hints qui reproduit exactement un plan : c'est
 * ce que la version complete injecte dans un SQL Profile, un SQL Patch ou une
 * baseline pour figer ce plan. La demonstration ne sait pas fixer un plan ;
 * lui laisser la matiere premiere n'aurait servi qu'a l'exposer.
 *
 * Le reste des metadonnees OTHER_XML — profil applique, usage des hints, plan
 * adaptable, degre de parallelisme, binds peeked — est conserve : il explique
 * le plan sans permettre de le contraindre.
 */
foreach ($plans as &$_p) {
    $_p['xml_info']['outline'] = [];
}
unset($_p);

$notFound = empty($plans) && $sqlText['text'] === '';

View::display(__DIR__ . '/views/report/sql_detail.php', [
    'sqlId'      => $sqlId,
    'fetchError' => $fetchError,
    'notFound'   => $notFound,
    'hasLicense' => $hasLicense,
    'packAccess'     => $packAccess,
    'licenseFrom'    => $licenseFrom,
    'hasBashSnapper' => $hasBashSnapper,
    'memPhvs'    => $memPhvs,
    'plans'      => $plans,
    'efficiency' => $efficiency,
    'sqlText'    => $sqlText,
    'loadHist'   => $loadHist,
    'sqlMon'     => $sqlMon,
    'sqlMonUrl'  => demo_oracle_url('report', ['section' => 'reports/sqlmon']),
    'settings'   => $settings,
]);
