<?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<800,'Whole readonly SQL cap800');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);}
require __DIR__.'/CoreGate.php';
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;}
