<?php
declare(strict_types=1);
function consumeSQL($kind){global $runtime;$f=fopen($runtime.'/sql-budget.jsonl','c+');ck($f!==false&&flock($f,LOCK_EX),'SQL budget lock');$raw=stream_get_contents($f);$n=$raw===''?0:count(explode("\n",rtrim($raw,"\n")));ck($n<600,'Whole readonly SQL cap600');fseek($f,0,SEEK_END);fwrite($f,json_encode(['n'=>$n+1,'kind'=>$kind,'at'=>microtime(true)])."\n");flock($f,LOCK_UN);fclose($f);chmod($runtime.'/sql-budget.jsonl',0600);}
final class CoreGate{
 public static $scenario='';public static $ids=[];public static $started=0;public static $meta=false;public static $total=0;public static $counts=[];public static $generated=[];public static $last=[];
 public static $tables=['AI'=>['store','store_setting','upload_file','user','user_oauth','user_points_log','order_refund','order_goods','goods_image','sign','sign_user','user_weight'],'ZHCT'=>['user','measurement_session','measurement_session_raw','health_record']];
 public static function rec($r){$r['scenario']=self::$scenario;event('business-native.jsonl',$r);}
 public static function kind($db,$sql){$p=preg_replace("/'(?:''|\\\\.|[^'\\\\])*'/s",'?', $sql);ck(!preg_match('/;|--|\/\*|\*\/|#|\b(?:INSERT|UPDATE|DELETE|REPLACE|MERGE|CREATE|ALTER|DROP|TRUNCATE|GRANT|REVOKE|OUTFILE|DUMPFILE|LOAD|EXEC|EXECUTE|SLEEP|BENCHMARK|UTL_HTTP|NEXTVAL)\b/i',$p),'Readonly0DMLDDL gate');
  if(preg_match('/^\s*SELECT\b/i',$sql)){ck(!preg_match('/\bINTO\b/i',$p),'No SELECT INTO');preg_match_all('/\b(yoshop|ydy)_([a-z0-9_]+)/i',$sql,$m,PREG_SET_ORDER);foreach($m as $r)ck(($db==='AI'?strtolower($r[1])==='yoshop':strtolower($r[1])==='ydy')&&in_array(strtolower($r[2]),self::$tables[$db],true),'Exact source-read table scope '.$r[0]);return 'SELECT';}
  if(preg_match('/^SHOW FULL COLUMNS FROM `((yoshop|ydy)_([a-z_]+))`$/D',$sql,$m)){ck(($db==='AI'?$m[2]==='yoshop':$m[2]==='ydy')&&in_array($m[3],self::$tables[$db],true),'Exact readonly SHOW metadata');return 'metadata_SHOW';}
  if($sql==='CALL SP_TABLEDEF(?, ?)'&&self::$meta)return 'metadata_CALL';throw new RuntimeException('Unknown non-read source SQL');
 }
 public static function plan($db,$sql,$bind){$kind=self::kind($db,$sql);$values=array_map(static function($x){return is_array($x)?$x[0]:$x;},$bind);
  if($kind==='metadata_CALL'){ck(count($values)===2&&$values[0]===($db==='AI'?'AIZHCT_APP':'ZHCT_APP')&&in_array(strtolower($values[1]),array_map(static function($n)use($db){return ($db==='AI'?'yoshop_':'ydy_').$n;},self::$tables[$db]),true),'Exact original metadata procedure scope');}
  if($kind==='SELECT'&&preg_match('/\bUSER_(?:TAB_COLUMNS|CONSTRAINTS|CONS_COLUMNS)\b/i',$sql)){ck(isset($values[0])&&in_array(strtolower((string)$values[0]),array_map(static function($n)use($db){return ($db==='AI'?'yoshop_':'ydy_').$n;},self::$tables[$db]),true),'Exact metadata table bind');}
 }
}
class ReadStatement extends PDOStatement{private $bound=[];private $db='';protected function __construct($db){$this->db=$db;}public function bindValue($p,$v,$type=PDO::PARAM_STR){$ok=parent::bindValue($p,$v,$type);$this->bound[(string)$p]=['value'=>$v,'pdo_type'=>$type,'php_type'=>gettype($v),'ok'=>$ok];return $ok;}public function bindParam($p,&$v,$type=PDO::PARAM_STR,$length=0,$options=null){throw new RuntimeException('No bindParam');}public function execute($params=null){$kind=CoreGate::kind($this->db,$this->queryString);consumeSQL('original.'.$kind);CoreGate::$total++;$r=['connection'=>$this->db,'layer'=>'actual PDOStatement.execute','queryString'=>$this->queryString,'bindValue'=>$this->bound,'execute_array'=>$params,'operation'=>$kind];try{$ok=parent::execute($params);$r['success']=$ok;$r['rowCount']=$this->rowCount();CoreGate::rec($r);return $ok;}catch(Throwable $e){$r['error']=['class'=>get_class($e),'message'=>$e->getMessage()];CoreGate::rec($r);throw $e;}}}
class ReadPDO extends PDO{public $db;public function prepare($sql,$options=[]){$kind=CoreGate::kind($this->db,$sql);$s=parent::prepare($sql,$options);CoreGate::rec(['connection'=>$this->db,'layer'=>'actual PDO.prepare','queryString'=>$sql,'operation'=>$kind,'success'=>$s!==false]);return $s;}public function query($sql,...$args){$s=$this->prepare($sql);$s->execute();if($args)$s->setFetchMode(...$args);return $s;}public function exec($sql){throw new RuntimeException('Readonly0exec/transaction');}public function beginTransaction(){throw new RuntimeException('Readonly0transaction');}public function commit(){throw new RuntimeException('Readonly0commit');}public function rollBack(){throw new RuntimeException('Readonly0rollback');}public function lastInsertId($name=null){throw new RuntimeException('Readonly0identitygeneration');}}
trait ReadConnection{protected function createPdo($dsn,$username,$password,$params){$db=$this->getConfig('prefix')==='yoshop_'?'AI':'ZHCT';$dm=getenv('POC_DIALECT')==='dm';$want=$dm?'dm:host=127.0.0.1;port='.getenv('POC_'.$db.'_PORT').';schema='.getenv('POC_'.$db.'_SCHEMA'):'mysql:host='.getenv('POC_'.$db.'_HOST').';port='.getenv('POC_'.$db.'_PORT').';dbname='.getenv('POC_'.$db.'_SCHEMA').';charset='.getenv('POC_'.$db.'_CHARSET');ck($dsn===$want&&$username===getenv('POC_'.$db.'_USER')&&$password===getenv('POC_'.$db.'_PASSWORD')&&!isset($params[1002]),'Exact approved two native targets');$p=new ReadPDO($dsn,$username,$password,$params);$p->db=$db;ck($p->setAttribute(PDO::ATTR_STATEMENT_CLASS,[ReadStatement::class,[$db]]),'Native statement capture');return $p;}public function getPDOStatement(string $sql,array $bind=[],bool $master=false,bool $procedure=false):PDOStatement{$db=$this->getConfig('prefix')==='yoshop_'?'AI':'ZHCT';CoreGate::$meta=$sql==='CALL SP_TABLEDEF(?, ?)';try{CoreGate::plan($db,$sql,$bind);CoreGate::rec(['connection'=>$db,'layer'=>'original framework bind plan','queryString'=>$sql,'bind_plan'=>$bind,'procedure'=>$procedure]);return parent::getPDOStatement($sql,$bind,$master,$procedure);}catch(Throwable $e){CoreGate::rec(['connection'=>$db,'layer'=>'source gate/plan error','queryString'=>$sql,'bind_plan'=>$bind,'error'=>['class'=>get_class($e),'message'=>$e->getMessage()]]);throw $e;}finally{CoreGate::$meta=false;}}}
class CoreMysql extends \think\db\connector\Mysql{use ReadConnection;public function getBuilderClass():string{return \think\db\builder\Mysql::class;}}
class CoreDm extends \app\common\database\Dm{use ReadConnection;}
