<?php
/**
 * Demo Oracle — analyse du bloc OTHER_XML d'un plan d'execution.
 *
 * Extrait de _report_sql_detail.php (lui-meme porte de
 * modules/oracle/_report_sql_detail.php, ou ces fonctions vivent dans le
 * controleur). Elles sont ici pour une raison precise : un banc de test doit
 * pouvoir les exercer sans declencher la connexion Oracle du controleur.
 *
 * OTHER_XML porte tout ce qu'Oracle sait d'un plan sans le redire dans les
 * lignes : profil applique, usage des hints, plan adaptable, degre de
 * parallelisme, binds peekes, et les directives OUTLINE qui reproduisent le
 * plan a l'identique.
 *
 * Les outlines SONT analysees ici, et c'est voulu : _demo_sqld_xmlEmpty() s'en
 * sert pour decider si l'OTHER_XML memoire est exploitable ou s'il faut aller
 * chercher celui de l'AWR. C'est le controleur qui les vide ensuite, une fois
 * la source choisie — la demonstration ne sait pas fixer un plan, lui laisser
 * la matiere premiere n'aurait servi qu'a l'exposer.
 *
 * Safe to include multiple times (gardes function_exists).
 */

if (!function_exists('_demo_sqld_dtyName')) {
    function _demo_sqld_dtyName(int $dty): string
    {
        return match($dty) {
            1   => 'VARCHAR2', 2   => 'NUMBER',  8   => 'LONG',
            12  => 'DATE',     23  => 'RAW',     24  => 'LONG RAW',
            69  => 'ROWID',    96  => 'CHAR',    100 => 'BINARY_FLOAT',
            101 => 'BINARY_DOUBLE', 112 => 'CLOB', 113 => 'BLOB',
            180 => 'TIMESTAMP', 181 => 'TIMESTAMP WITH TZ',
            182 => 'INTERVAL YEAR TO MONTH', 183 => 'INTERVAL DAY TO SECOND',
            231 => 'TIMESTAMP WITH LOCAL TZ',
            default => 'TYPE(' . $dty . ')',
        };
    }
}
if (!function_exists('_demo_sqld_parseXml')) {
    function _demo_sqld_parseXml(string $xml): array
    {
        $r = [
            'db_version'          => '',
            'sql_profile'         => '',    // nom du profil ou '' si absent
            'hint_usage'          => '',    // texte brut de l'utilisation des hints
            'adaptive'            => false, // plan adaptable
            'parallel_dop'        => null,  // degré de parallélisme (int|string|null)
            'cardinality_feedback'=> false, // feedback de cardinalité
            'dynamic_sampling'    => null,  // niveau d'échantillonnage dynamique (int)
            'bind_equiv_failure'  => '',    // message d'échec d'équivalence de bind
            'peeked_binds'        => [],    // [{name, pos, type, value}]
            'outline'             => [],    // hints de reproduction du plan
            'raw_info'            => [],    // tous les <info> key=>value
            'display_map'         => [],    // adaptive plan : carte de présentation
                                            // keyed by op (= original ID dans v$sql_plan)
                                            // → {dis, par, dep, skp}
                                            // skp=1 ⇒ étape non retenue (estompée)
        ];
        if ($xml === '') return $r;
        libxml_use_internal_errors(true);
        try {
            $doc = @new SimpleXMLElement($xml);
            if (isset($doc->db_version)) $r['db_version'] = (string)$doc->db_version;
            foreach ($doc->info as $n) {
                $t = (string)($n['type'] ?? '');
                $v = trim((string)$n);
                $r['raw_info'][$t] = $v;
                switch ($t) {
                    case 'sql_profile':
                        $r['sql_profile'] = $v;
                        break;
                    case 'hint_usage':
                        $r['hint_usage'] = $v;
                        break;
                    case 'adaptive_plan':
                        $r['adaptive'] = true;
                        break;
                    case 'parallel':
                        if ($v !== '' && strtolower($v) !== 'no') {
                            preg_match('/\d+/', $v, $m);
                            $r['parallel_dop'] = !empty($m[0]) ? (int)$m[0] : $v;
                        }
                        break;
                    case 'cardinality_feedback':
                        $r['cardinality_feedback'] = ($v !== '' && $v !== '0');
                        break;
                    case 'dynamic_sampling':
                        if (is_numeric($v)) $r['dynamic_sampling'] = (int)$v;
                        break;
                    case 'db_version':
                        $r['db_version'] = $v;
                        break;
                    case 'bind_equiv_failure':
                        $r['bind_equiv_failure'] = $v;
                        break;
                }
            }
            /* Peeked bind variables */
            if (isset($doc->peeked_binds->bind)) {
                foreach ($doc->peeked_binds->bind as $bind) {
                    $r['peeked_binds'][] = [
                        'name'  => (string)($bind['nam'] ?? ''),
                        'pos'   => (int)($bind['pos'] ?? 0),
                        'type'  => _demo_sqld_dtyName((int)($bind['dty'] ?? 0)),
                        'value' => isset($bind->v) ? (string)$bind->v : trim((string)$bind),
                    ];
                }
            }
            /* Outline hints */
            if (isset($doc->outline_data->hint))
                foreach ($doc->outline_data->hint as $h) $r['outline'][] = (string)$h;

            /* display_map (plans adaptables) — chaque <row> donne la
               présentation à utiliser pour une étape du plan :
                 op  → ID original (= v$sql_plan.id, sert de clé de mapping)
                 dis → ID à afficher (renuméroté pour l'adaptive)
                 par → ID parent dans l'arbre d'affichage
                 dep → profondeur dans l'arbre
                 skp → 0 = étape retenue, 1 = étape estompée (non retenue)
               Si display_map est absente, on garde le rendu naturel
               (utilisé par les plans non-adaptables). */
            if (isset($doc->display_map->row)) {
                foreach ($doc->display_map->row as $row) {
                    $a  = $row->attributes();
                    $op = isset($a['op']) ? (int)$a['op'] : -1;
                    if ($op < 0) continue;
                    $r['display_map'][$op] = [
                        'dis' => (int)($a['dis'] ?? 0),
                        'par' => (int)($a['par'] ?? -1),
                        'dep' => (int)($a['dep'] ?? 0),
                        'skp' => (int)($a['skp'] ?? 0),
                    ];
                }
            }
        } catch (Exception $ignored) {}
        libxml_clear_errors();
        return $r;
    }
}

/* Vrai si l'OTHER_XML parsé ne contient aucune métadonnée exploitable
   (miroir de la condition _hasXml du JS) → permet un fallback mémoire → AWR. */
if (!function_exists('_demo_sqld_xmlEmpty')) {
    function _demo_sqld_xmlEmpty(array $xi): bool
    {
        return ($xi['sql_profile']        ?? '')   === ''
            && ($xi['hint_usage']         ?? '')   === ''
            && empty($xi['adaptive'])
            && empty($xi['cardinality_feedback'])
            && ($xi['parallel_dop']       ?? null) === null
            && ($xi['dynamic_sampling']   ?? null) === null
            && ($xi['bind_equiv_failure'] ?? '')   === ''
            && empty($xi['peeked_binds'])
            && empty($xi['outline']);
    }
}
