<?php
class NRGate {
 public static $journal=[],$deny=false;
 public static $tables=['ydy_nutrition','ydy_nutrition_set','ydy_nutrition_category','ydy_nutrition_time','ydy_user','ydy_config'];
 public static $bootstrap='SELECT `config_key`,`config_value` FROM `ydy_config` WHERE  `status` = :where_AND_status  AND `config_key` IN (:where_AND_config_key_in_1,:where_AND_config_key_in_2,:where_AND_config_key_in_3,:where_AND_config_key_in_4,:where_AND_config_key_in_5,:where_AND_config_key_in_6)';
 public static function sql($sql){
  if(preg_match('/^CALL SP_TABLEDEF\(\?, \?\)$/D',$sql))return 'metadata-definition';
  if(preg_match('/^SHOW (?:FULL )?COLUMNS FROM `?(ydy_[a-z_]+)`?$/iD',$sql,$m)&&in_array(strtolower($m[1]),self::$tables,true))return 'metadata-show';
  if(preg_match('/\b(?:FROM|JOIN)\s+[`"]?ydy_config\b/i',$sql)){if($sql!==self::$bootstrap)throw new RuntimeException('Only exact six-key bootstrap config SELECT');return 'exact-bootstrap-six-clock-keys';}
  if(!preg_match('/^\s*SELECT\b/i',$sql))throw new RuntimeException('Lists readonly gate');$plain=preg_replace("/'(?:''|[^'])*'/s","''",$sql);
  if(preg_match('/;|--|\/\*|\b(INSERT|UPDATE|DELETE|MERGE|DROP|ALTER|CREATE|TRUNCATE|CALL|EXEC|INTO|OUTFILE|SLEEP|BENCHMARK|DBMS_LOCK|DBLINK)\b/i',$plain))throw new RuntimeException('Unsafe readonly SQL');
  preg_match_all('/\b(?:FROM|JOIN)\s+([`"]?[A-Za-z_][A-Za-z_0-9]*[`"]?)/i',$plain,$m);$allowed=array_merge(self::$tables,['user_tab_columns','user_constraints','user_cons_columns','user_objects']);if(!$m[1])throw new RuntimeException('No scoped table');foreach($m[1]as $table)if(!in_array(strtolower(str_replace(['`','"'],'',$table)),$allowed,true))throw new RuntimeException('Unfrozen native read table');return 'select';
 }
 public static function binds($sql,$bound,$params){
  if(strpos($sql,'CALL ')===0){if($sql!=='CALL SP_TABLEDEF(?, ?)'||count($bound)!==2||($bound['1']['value']??'')!=='ZHCT_APP'||($bound['1']['type']??0)!==PDO::PARAM_STR||($bound['2']['type']??0)!==PDO::PARAM_STR||!in_array(strtolower($bound['2']['value']??''),self::$tables,true))throw new RuntimeException('Exact metadata schema/table binds');}
  if($sql===self::$bootstrap){$want=[':where_AND_status'=>[1,PDO::PARAM_INT]];foreach(['morning_start','morning_end','noon_start','noon_end','night_start','night_end']as $i=>$k)$want[':where_AND_config_key_in_'.($i+1)]=[$k,PDO::PARAM_STR];if(count($bound)!==7||!in_array($params,[null,[]],true))throw new RuntimeException('Exact bootstrap parameter set');foreach($want as $k=>$v)if(!isset($bound[$k])||$bound[$k]['value']!==$v[0]||$bound[$k]['type']!==$v[1])throw new RuntimeException('Exact bootstrap clock keys/types');}
 }
}
class NRStatement extends PDOStatement {
 private $bound=[];protected function __construct(){}
 public function bindValue($p,$v,$t=PDO::PARAM_STR){$ok=parent::bindValue($p,$v,$t);$this->bound[(string)$p]=['value'=>$v,'type'=>$t,'php_type'=>gettype($v),'ok'=>$ok];return $ok;}
 public function execute($params=null){NRGate::sql($this->queryString);NRGate::binds($this->queryString,$this->bound,$params);$e=['layer'=>'actual PDOStatement.execute','queryString'=>$this->queryString,'bound'=>$this->bound,'execute_params'=>$params,'success'=>false];try{$ok=parent::execute($params);$e['success']=$ok;$e['rowCount']=$this->rowCount();$e['errorInfo']=$this->errorInfo();NRGate::$journal[]=$e;return $ok;}catch(Throwable $x){$e['error']=['class'=>get_class($x),'message'=>$x->getMessage(),'errorInfo'=>$this->errorInfo()];NRGate::$journal[]=$e;throw $x;}}
 public function fetchAll($how=null,$class_name=null,$ctor_args=null){$rows=parent::fetchAll(...func_get_args());if(count($rows)>200)throw new RuntimeException('Native row cap');$e=['layer'=>'actual PDOStatement.fetchAll','queryString'=>$this->queryString];if($this->queryString===NRGate::$bootstrap){$e['rows_count']=count($rows);$e['rows_sha256']=hash('sha256',json_encode($rows,JSON_PRESERVE_ZERO_FRACTION));$e['config_values_recorded']=false;}else $e['rows']=$rows;NRGate::$journal[]=$e;return $rows;}
}
class NRPdo extends PDO {
 public function prepare($sql,$options=[]){$op=NRGate::sql($sql);$s=parent::prepare($sql,$options);NRGate::$journal[]=['layer'=>'actual PDO.prepare','queryString'=>$sql,'operation'=>$op,'prepared'=>$s!==false];return $s;}
 public function query($sql,...$args){NRGate::sql($sql);if($sql===NRGate::$bootstrap)throw new RuntimeException('Bootstrap requires exact bound prepare');$s=parent::query($sql,...$args);NRGate::$journal[]=['layer'=>'actual PDO.query','queryString'=>$sql];return $s;}
 public function exec($sql){throw new RuntimeException('No lists exec');}
 public function beginTransaction(){throw new RuntimeException('No lists transaction');}
}
